diff --git a/Knot.files b/Knot.files
index f99aa1d3f572d425163b7811180e5d50b11fc480..4a28db2c2f879e382c2f90c1307171d3e85d446c 100644
--- a/Knot.files
+++ b/Knot.files
@@ -14,6 +14,7 @@ doc/reference.texi
 doc/requirements.texi
 doc/running.texi
 doc/security.texi
+doc/synth_record.texi
 doc/troubleshooting.texi
 libtap/Makefile.am
 libtap/runtests.c
@@ -208,6 +209,8 @@ src/libknot/packet/wire.h
 src/libknot/processing/process.c
 src/libknot/processing/process.h
 src/libknot/rdata.h
+src/libknot/rr.c
+src/libknot/rr.h
 src/libknot/rrset-dump.c
 src/libknot/rrset-dump.h
 src/libknot/rrset.c
@@ -290,3 +293,5 @@ tests/slab.c
 tests/wire.c
 tests/zonedb.c
 tests/ztree.c
+src/knot/server/serialization.c
+src/knot/server/serialization.h
diff --git a/src/Makefile.am b/src/Makefile.am
index 1059ce8c0c738ad36a39ad29b271d2d3781a0519..cc67ce21a9516bb6c3319dac05cf4e3746611aa0 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -172,6 +172,8 @@ libknot_la_SOURCES =				\
 	libknot/rdata.h				\
 	libknot/rrset-dump.c			\
 	libknot/rrset-dump.h			\
+	libknot/rr.c				\
+	libknot/rr.h				\
 	libknot/rrset.c				\
 	libknot/rrset.h				\
 	libknot/tsig-op.c			\
@@ -254,6 +256,8 @@ libknotd_la_SOURCES =				\
 	knot/server/zone-load.h			\
 	knot/server/zones.c			\
 	knot/server/zones.h			\
+	knot/server/serialization.c		\
+	knot/server/serialization.h		\
 	knot/updates/acl.c			\
 	knot/updates/acl.h			\
 	knot/updates/changesets.c		\
diff --git a/src/common/descriptor.c b/src/common/descriptor.c
index 4a74a3d702d2f4315edf019c1ef1e73f18e601ac..ace5623c897b209dfcb39787dee32ed090e274d5 100644
--- a/src/common/descriptor.c
+++ b/src/common/descriptor.c
@@ -323,3 +323,11 @@ int knot_rrtype_is_ddns_forbidden(const uint16_t type)
 	       type == KNOT_RRTYPE_NSEC       ||
 	       type == KNOT_RRTYPE_NSEC3;
 }
+
+int knot_rrtype_additional_needed(const uint16_t rrtype)
+{
+	return (rrtype == KNOT_RRTYPE_NS ||
+		rrtype == KNOT_RRTYPE_MX ||
+		rrtype == KNOT_RRTYPE_SRV);
+}
+
diff --git a/src/common/descriptor.h b/src/common/descriptor.h
index b8d3b3d55aaf54863a9b3b53fea43543fd20e832..6e1cb999868f57cc5012cc9dd97ad3ffd4113937 100644
--- a/src/common/descriptor.h
+++ b/src/common/descriptor.h
@@ -287,6 +287,18 @@ int knot_rrtype_is_metatype(const uint16_t type);
  */
 int knot_rrtype_is_ddns_forbidden(const uint16_t type);
 
+/*!
+ * \brief Checks whether the given type requires additional processing.
+ *
+ * Only MX, NS and SRV types require additional processing.
+ *
+ * \param rrtype Type to check.
+ *
+ * \retval <> 0 if additional processing is needed for \a qtype.
+ * \retval 0 otherwise.
+ */
+int knot_rrtype_additional_needed(const uint16_t rrtype);
+
 #endif // _KNOT_DESCRIPTOR_H_
 
 /*! @} */
diff --git a/src/common/errcode.c b/src/common/errcode.c
index 0c0d49a664145a00483d8986cf33e7cebafb71c4..37ab4bb3b48f53d1b411448b0f53008f3b89bd51 100644
--- a/src/common/errcode.c
+++ b/src/common/errcode.c
@@ -39,7 +39,7 @@ const error_table_t knot_error_msgs[] = {
 	{ KNOT_ERANGE, "Value is out of range." },
 
 	/* General errors. */
-	{ KNOT_ERROR, "General error." },
+	{ -10000 /*KNOT_ERROR*/, "General error." },
 	{ KNOT_ENOTRUNNING, "Resource is not running." },
 	{ KNOT_EPARSEFAIL, "Parser failed." },
 	{ KNOT_EEXPIRED, "Resource is expired." },
diff --git a/src/common/hattrie/hat-trie.c b/src/common/hattrie/hat-trie.c
index 692365ce77f324b268d3829e26be8779005fca3f..0993635026a8421f096215e65fc2401b383d8afc 100644
--- a/src/common/hattrie/hat-trie.c
+++ b/src/common/hattrie/hat-trie.c
@@ -317,6 +317,10 @@ hattrie_t* hattrie_dup(const hattrie_t* T, value_t (*nval)(value_t))
 {
     hattrie_t *N = hattrie_create_n(T->bsize, &T->mm);
 
+    if (nval == NULL) {
+        return N;
+    }
+
     /* assignment */
     if (!nval) nval = hattrie_setval;
 
diff --git a/src/common/lists.c b/src/common/lists.c
index 3839f70387bac59a2ef0b1d6e40d9ff23492ed56..e59721cedca85946914c19982a39758f08d69cad 100644
--- a/src/common/lists.c
+++ b/src/common/lists.c
@@ -185,7 +185,7 @@ size_t list_size(const list_t *l)
  */
 ptrnode_t *ptrlist_add(list_t *to, const void *val, mm_ctx_t *mm)
 {
-	ptrnode_t *node = mm->alloc(mm->ctx, sizeof(ptrnode_t));
+	ptrnode_t *node = mm_alloc(mm , sizeof(ptrnode_t));
 	if (node == NULL) {
 		return NULL;
 	} else {
@@ -204,7 +204,7 @@ void ptrlist_free(list_t *list, mm_ctx_t *mm)
 {
 	node_t *n = NULL, *nxt = NULL;
 	WALK_LIST_DELSAFE(n, nxt, *list) {
-		mm->free(n);
+		mm_free(mm, n);
 	}
 	init_list(list);
 }
diff --git a/src/common/mempattern.c b/src/common/mempattern.c
index 9bdfe8a5bf3ae469f1b2d8856454c1d1368d19ff..e89ebc2097d1dcbca72c360298dbf022c5297761 100644
--- a/src/common/mempattern.c
+++ b/src/common/mempattern.c
@@ -43,27 +43,6 @@ void *mm_alloc(mm_ctx_t *mm, size_t size)
 	}
 }
 
-void *mm_realloc(mm_ctx_t *mm, void *what, size_t size, size_t prev_size)
-{
-	if (mm) {
-		void *p = mm->alloc(mm->ctx, size);
-		if (knot_unlikely(p == NULL)) {
-			return NULL;
-		} else {
-			if (what) {
-				memcpy(p, what,
-				       prev_size < size ? prev_size : size);
-			}
-			if (mm->free) {
-				mm->free(what);
-			}
-			return p;
-		}
-	} else {
-		return realloc(what, size);
-	}
-}
-
 void mm_free(mm_ctx_t *mm, void *what)
 {
 	if (mm) {
diff --git a/src/common/mempattern.h b/src/common/mempattern.h
index a02726849825dd671eb9d2cfdc5b5ded10c855ab..81714071d56f96afcf496f9649163b9beca5dedc 100644
--- a/src/common/mempattern.h
+++ b/src/common/mempattern.h
@@ -46,8 +46,6 @@ typedef struct mm_ctx {
 /*! \brief Allocs using 'mm' if any, uses system malloc() otherwise. */
 void *mm_alloc(mm_ctx_t *mm, size_t size);
 /*! \brief Reallocs using 'mm' if any, uses system realloc() otherwise. */
-void *mm_realloc(mm_ctx_t *mm, void *what, size_t size, size_t prev_size);
-/*! \brief Frees using 'mm' if any, uses system free() otherwise. */
 void mm_free(mm_ctx_t *mm, void *what);
 
 /*! \brief Initialize default memory allocation context. */
diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y
index 29095d84f21a211c79700b0d4a9780e86fc5bc31..58e3f0aeb38c7cb8d9fb6206d570389ce5a70ccf 100644
--- a/src/knot/conf/cf-parse.y
+++ b/src/knot/conf/cf-parse.y
@@ -306,12 +306,12 @@ static int conf_key_exists(void *scanner, char *item)
 	WALK_LIST (r, new_config->keys) {
 		if (knot_dname_cmp(r->k.name, sample) == 0) {
 			cf_error(scanner, "key '%s' is already defined", item);
-			knot_dname_free(&sample);
+			knot_dname_free(&sample, NULL);
 			return 1;
 		}
 	}
 
-	knot_dname_free(&sample);
+	knot_dname_free(&sample, NULL);
 	return 0;
 }
 
@@ -328,13 +328,13 @@ static int conf_key_add(void *scanner, knot_tsig_key_t **key, char *item)
 	WALK_LIST (r, new_config->keys) {
 		if (knot_dname_cmp(r->k.name, sample) == 0) {
 			*key = &r->k;
-			knot_dname_free(&sample);
+			knot_dname_free(&sample, NULL);
 			return 0;
 		}
 	}
 
 	cf_error(scanner, "key '%s' is not defined", item);
-	knot_dname_free(&sample);
+	knot_dname_free(&sample, NULL);
 	return 1;
 }
 
@@ -382,7 +382,7 @@ static void conf_zone_start(void *scanner, char *name) {
 	                   knot_dname_size(dn)) != NULL) {
 		cf_error(scanner, "zone '%s' is already present, refusing to "
 		         "duplicate", this_zone->name);
-		knot_dname_free(&dn);
+		knot_dname_free(&dn, NULL);
 		free(this_zone->name);
 		this_zone->name = NULL;
 		/* Must not free, some versions of flex might continue after
@@ -394,7 +394,7 @@ static void conf_zone_start(void *scanner, char *name) {
 
 	*hattrie_get(new_config->zones, (const char *)dn,
 	             knot_dname_size(dn)) = this_zone;
-	knot_dname_free(&dn);
+	knot_dname_free(&dn, NULL);
 	}
 }
 
@@ -678,7 +678,7 @@ keys:
              k->k.algorithm = $3.alg;
              if (knot_binary_from_base64($4.t, &(k->k.secret)) != 0) {
                  cf_error(scanner, "invalid key secret '%s'", $4.t);
-                 knot_dname_free(&dname);
+                 knot_dname_free(&dname, NULL);
                  free(k);
              } else {
                  add_tail(&new_config->keys, &k->n);
diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c
index b6a5fe5b5ac5cd4c7388e21607a9ed904ba8f348..185704b784c8225233aad420971392f7978a78ae 100644
--- a/src/knot/ctl/knotc_main.c
+++ b/src/knot/ctl/knotc_main.c
@@ -119,7 +119,7 @@ void help(void)
 static int cmd_remote_print_reply(const knot_rrset_t *rr)
 {
 	/* Process first RRSet in data section. */
-	if (knot_rrset_type(rr) != KNOT_RRTYPE_TXT) {
+	if (rr->type != KNOT_RRTYPE_TXT) {
 		return KNOT_EMALF;
 	}
 
@@ -162,7 +162,7 @@ static int cmd_remote_reply(int c)
 	switch(ret) {
 	case KNOT_RCODE_NOERROR:
 		if (authority->count > 0) {
-			ret = cmd_remote_print_reply(authority->rr[0]);
+			ret = cmd_remote_print_reply(&authority->rr[0]);
 		}
 		break;
 	case KNOT_RCODE_REFUSED:
@@ -199,24 +199,29 @@ static int cmd_remote(const char *cmd, uint16_t rrt, int argc, char *argv[])
 
 	/* Build query data. */
 	knot_pkt_begin(pkt, KNOT_AUTHORITY);
-	knot_rrset_t *rr = NULL;
 	if (argc > 0) {
-		rr = remote_build_rr("data.", rrt);
+		knot_rrset_t rr;
+		int res = remote_build_rr(&rr, "data.", rrt);
+		if (res != KNOT_EOK) {
+			log_server_error("Couldn't create the query.\n");
+			knot_pkt_free(&pkt);
+			return 1;
+		}
 		for (int i = 0; i < argc; ++i) {
 			switch(rrt) {
 			case KNOT_RRTYPE_NS:
-				remote_create_ns(rr, argv[i]);
+				remote_create_ns(&rr, argv[i]);
 				break;
 			case KNOT_RRTYPE_TXT:
 			default:
-				remote_create_txt(rr, argv[i], strlen(argv[i]));
+				remote_create_txt(&rr, argv[i], strlen(argv[i]));
 				break;
 			}
 		}
-		int res = knot_pkt_put(pkt, 0, rr, KNOT_PF_FREE);
+		res = knot_pkt_put(pkt, 0, &rr, KNOT_PF_FREE);
 		if (res != KNOT_EOK) {
 			log_server_error("Couldn't create the query.\n");
-			knot_rrset_free(&rr, NULL);
+			knot_rrset_clear(&rr, NULL);
 			knot_pkt_free(&pkt);
 			return 1;
 		}
diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index 2c6d028bdeff3da1daca98c38d364a3720d6ed9d..68e5ea20cf460f7eb6b5648b5f5d12e0e2a751f3 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.c
@@ -39,7 +39,7 @@
 
 /*! \brief Remote command structure. */
 typedef struct remote_cmdargs_t {
-	const knot_rrset_t **arg;
+	const knot_rrset_t *arg;
 	unsigned argc;
 	knot_rcode_t rc;
 	char resp[CMDARGS_BUFLEN];
@@ -93,14 +93,14 @@ static int remote_rdata_apply(server_t *s, remote_cmdargs_t* a, remote_zonef_t *
 
 	for (unsigned i = 0; i < a->argc; ++i) {
 		/* Process all zones in data section. */
-		const knot_rrset_t *rr = a->arg[i];
-		if (knot_rrset_type(rr) != KNOT_RRTYPE_NS) {
+		const knot_rrset_t *rr = &a->arg[i];
+		if (rr->type != KNOT_RRTYPE_NS) {
 			continue;
 		}
 
 		uint16_t rr_count = knot_rrset_rr_count(rr);
 		for (uint16_t i = 0; i < rr_count; i++) {
-			const knot_dname_t *dn = knot_rdata_ns_name(rr, i);
+			const knot_dname_t *dn = knot_rrs_ns_name(&rr->rrs, i);
 			rcu_read_lock();
 			zone = knot_zonedb_find(s->zone_db, dn);
 			if (cb(s, zone) != KNOT_EOK) {
@@ -235,13 +235,13 @@ static int remote_c_zonestatus(server_t *s, remote_cmdargs_t* a)
 		const zone_t *zone = knot_zonedb_iter_val(&it);
 
 		/* Fetch latest serial. */
-		const knot_rrset_t *soa_rrs = 0;
+		const knot_rrs_t *soa_rrs = NULL;
 		uint32_t serial = 0;
 		if (zone->contents) {
-			soa_rrs = knot_node_rrset(zone->contents->apex,
-			                          KNOT_RRTYPE_SOA);
+			soa_rrs = knot_node_rrs(zone->contents->apex,
+			                        KNOT_RRTYPE_SOA);
 			assert(soa_rrs != NULL);
-			serial = knot_rdata_soa_serial(soa_rrs);
+			serial = knot_rrs_soa_serial(soa_rrs);
 		}
 
 		/* Evalute zone type. */
@@ -500,18 +500,18 @@ static int remote_send_chunk(int c, knot_pkt_t *query, const char* d, uint16_t l
 	assert(ret == KNOT_EOK);
 
 	/* Create TXT RR with result. */
-	knot_rrset_t *rr = remote_build_rr("result.", KNOT_RRTYPE_TXT);
-	if (rr == NULL) {
-		ret = KNOT_ENOMEM;
+	knot_rrset_t rr;
+	ret = remote_build_rr(&rr, "result.", KNOT_RRTYPE_TXT);
+	if (ret != KNOT_EOK) {
 		goto failed;
 	}
 
-	ret = remote_create_txt(rr, d, len);
+	ret = remote_create_txt(&rr, d, len);
 	assert(ret == KNOT_EOK);
 
-	ret = knot_pkt_put(resp, 0, rr, KNOT_PF_FREE);
+	ret = knot_pkt_put(resp, 0, &rr, KNOT_PF_FREE);
 	if (ret != KNOT_EOK) {
-		knot_rrset_free(&rr, NULL);
+		knot_rrset_clear(&rr, NULL);
 		goto failed;
 	}
 
@@ -531,14 +531,14 @@ static void log_command(const char *cmd, const remote_cmdargs_t* args)
 	size_t rest = CMDARGS_BUFLEN_LOG;
 
 	for (unsigned i = 0; i < args->argc; i++) {
-		const knot_rrset_t *rr = args->arg[i];
-		if (knot_rrset_type(rr) != KNOT_RRTYPE_NS) {
+		const knot_rrset_t *rr = &args->arg[i];
+		if (rr->type != KNOT_RRTYPE_NS) {
 			continue;
 		}
 
 		uint16_t rr_count = knot_rrset_rr_count(rr);
 		for (uint16_t j = 0; j < rr_count; j++) {
-			const knot_dname_t *dn = knot_rdata_ns_name(rr, j);
+			const knot_dname_t *dn = knot_rrs_ns_name(&rr->rrs, j);
 			char *name = knot_dname_to_str(dn);
 
 			int ret = snprintf(params, rest, " %s", name);
@@ -572,10 +572,10 @@ int remote_answer(int sock, server_t *s, knot_pkt_t *pkt)
 	knot_dname_t *realm = knot_dname_from_str(KNOT_CTL_REALM);
 	if (!knot_dname_is_sub(qname, realm) != 0) {
 		dbg_server("remote: qname != *%s\n", KNOT_CTL_REALM_EXT);
-		knot_dname_free(&realm);
+		knot_dname_free(&realm, NULL);
 		return KNOT_EMALF;
 	}
-	knot_dname_free(&realm);
+	knot_dname_free(&realm, NULL);
 
 	/* Command:
 	 * QNAME: leftmost label of QNAME
@@ -742,11 +742,11 @@ knot_pkt_t* remote_query(const char *query, const knot_tsig_key_t *key)
 	/* Cannot return != KNOT_EOK, but still. */
 	if (knot_pkt_put_question(pkt, dname, KNOT_CLASS_CH, KNOT_RRTYPE_ANY) != KNOT_EOK) {
 		knot_pkt_free(&pkt);
-		knot_dname_free(&dname);
+		knot_dname_free(&dname, NULL);
 		return NULL;
 	}
 
-	knot_dname_free(&dname);
+	knot_dname_free(&dname, NULL);
 	return pkt;
 }
 
@@ -770,24 +770,22 @@ int remote_query_sign(uint8_t *wire, size_t *size, size_t maxlen,
 	return ret;
 }
 
-knot_rrset_t* remote_build_rr(const char *k, uint16_t t)
+int remote_build_rr(knot_rrset_t *rr, const char *k, uint16_t t)
 {
 	if (!k) {
-		return NULL;
+		return KNOT_EINVAL;
 	}
 
 	/* Assert K is FQDN. */
 	knot_dname_t *key = knot_dname_from_str(k);
 	if (key == NULL) {
-		return NULL;
+		return KNOT_ENOMEM;
 	}
 
-	/* Create RRSet. */
-	knot_rrset_t *rr = knot_rrset_new(key, t, KNOT_CLASS_CH, NULL);
-	if (rr == NULL)
-		knot_dname_free(&key);
+	/* Init RRSet. */
+	knot_rrset_init(rr, key, t, KNOT_CLASS_CH);
 
-	return rr;
+	return KNOT_EOK;
 }
 
 int remote_create_txt(knot_rrset_t *rr, const char *v, size_t v_len)
@@ -799,24 +797,26 @@ int remote_create_txt(knot_rrset_t *rr, const char *v, size_t v_len)
 	/* Number of chunks. */
 	const size_t K = 255;
 	unsigned chunks = v_len / K + 1;
-	uint8_t *raw = knot_rrset_create_rr(rr, v_len + chunks, 0, NULL);
+	uint8_t raw[v_len + chunks];
+	memset(raw, 0, v_len + chunks);
 
 	/* Write TXT item. */
 	unsigned p = 0;
+	size_t off = 0;
 	if (v_len > K) {
 		for (; p + K < v_len; p += K) {
-			*(raw++) = (uint8_t)K;
-			memcpy(raw, v+p, K);
-			raw += K;
+			raw[off++] = (uint8_t)K;
+			memcpy(raw + off, v + p, K);
+			off += K;
 		}
 	}
 	unsigned r = v_len - p;
 	if (r > 0) {
-		*(raw++) = (uint8_t)r;
-		memcpy(raw, v+p, r);
+		raw[off++] = (uint8_t)r;
+		memcpy(raw + off, v + p, r);
 	}
 
-	return KNOT_EOK;
+	return knot_rrset_add_rr(rr, raw, v_len + chunks, 0, NULL);
 }
 
 int remote_create_ns(knot_rrset_t *rr, const char *d)
@@ -834,7 +834,7 @@ int remote_create_ns(knot_rrset_t *rr, const char *d)
 	/* Build RDATA. */
 	int dn_size = knot_dname_size(dn);
 	int result = knot_rrset_add_rr(rr, dn, dn_size, 0, NULL);
-	knot_dname_free(&dn);
+	knot_dname_free(&dn, NULL);
 
 	return result;
 }
diff --git a/src/knot/ctl/remote.h b/src/knot/ctl/remote.h
index 6c8389aa5aa1a803f9434094769ce7bcfe645f6b..bf198e19d505d25d1b7a041d8ce181a11cd0ba32 100644
--- a/src/knot/ctl/remote.h
+++ b/src/knot/ctl/remote.h
@@ -161,7 +161,7 @@ int remote_query_sign(uint8_t *wire, size_t *size, size_t maxlen,
  *
  * \return created RR set or NULL.
  */
-knot_rrset_t* remote_build_rr(const char *k, uint16_t t);
+int remote_build_rr(knot_rrset_t *rr, const char *k, uint16_t t);
 
 /*!
  * \brief Create a TXT rdata.
@@ -171,7 +171,6 @@ knot_rrset_t* remote_build_rr(const char *k, uint16_t t);
  */
 int remote_create_txt(knot_rrset_t *rr, const char *v, size_t v_len);
 
-
 /*!
  * \brief Create a CNAME rdata.
  * \param d Domain name as a string.
diff --git a/src/knot/dnssec/nsec-chain.c b/src/knot/dnssec/nsec-chain.c
index d838e4de3068bce6de2d218f39d6bb62b443d98f..f2547054b0f462b06ef9856a6cfdd454f25c99a3 100644
--- a/src/knot/dnssec/nsec-chain.c
+++ b/src/knot/dnssec/nsec-chain.c
@@ -21,6 +21,7 @@
 #include "common/debug.h"
 #include "knot/dnssec/nsec-chain.h"
 #include "knot/dnssec/zone-sign.h"
+#include "libknot/dnssec/rrset-sign.h"
 #include "knot/dnssec/zone-nsec.h"
 
 /* - NSEC chain construction ------------------------------------------------ */
@@ -40,12 +41,8 @@ static knot_rrset_t *create_nsec_rrset(const knot_node_t *from,
 {
 	assert(from);
 	assert(to);
-
-	// Create new RRSet
-	knot_dname_t *owner_cpy = knot_dname_copy(from->owner);
-	knot_rrset_t *rrset = knot_rrset_new(owner_cpy,
-	                                     KNOT_RRTYPE_NSEC, KNOT_CLASS_IN,
-	                                     NULL);
+	knot_rrset_t *rrset = knot_rrset_new(from->owner, KNOT_RRTYPE_NSEC,
+					     KNOT_CLASS_IN, NULL);
 	if (!rrset) {
 		return NULL;
 	}
@@ -55,7 +52,7 @@ static knot_rrset_t *create_nsec_rrset(const knot_node_t *from,
 	bitmap_add_node_rrsets(&rr_types, from);
 	bitmap_add_type(&rr_types, KNOT_RRTYPE_NSEC);
 	bitmap_add_type(&rr_types, KNOT_RRTYPE_RRSIG);
-	if (knot_node_rrset(from, KNOT_RRTYPE_SOA)) {
+	if (knot_node_rrtype_exists(from, KNOT_RRTYPE_SOA)) {
 		bitmap_add_type(&rr_types, KNOT_RRTYPE_DNSKEY);
 	}
 
@@ -63,16 +60,18 @@ static knot_rrset_t *create_nsec_rrset(const knot_node_t *from,
 	assert(to->owner);
 	size_t next_owner_size = knot_dname_size(to->owner);
 	size_t rdata_size = next_owner_size + bitmap_size(&rr_types);
-	uint8_t *rdata = knot_rrset_create_rr(rrset, rdata_size, ttl, NULL);
-	if (!rdata) {
-		knot_rrset_free(&rrset, NULL);
-		return NULL;
-	}
+	uint8_t rdata[rdata_size];
 
 	// Fill RDATA
 	memcpy(rdata, to->owner, next_owner_size);
 	bitmap_write(&rr_types, rdata + next_owner_size);
 
+	int ret = knot_rrset_add_rr(rrset, rdata, rdata_size, ttl, NULL);
+	if (ret != KNOT_EOK) {
+		knot_rrset_free(&rrset, NULL);
+		return NULL;
+	}
+
 	return rrset;
 }
 
@@ -99,14 +98,13 @@ static int connect_nsec_nodes(knot_node_t *a, knot_node_t *b,
 		return NSEC_NODE_SKIP;
 	}
 
-	knot_rrset_t *old_next_nsec = knot_node_get_rrset(b, KNOT_RRTYPE_NSEC);
 	int ret = 0;
 
 	/*!
 	 * If the node has no other RRSets than NSEC (and possibly RRSIGs),
 	 * just remove the NSEC and its RRSIG, they are redundant
 	 */
-	if (old_next_nsec != NULL
+	if (knot_node_rrtype_exists(b, KNOT_RRTYPE_NSEC)
 	    && knot_nsec_empty_nsec_and_rrsigs_in_node(b)) {
 		ret = knot_nsec_changeset_remove(b, data->changeset);
 		if (ret != KNOT_EOK) {
@@ -123,9 +121,9 @@ static int connect_nsec_nodes(knot_node_t *a, knot_node_t *b,
 		return KNOT_ENOMEM;
 	}
 
-	knot_rrset_t *old_nsec = knot_node_get_rrset(a, KNOT_RRTYPE_NSEC);
-	if (old_nsec != NULL) {
-		if (knot_rrset_equal(new_nsec, old_nsec,
+	knot_rrset_t old_nsec = knot_node_rrset(a, KNOT_RRTYPE_NSEC);
+	if (!knot_rrset_empty(&old_nsec)) {
+		if (knot_rrset_equal(new_nsec, &old_nsec,
 		                     KNOT_RRSET_COMPARE_WHOLE)) {
 			// current NSEC is valid, do nothing
 			dbg_dnssec_detail("NSECs equal.\n");
@@ -216,45 +214,39 @@ int knot_nsec_changeset_remove(const knot_node_t *n,
 
 	int result = KNOT_EOK;
 
-	const knot_rrset_t *nsec = knot_node_rrset(n, KNOT_RRTYPE_NSEC);
+	knot_rrset_t *nsec = knot_node_create_rrset(n, KNOT_RRTYPE_NSEC);
 	if (nsec == NULL) {
-		nsec = knot_node_rrset(n, KNOT_RRTYPE_NSEC3);
+		nsec = knot_node_create_rrset(n, KNOT_RRTYPE_NSEC3);
 	}
-	const knot_rrset_t *rrsigs = knot_node_rrset(n, KNOT_RRTYPE_RRSIG);
-
-	// extract copy of NSEC
-	knot_rrset_t *old_nsec = NULL;
 	if (nsec) {
-		result = knot_rrset_copy(nsec, &old_nsec, NULL);
-		if (result != KNOT_EOK) {
-			return result;
-		}
-
 		// update changeset
-
-		result = knot_changeset_add_rrset(changeset, old_nsec,
+		result = knot_changeset_add_rrset(changeset, nsec,
 		                                  KNOT_CHANGESET_REMOVE);
 		if (result != KNOT_EOK) {
-			knot_rrset_free(&old_nsec, NULL);
+			knot_rrset_free(&nsec, NULL);
 			return result;
 		}
 	}
 
-	if (rrsigs) {
-		// Sythesize RRSets' RRSIG
-		knot_rrset_t *synth_rrsigs = NULL;
-		result = knot_rrset_synth_rrsig(rrsigs->owner, KNOT_RRTYPE_NSEC,
-		                                rrsigs, &synth_rrsigs, NULL);
-
+	knot_rrset_t rrsigs = knot_node_rrset(n, KNOT_RRTYPE_RRSIG);
+	if (!knot_rrset_empty(&rrsigs)) {
+		knot_rrset_t *synth_rrsigs = knot_rrset_new(n->owner,
+							    KNOT_RRTYPE_RRSIG,
+							    KNOT_CLASS_IN,
+							    NULL);
+		if (synth_rrsigs == NULL) {
+			return KNOT_ENOMEM;
+		}
+		result = knot_synth_rrsig(KNOT_RRTYPE_NSEC, &rrsigs.rrs,
+		                          &synth_rrsigs->rrs, NULL);
 		if (result == KNOT_ENOENT) {
 			// Try removing NSEC3 RRSIGs
-			result = knot_rrset_synth_rrsig(rrsigs->owner,
-			                                KNOT_RRTYPE_NSEC3,
-			                                rrsigs, &synth_rrsigs,
-			                                NULL);
+			result = knot_synth_rrsig(KNOT_RRTYPE_NSEC3, &rrsigs.rrs,
+			                          &synth_rrsigs->rrs, NULL);
 		}
 
 		if (result != KNOT_EOK) {
+			knot_rrset_free(&synth_rrsigs, NULL);
 			if (result != KNOT_ENOENT) {
 				return result;
 			}
@@ -280,10 +272,10 @@ int knot_nsec_changeset_remove(const knot_node_t *n,
 bool knot_nsec_empty_nsec_and_rrsigs_in_node(const knot_node_t *n)
 {
 	assert(n);
-	const knot_rrset_t **rrsets = knot_node_rrsets_no_copy(n);
 	for (int i = 0; i < n->rrset_count; ++i) {
-		if (rrsets[i]->type != KNOT_RRTYPE_NSEC &&
-		    rrsets[i]->type != KNOT_RRTYPE_RRSIG) {
+		knot_rrset_t rrset = knot_node_rrset_at(n, i);
+		if (rrset.type != KNOT_RRTYPE_NSEC &&
+		    rrset.type != KNOT_RRTYPE_RRSIG) {
 			return false;
 		}
 	}
@@ -308,4 +300,3 @@ int knot_nsec_create_chain(const knot_zone_contents_t *zone, uint32_t ttl,
 	return knot_nsec_chain_iterate_create(zone->nodes,
 	                                      connect_nsec_nodes, &data);
 }
-
diff --git a/src/knot/dnssec/nsec-chain.h b/src/knot/dnssec/nsec-chain.h
index a21c683713d7a85d1e79fe8eceb134783aad82c7..299f793506712c9b7b32006c5da4818992ca50c9 100644
--- a/src/knot/dnssec/nsec-chain.h
+++ b/src/knot/dnssec/nsec-chain.h
@@ -56,19 +56,17 @@ enum {
 typedef int (*chain_iterate_create_cb)(knot_node_t *, knot_node_t *,
                                        nsec_chain_iterate_data_t *);
 
-
 /*!
  * \brief Add all RR types from a node into the bitmap.
  */
 inline static void bitmap_add_node_rrsets(bitmap_t *bitmap,
                                           const knot_node_t *node)
 {
-	const knot_rrset_t **node_rrsets = knot_node_rrsets_no_copy(node);
 	for (int i = 0; i < node->rrset_count; i++) {
-		const knot_rrset_t *rr = node_rrsets[i];
-		if (rr->type != KNOT_RRTYPE_NSEC &&
-		    rr->type != KNOT_RRTYPE_RRSIG) {
-			bitmap_add_type(bitmap, rr->type);
+		knot_rrset_t rr = knot_node_rrset_at(node, i);
+		if (rr.type != KNOT_RRTYPE_NSEC &&
+		    rr.type != KNOT_RRTYPE_RRSIG) {
+			bitmap_add_type(bitmap, rr.type);
 		}
 	}
 }
diff --git a/src/knot/dnssec/nsec3-chain.c b/src/knot/dnssec/nsec3-chain.c
index 45828aa4e75c58747b02b075d5c02fedf5e84931..23b90d70c806aac096dfdfa060c23cad37aed017 100644
--- a/src/knot/dnssec/nsec3-chain.c
+++ b/src/knot/dnssec/nsec3-chain.c
@@ -30,11 +30,12 @@
 
 /* - Forward declarations --------------------------------------------------- */
 
-static knot_rrset_t *create_nsec3_rrset(knot_dname_t *,
-                                        const knot_nsec3_params_t *,
-                                        const bitmap_t *,
-                                        const uint8_t *,
-                                        uint32_t);
+static int create_nsec3_rrset(knot_rrset_t *rrset,
+                              knot_dname_t *dname,
+                              const knot_nsec3_params_t *,
+                              const bitmap_t *,
+                              const uint8_t *,
+                              uint32_t);
 
 /* - Helper functions ------------------------------------------------------- */
 
@@ -51,12 +52,12 @@ inline static bool valid_nsec3_node(const knot_node_t *node)
 		return false;
 	}
 
-	const knot_rrset_t *nsec3 = knot_node_rrset(node, KNOT_RRTYPE_NSEC3);
+	const knot_rrs_t *nsec3 = knot_node_rrs(node, KNOT_RRTYPE_NSEC3);
 	if (nsec3 == NULL) {
 		return false;
 	}
 
-	if (knot_rrset_rr_count(nsec3) != 1) {
+	if (nsec3->rr_count != 1) {
 		return false;
 	}
 
@@ -72,10 +73,9 @@ static bool are_nsec3_nodes_equal(const knot_node_t *a, const knot_node_t *b)
 		return false;
 	}
 
-	const knot_rrset_t *a_rrset = knot_node_rrset(a, KNOT_RRTYPE_NSEC3);
-	const knot_rrset_t *b_rrset = knot_node_rrset(b, KNOT_RRTYPE_NSEC3);
-
-	return knot_rrset_equal(a_rrset, b_rrset, KNOT_RRSET_COMPARE_WHOLE);
+	knot_rrset_t a_rrset = knot_node_rrset(a, KNOT_RRTYPE_NSEC3);
+	knot_rrset_t b_rrset = knot_node_rrset(b, KNOT_RRTYPE_NSEC3);
+	return knot_rrset_equal(&a_rrset, &b_rrset, KNOT_RRSET_COMPARE_WHOLE);
 }
 
 /*!
@@ -88,14 +88,14 @@ static bool are_nsec3_nodes_equal(const knot_node_t *a, const knot_node_t *b)
  */
 static bool node_should_be_signed_nsec3(const knot_node_t *n)
 {
-	knot_rrset_t **node_rrsets = knot_node_get_rrsets_no_copy(n);
 	for (int i = 0; i < n->rrset_count; i++) {
-		if (node_rrsets[i]->type == KNOT_RRTYPE_NSEC ||
-		    node_rrsets[i]->type == KNOT_RRTYPE_RRSIG) {
+		knot_rrset_t rrset = knot_node_rrset_at(n, i);
+		if (rrset.type == KNOT_RRTYPE_NSEC ||
+		    rrset.type == KNOT_RRTYPE_RRSIG) {
 			continue;
 		}
 		bool should_sign = false;
-		int ret = knot_zone_sign_rr_should_be_signed(n, node_rrsets[i],
+		int ret = knot_zone_sign_rr_should_be_signed(n, &rrset,
 		                                             &should_sign);
 		assert(ret == KNOT_EOK); // No tree inside the function, no fail
 		if (should_sign) {
@@ -117,11 +117,11 @@ static int shallow_copy_signature(const knot_node_t *from, knot_node_t *to)
 	assert(valid_nsec3_node(from));
 	assert(valid_nsec3_node(to));
 
-	knot_rrset_t *from_sig = knot_node_get_rrset(from, KNOT_RRTYPE_RRSIG);
-	if (from_sig == NULL) {
+	knot_rrset_t from_sig = knot_node_rrset(from, KNOT_RRTYPE_RRSIG);
+	if (knot_rrset_empty(&from_sig)) {
 		return KNOT_EOK;
 	}
-	return knot_node_add_rrset(to, from_sig, NULL);
+	return knot_node_add_rrset(to, &from_sig, NULL);
 }
 
 /*!
@@ -165,9 +165,6 @@ static int copy_signatures(const knot_zone_tree_t *from, knot_zone_tree_t *to)
 /*!
  * \brief Custom NSEC3 tree free function.
  *
- * - Leaves RRSIGs, as these are only referenced (shallow copied).
- * - Deep frees NSEC3 RRs, as these nodes were created.
- *
  */
 static void free_nsec3_tree(knot_zone_tree_t *nodes)
 {
@@ -178,9 +175,10 @@ static void free_nsec3_tree(knot_zone_tree_t *nodes)
 	for (/* NOP */; !hattrie_iter_finished(it); hattrie_iter_next(it)) {
 		knot_node_t *node = (knot_node_t *)*hattrie_iter_val(it);
 		// newly allocated NSEC3 nodes
-		knot_rrset_t *nsec3 = knot_node_get_rrset(node,
-		                                          KNOT_RRTYPE_NSEC3);
-		knot_rrset_free(&nsec3, NULL);
+		knot_rrs_t *nsec3 = knot_node_get_rrs(node, KNOT_RRTYPE_NSEC3);
+		knot_rrs_t *rrsig = knot_node_get_rrs(node, KNOT_RRTYPE_RRSIG);
+		knot_rrs_clear(nsec3, NULL);
+		knot_rrs_clear(rrsig, NULL);
 		knot_node_free(&node);
 	}
 
@@ -250,32 +248,25 @@ static void nsec3_fill_rdata(uint8_t *rdata, const knot_nsec3_params_t *params,
  *
  * \return Pointer to created RRSet on success, NULL on errors.
  */
-static knot_rrset_t *create_nsec3_rrset(knot_dname_t *owner,
-                                        const knot_nsec3_params_t *params,
-                                        const bitmap_t *rr_types,
-                                        const uint8_t *next_hashed,
-                                        uint32_t ttl)
+static int create_nsec3_rrset(knot_rrset_t *rrset,
+                              knot_dname_t *owner,
+                              const knot_nsec3_params_t *params,
+                              const bitmap_t *rr_types,
+                              const uint8_t *next_hashed,
+                              uint32_t ttl)
 {
+	assert(rrset);
 	assert(owner);
 	assert(params);
 	assert(rr_types);
 
-	knot_rrset_t *rrset;
-	rrset = knot_rrset_new(owner, KNOT_RRTYPE_NSEC3, KNOT_CLASS_IN, NULL);
-	if (!rrset) {
-		return NULL;
-	}
+	knot_rrset_init(rrset, owner, KNOT_RRTYPE_NSEC3, KNOT_CLASS_IN);
 
 	size_t rdata_size = nsec3_rdata_size(params, rr_types);
-	uint8_t *rdata = knot_rrset_create_rr(rrset, rdata_size, ttl, NULL);
-	if (!rdata) {
-		knot_rrset_free(&rrset, NULL);
-		return NULL;
-	}
-
+	uint8_t rdata[rdata_size];
 	nsec3_fill_rdata(rdata, params, rr_types, next_hashed, ttl);
 
-	return rrset;
+	return knot_rrset_add_rr(rrset, rdata, rdata_size, ttl, NULL);
 }
 
 /*!
@@ -298,16 +289,17 @@ static knot_node_t *create_nsec3_node(knot_dname_t *owner,
 		return NULL;
 	}
 
-	knot_rrset_t *nsec3_rrset;
-	nsec3_rrset = create_nsec3_rrset(owner, nsec3_params,
-	                                           rr_types, NULL, ttl);
-	if (!nsec3_rrset) {
+	knot_rrset_t nsec3_rrset;
+	int ret = create_nsec3_rrset(&nsec3_rrset, owner, nsec3_params,
+	                             rr_types, NULL, ttl);
+	if (ret != KNOT_EOK) {
 		knot_node_free(&new_node);
 		return NULL;
 	}
 
-	if (knot_node_add_rrset_no_merge(new_node, nsec3_rrset) != KNOT_EOK) {
-		knot_rrset_free(&nsec3_rrset, NULL);
+	ret = knot_node_add_rrset(new_node, &nsec3_rrset, NULL);
+	knot_rrset_clear(&nsec3_rrset, NULL);
+	if (ret != KNOT_EOK) {
 		knot_node_free(&new_node);
 		return NULL;
 	}
@@ -375,16 +367,16 @@ static int connect_nsec3_nodes(knot_node_t *a, knot_node_t *b,
 
 	assert(a->rrset_count == 1);
 
-	knot_rrset_t *a_rrset = knot_node_get_rrset(a, KNOT_RRTYPE_NSEC3);
-	assert(a_rrset);
-	uint8_t algorithm = knot_rdata_nsec3_algorithm(a_rrset, 0);
+	knot_rrs_t *a_rrs = knot_node_get_rrs(a, KNOT_RRTYPE_NSEC3);
+	assert(a_rrs);
+	uint8_t algorithm = knot_rrs_nsec3_algorithm(a_rrs, 0);
 	if (algorithm == 0) {
 		return KNOT_EINVAL;
 	}
 
 	uint8_t *raw_hash = NULL;
 	uint8_t raw_length = 0;
-	knot_rdata_nsec3_next_hashed(a_rrset, 0, &raw_hash, &raw_length);
+	knot_rrs_nsec3_next_hashed(a_rrs, 0, &raw_hash, &raw_length);
 	if (raw_hash == NULL) {
 		return KNOT_EINVAL;
 	}
@@ -447,7 +439,7 @@ static int create_nsec3_nodes(const knot_zone_contents_t *zone, uint32_t ttl,
 		if (result != KNOT_EOK) {
 			break;
 		}
-		if (knot_node_rrset(node, KNOT_RRTYPE_NSEC)) {
+		if (knot_node_rrtype_exists(node, KNOT_RRTYPE_NSEC)) {
 			knot_node_set_removed_nsec(node);
 		}
 		if (knot_node_is_non_auth(node) || knot_node_is_empty(node)) {
@@ -637,4 +629,3 @@ int knot_nsec3_create_chain(const knot_zone_contents_t *zone, uint32_t ttl,
 
 	return result;
 }
-
diff --git a/src/knot/dnssec/zone-events.c b/src/knot/dnssec/zone-events.c
index e84f95a13c61764214d4cd1d2a2c74f872fc61b3..a07117ecec5fdfeb1c189bb0c352cafc490ac158 100644
--- a/src/knot/dnssec/zone-events.c
+++ b/src/knot/dnssec/zone-events.c
@@ -128,12 +128,10 @@ static int zone_sign(knot_zone_contents_t *zone, conf_zone_t *zone_config,
 	}
 
 	// update SOA if there were any changes
-	const knot_rrset_t *soa = knot_node_rrset(zone->apex,
-	                                          KNOT_RRTYPE_SOA);
-	const knot_rrset_t *rrsigs = knot_node_rrset(zone->apex,
-	                                             KNOT_RRTYPE_RRSIG);
-	assert(soa);
-	result = knot_zone_sign_update_soa(soa, rrsigs, &zone_keys, &policy,
+	knot_rrset_t soa = knot_node_rrset(zone->apex, KNOT_RRTYPE_SOA);
+	knot_rrset_t rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
+	assert(!knot_rrset_empty(&soa));
+	result = knot_zone_sign_update_soa(&soa, &rrsigs, &zone_keys, &policy,
 	                                   new_serial, out_ch);
 	if (result != KNOT_EOK) {
 		log_zone_error("%s Cannot update SOA record (%s). Not signing"
@@ -240,12 +238,10 @@ int knot_dnssec_sign_changeset(const knot_zone_contents_t *zone,
 	}
 
 	// Update SOA RRSIGs
-	ret = knot_zone_sign_update_soa(knot_node_rrset(zone->apex,
-	                                                KNOT_RRTYPE_SOA),
-	                                knot_node_rrset(zone->apex,
-	                                                KNOT_RRTYPE_RRSIG),
-	                                &zone_keys, &policy, new_serial,
-	                                out_ch);
+	knot_rrset_t soa = knot_node_rrset(zone->apex, KNOT_RRTYPE_SOA);
+	knot_rrset_t rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
+	ret = knot_zone_sign_update_soa(&soa, &rrsigs, &zone_keys, &policy,
+	                                new_serial, out_ch);
 	if (ret != KNOT_EOK) {
 		log_zone_error("%s Failed to sign SOA RR (%s)\n", msgpref,
 		               knot_strerror(ret));
diff --git a/src/knot/dnssec/zone-nsec.c b/src/knot/dnssec/zone-nsec.c
index b07fdfd2cc772cd913453247eb3f49ca1a5a4ba4..ade08a681ea6ab621417c9c05e6ba36480caa8cd 100644
--- a/src/knot/dnssec/zone-nsec.c
+++ b/src/knot/dnssec/zone-nsec.c
@@ -93,12 +93,12 @@ static bool get_zone_soa_min_ttl(const knot_zone_contents_t *zone,
 	assert(ttl);
 
 	knot_node_t *apex = zone->apex;
-	knot_rrset_t *soa = knot_node_get_rrset(apex, KNOT_RRTYPE_SOA);
+	const knot_rrs_t *soa = knot_node_rrs(apex, KNOT_RRTYPE_SOA);
 	if (!soa) {
 		return false;
 	}
 
-	uint32_t result =  knot_rdata_soa_minimum(soa);
+	uint32_t result =  knot_rrs_soa_minimum(soa);
 	if (result == 0) {
 		return false;
 	}
@@ -127,9 +127,9 @@ static int mark_nsec3(knot_rrset_t *rrset, void *data)
 	knot_node_t *node = NULL;
 	int ret;
 
-	if (knot_rrset_type(rrset) == KNOT_RRTYPE_NSEC3) {
+	if (rrset->type == KNOT_RRTYPE_NSEC3) {
 		// Find the name in the NSEC3 tree and mark the node
-		ret = knot_zone_tree_get(nsec3s, knot_rrset_owner(rrset),
+		ret = knot_zone_tree_get(nsec3s, rrset->owner,
 		                         &node);
 		if (ret != KNOT_EOK) {
 			return ret;
@@ -284,4 +284,3 @@ int knot_zone_create_nsec_chain(const knot_zone_contents_t *zone,
 	// Sign newly created records right away
 	return knot_zone_sign_nsecs_in_changeset(zone_keys, policy, changeset);
 }
-
diff --git a/src/knot/dnssec/zone-sign.c b/src/knot/dnssec/zone-sign.c
index ccfac99789b8637767dbc0c6a91ab442dc4ff1d4..177c4594c4f1bc5d5d08fba54aa8f4e45626a97e 100644
--- a/src/knot/dnssec/zone-sign.c
+++ b/src/knot/dnssec/zone-sign.c
@@ -44,12 +44,10 @@
  */
 static knot_rrset_t *create_empty_rrsigs_for(const knot_rrset_t *covered)
 {
-	assert(covered);
-
-	knot_dname_t *owner_copy = knot_dname_copy(covered->owner);
+	assert(!knot_rrset_empty(covered));
 
-	return knot_rrset_new(owner_copy, KNOT_RRTYPE_RRSIG, covered->rclass,
-	                      NULL);
+	return knot_rrset_new(covered->owner, KNOT_RRTYPE_RRSIG,
+			      covered->rclass, NULL);
 }
 
 /*- private API - signing of in-zone nodes -----------------------------------*/
@@ -73,14 +71,14 @@ static bool valid_signature_exists(const knot_rrset_t *covered,
 {
 	assert(key);
 
-	if (!rrsigs) {
+	if (knot_rrset_empty(rrsigs)) {
 		return false;
 	}
 
 	uint16_t rrsigs_rdata_count = knot_rrset_rr_count(rrsigs);
 	for (uint16_t i = 0; i < rrsigs_rdata_count; i++) {
-		uint16_t keytag = knot_rdata_rrsig_key_tag(rrsigs, i);
-		uint16_t type_covered = knot_rdata_rrsig_type_covered(rrsigs, i);
+		uint16_t keytag = knot_rrs_rrsig_key_tag(&rrsigs->rrs, i);
+		uint16_t type_covered = knot_rrs_rrsig_type_covered(&rrsigs->rrs, i);
 		if (keytag != key->keytag || type_covered != covered->type) {
 			continue;
 		}
@@ -138,7 +136,7 @@ static bool all_signatures_exist(const knot_rrset_t *covered,
                                  const knot_zone_keys_t *zone_keys,
                                  const knot_dnssec_policy_t *policy)
 {
-	assert(covered);
+	assert(!knot_rrset_empty(covered));
 	assert(zone_keys);
 
 	for (int i = 0; i < zone_keys->count; i++) {
@@ -171,7 +169,7 @@ static const knot_zone_key_t *get_matching_zone_key(const knot_rrset_t *rrsigs,
 	assert(rrsigs && rrsigs->type == KNOT_RRTYPE_RRSIG);
 	assert(keys);
 
-	uint16_t keytag = knot_rdata_rrsig_key_tag(rrsigs, pos);
+	uint16_t keytag = knot_rrs_rrsig_key_tag(&rrsigs->rrs, pos);
 
 	return knot_get_zone_key(keys, keytag);
 }
@@ -189,7 +187,7 @@ static void note_earliest_expiration(const knot_rrset_t *rrsigs, size_t pos,
 	assert(rrsigs);
 	assert(expires_at);
 
-	const uint32_t current = knot_rdata_rrsig_sig_expiration(rrsigs, pos);
+	const uint32_t current = knot_rrs_rrsig_sig_expiration(&rrsigs->rrs, pos);
 	if (current < *expires_at) {
 		*expires_at = current;
 	}
@@ -216,7 +214,7 @@ static int remove_expired_rrsigs(const knot_rrset_t *covered,
 {
 	assert(changeset);
 
-	if (!rrsigs) {
+	if (knot_rrset_empty(rrsigs)) {
 		return KNOT_EOK;
 	}
 
@@ -225,9 +223,11 @@ static int remove_expired_rrsigs(const knot_rrset_t *covered,
 	knot_rrset_t *to_remove = NULL;
 	int result = KNOT_EOK;
 
-	knot_rrset_t *synth_rrsig = NULL;
-	result = knot_rrset_synth_rrsig(rrsigs->owner, covered->type,
-	                                rrsigs, &synth_rrsig, NULL);
+	knot_rrset_t synth_rrsig;
+	knot_rrset_init(&synth_rrsig, rrsigs->owner, KNOT_RRTYPE_RRSIG,
+	                KNOT_CLASS_IN);
+	result = knot_synth_rrsig(covered->type, &rrsigs->rrs,
+	                          &synth_rrsig.rrs, NULL);
 	if (result != KNOT_EOK) {
 		if (result != KNOT_ENOENT) {
 			return result;
@@ -235,18 +235,18 @@ static int remove_expired_rrsigs(const knot_rrset_t *covered,
 		return KNOT_EOK;
 	}
 
-	uint16_t rrsig_rdata_count = knot_rrset_rr_count(synth_rrsig);
+	uint16_t rrsig_rdata_count = knot_rrset_rr_count(&synth_rrsig);
 	for (uint16_t i = 0; i < rrsig_rdata_count; i++) {
 		const knot_zone_key_t *key;
-		key = get_matching_zone_key(synth_rrsig, i, zone_keys);
+		key = get_matching_zone_key(&synth_rrsig, i, zone_keys);
 
 		if (key && key->is_active && key->context) {
-			result = knot_is_valid_signature(covered, synth_rrsig, i,
+			result = knot_is_valid_signature(covered, &synth_rrsig, i,
 			                                 &key->dnssec_key,
 			                                 key->context, policy);
 			if (result == KNOT_EOK) {
 				// valid signature
-				note_earliest_expiration(synth_rrsig, i, expires_at);
+				note_earliest_expiration(&synth_rrsig, i, expires_at);
 				continue;
 			}
 
@@ -256,14 +256,15 @@ static int remove_expired_rrsigs(const knot_rrset_t *covered,
 		}
 
 		if (to_remove == NULL) {
-			to_remove = create_empty_rrsigs_for(synth_rrsig);
+			to_remove = create_empty_rrsigs_for(&synth_rrsig);
 			if (to_remove == NULL) {
 				result = KNOT_ENOMEM;
 				break;
 			}
 		}
 
-		result = knot_rrset_add_rr_from_rrset(to_remove, synth_rrsig, i, NULL);
+		knot_rr_t *rr_rem = knot_rrs_rr(&synth_rrsig.rrs, i);
+		result = knot_rrs_add_rr(&to_remove->rrs, rr_rem, NULL);
 		if (result != KNOT_EOK) {
 			break;
 		}
@@ -278,7 +279,7 @@ static int remove_expired_rrsigs(const knot_rrset_t *covered,
 		knot_rrset_free(&to_remove, NULL);
 	}
 
-	knot_rrset_free(&synth_rrsig, NULL);
+	knot_rrs_clear(&synth_rrsig.rrs, NULL);
 
 	return result;
 }
@@ -300,12 +301,9 @@ static int add_missing_rrsigs(const knot_rrset_t *covered,
                               const knot_dnssec_policy_t *policy,
                               knot_changeset_t *changeset)
 {
-	assert(covered);
+	assert(!knot_rrset_empty(covered));
 	assert(zone_keys);
 	assert(changeset);
-	if (knot_rrset_rr_count(covered) == 0) {
-		return KNOT_EOK;
-	}
 
 	int result = KNOT_EOK;
 	knot_rrset_t *to_add = NULL;
@@ -361,11 +359,14 @@ static int remove_rrset_rrsigs(const knot_dname_t *owner, uint16_t type,
 {
 	assert(owner);
 	assert(changeset);
-
-	knot_rrset_t *synth_rrsig = NULL;
-	int ret = knot_rrset_synth_rrsig(owner, type, rrsigs, &synth_rrsig,
-	                                 NULL);
+	knot_rrset_t *synth_rrsig =
+		knot_rrset_new(owner, KNOT_RRTYPE_RRSIG, KNOT_CLASS_IN, NULL);
+	if (synth_rrsig == NULL) {
+		return KNOT_ENOMEM;
+	}
+	int ret = knot_synth_rrsig(type, &rrsigs->rrs, &synth_rrsig->rrs, NULL);
 	if (ret != KNOT_EOK) {
+		knot_rrset_free(&synth_rrsig, NULL);
 		if (ret != KNOT_ENOENT) {
 			return ret;
 		}
@@ -397,9 +398,9 @@ static int force_resign_rrset(const knot_rrset_t *covered,
                               const knot_dnssec_policy_t *policy,
                               knot_changeset_t *changeset)
 {
-	assert(covered);
+	assert(!knot_rrset_empty(covered));
 
-	if (rrsigs) {
+	if (!knot_rrset_empty(rrsigs)) {
 		int result = remove_rrset_rrsigs(covered->owner, covered->type,
 		                                 rrsigs, changeset);
 		if (result != KNOT_EOK) {
@@ -428,7 +429,7 @@ static int resign_rrset(const knot_rrset_t *covered,
                         knot_changeset_t *changeset,
                         uint32_t *expires_at)
 {
-	assert(covered);
+	assert(!knot_rrset_empty(covered));
 
 	// TODO this function creates some signatures twice (for checking)
 	// maybe merge the two functions into one
@@ -457,18 +458,23 @@ static int remove_standalone_rrsigs(const knot_node_t *node,
 
 	uint16_t rrsigs_rdata_count = knot_rrset_rr_count(rrsigs);
 	for (uint16_t i = 0; i < rrsigs_rdata_count; ++i) {
-		uint16_t type_covered = knot_rdata_rrsig_type_covered(rrsigs, i);
-		if (!knot_node_rrset(node, type_covered)) {
-			knot_rrset_t *to_remove = knot_rrset_new_from(rrsigs, NULL);
+		uint16_t type_covered = knot_rrs_rrsig_type_covered(&rrsigs->rrs, i);
+		if (!knot_node_rrtype_exists(node, type_covered)) {
+			knot_rrset_t *to_remove = knot_rrset_new(rrsigs->owner,
+			                                         rrsigs->type,
+			                                         rrsigs->rclass,
+			                                         NULL);
 			if (to_remove == NULL) {
 				return KNOT_ENOMEM;
 			}
-			int ret = knot_rrset_add_rr_from_rrset(to_remove, rrsigs, i, NULL);
+			knot_rr_t *rr_rem = knot_rrs_rr(&rrsigs->rrs, i);
+			int ret = knot_rrs_add_rr(&to_remove->rrs, rr_rem, NULL);
 			if (ret != KNOT_EOK) {
 				knot_rrset_free(&to_remove, NULL);
 				return ret;
 			}
-			ret = knot_changeset_add_rr(changeset, to_remove, KNOT_CHANGESET_REMOVE);
+			ret = knot_changeset_add_rrset(changeset,
+			                               to_remove, KNOT_CHANGESET_REMOVE);
 			if (ret != KNOT_EOK) {
 				knot_rrset_free(&to_remove, NULL);
 				return ret;
@@ -500,15 +506,15 @@ static int sign_node_rrsets(const knot_node_t *node,
 	assert(policy);
 
 	int result = KNOT_EOK;
-	const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+	knot_rrset_t rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
 
 	for (int i = 0; i < node->rrset_count; i++) {
-		const knot_rrset_t *rrset = node->rrset_tree[i];
-		if (rrset->type == KNOT_RRTYPE_RRSIG) {
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
+		if (rrset.type == KNOT_RRTYPE_RRSIG) {
 			continue;
 		}
 		bool should_sign = false;
-		result = knot_zone_sign_rr_should_be_signed(node, rrset,
+		result = knot_zone_sign_rr_should_be_signed(node, &rrset,
 		                                            &should_sign);
 		if (result != KNOT_EOK) {
 			return result;
@@ -518,10 +524,10 @@ static int sign_node_rrsets(const knot_node_t *node,
 		}
 
 		if (policy->forced_sign) {
-			result = force_resign_rrset(rrset, rrsigs, zone_keys, policy,
+			result = force_resign_rrset(&rrset, &rrsigs, zone_keys, policy,
 			         changeset);
 		} else {
-			result = resign_rrset(rrset, rrsigs, zone_keys, policy,
+			result = resign_rrset(&rrset, &rrsigs, zone_keys, policy,
 			                      changeset, expires_at);
 		}
 
@@ -530,7 +536,7 @@ static int sign_node_rrsets(const knot_node_t *node,
 		}
 	}
 
-	return remove_standalone_rrsigs(node, rrsigs, changeset);
+	return remove_standalone_rrsigs(node, &rrsigs, changeset);
 }
 
 /*!
@@ -684,7 +690,7 @@ static bool dnskey_rdata_match(const knot_zone_key_t *key,
 static bool dnskey_exists_in_zone(const knot_rrset_t *dnskeys,
                                   const knot_zone_key_t *key)
 {
-	assert(dnskeys);
+	assert(!knot_rrset_empty(dnskeys));
 	assert(key);
 
 	uint16_t dnskeys_rdata_count = knot_rrset_rr_count(dnskeys);
@@ -730,11 +736,10 @@ static int remove_invalid_dnskeys(const knot_rrset_t *soa,
                                   const knot_zone_keys_t *zone_keys,
                                   knot_changeset_t *changeset)
 {
-	assert(soa);
 	assert(soa->type == KNOT_RRTYPE_SOA);
 	assert(changeset);
 
-	if (!dnskeys) {
+	if (knot_rrset_empty(dnskeys)) {
 		return KNOT_EOK;
 	}
 	assert(dnskeys->type == KNOT_RRTYPE_DNSKEY);
@@ -742,9 +747,10 @@ static int remove_invalid_dnskeys(const knot_rrset_t *soa,
 	knot_rrset_t *to_remove = NULL;
 	int result = KNOT_EOK;
 
-	if (!knot_rrset_ttl_equal(dnskeys, soa)) {
+	if (knot_rrset_rr_ttl(dnskeys, 0) != knot_rrset_rr_ttl(soa, 0)) {
 		dbg_dnssec_detail("removing DNSKEYs (SOA TTL differs)\n");
-		result = knot_rrset_copy(dnskeys, &to_remove, NULL);
+		to_remove = knot_rrset_copy(dnskeys, NULL);
+		result = to_remove ? KNOT_EOK : KNOT_ENOMEM;
 		goto done;
 	}
 
@@ -769,14 +775,18 @@ static int remove_invalid_dnskeys(const knot_rrset_t *soa,
 		dbg_dnssec_detail("removing DNSKEY with tag %d\n", keytag);
 
 		if (to_remove == NULL) {
-			to_remove = knot_rrset_new_from(dnskeys, NULL);
+			to_remove = knot_rrset_new(dnskeys->owner,
+			                           dnskeys->type,
+			                           dnskeys->rclass,
+			                           NULL);
 			if (to_remove == NULL) {
 				result = KNOT_ENOMEM;
 				break;
 			}
 		}
 
-		result = knot_rrset_add_rr_from_rrset(to_remove, dnskeys, i, NULL);
+		knot_rr_t *to_rem = knot_rrs_rr(&dnskeys->rrs, i);
+		result = knot_rrs_add_rr(&to_remove->rrs, to_rem, NULL);
 		if (result != KNOT_EOK) {
 			break;
 		}
@@ -807,13 +817,7 @@ static knot_rrset_t *create_dnskey_rrset_from_soa(const knot_rrset_t *soa)
 {
 	assert(soa);
 
-	knot_dname_t *owner = knot_dname_copy(soa->owner);
-	if (!owner) {
-		return NULL;
-	}
-
-	return knot_rrset_new(owner, KNOT_RRTYPE_DNSKEY, soa->rclass,
-	                      NULL);
+	return knot_rrset_new(soa->owner, KNOT_RRTYPE_DNSKEY, soa->rclass, NULL);
 }
 
 /*!
@@ -833,13 +837,14 @@ static int add_missing_dnskeys(const knot_rrset_t *soa,
 {
 	assert(soa);
 	assert(soa->type == KNOT_RRTYPE_SOA);
-	assert(!dnskeys || dnskeys->type == KNOT_RRTYPE_DNSKEY);
+	assert(knot_rrset_empty(dnskeys) || dnskeys->type == KNOT_RRTYPE_DNSKEY);
 	assert(zone_keys);
 	assert(changeset);
 
 	knot_rrset_t *to_add = NULL;
 	int result = KNOT_EOK;
-	bool add_all = (dnskeys == NULL || !knot_rrset_ttl_equal(dnskeys, soa));
+	bool add_all = (knot_rrset_empty(dnskeys) ||
+	                knot_rrset_rr_ttl(dnskeys, 0) != knot_rrset_rr_ttl(soa, 0));
 
 	for (int i = 0; i < zone_keys->count; i++) {
 		const knot_zone_key_t *key = &zone_keys->keys[i];
@@ -870,12 +875,6 @@ static int add_missing_dnskeys(const knot_rrset_t *soa,
 	}
 
 	if (to_add != NULL && result == KNOT_EOK) {
-		//! \todo Sorting should be handled by changesets application.
-		result = knot_rrset_sort_rdata(to_add);
-		if (result != KNOT_EOK) {
-			knot_rrset_free(&to_add, NULL);
-			return result;
-		}
 		result = knot_changeset_add_rrset(changeset, to_add,
 		                                  KNOT_CHANGESET_ADD);
 	}
@@ -928,7 +927,8 @@ static int update_dnskeys_rrsigs(const knot_rrset_t *dnskeys,
 			continue;
 		}
 
-		result = knot_rrset_add_rr_from_rrset(new_dnskeys, dnskeys, i, NULL);
+		knot_rr_t *to_add = knot_rrs_rr(&dnskeys->rrs, i);
+		result = knot_rrs_add_rr(&new_dnskeys->rrs, to_add, NULL);
 		if (result != KNOT_EOK) {
 			goto fail;
 		}
@@ -948,18 +948,13 @@ static int update_dnskeys_rrsigs(const knot_rrset_t *dnskeys,
 		}
 	}
 
-	result = knot_rrset_sort_rdata(new_dnskeys);
-	if (result != KNOT_EOK) {
-		goto fail;
-	}
-
 	result = add_missing_rrsigs(new_dnskeys, NULL, zone_keys, policy,
 	                            changeset);
 	if (result != KNOT_EOK) {
 		goto fail;
 	}
 
-	if (dnskeys) {
+	if (!knot_rrset_empty(dnskeys)) {
 		result = remove_rrset_rrsigs(dnskeys->owner, dnskeys->type,
 		                             rrsigs, changeset);
 	}
@@ -990,30 +985,30 @@ static int update_dnskeys(const knot_zone_contents_t *zone,
 	assert(changeset);
 
 	const knot_node_t *apex = zone->apex;
-	const knot_rrset_t *dnskeys = knot_node_rrset(apex, KNOT_RRTYPE_DNSKEY);
-	const knot_rrset_t *soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
-	const knot_rrset_t *rrsigs = knot_node_rrset(apex, KNOT_RRTYPE_RRSIG);
-
-	if (!soa) {
+	knot_rrset_t dnskeys = knot_node_rrset(apex, KNOT_RRTYPE_DNSKEY);
+	knot_rrset_t soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
+	knot_rrset_t rrsigs = knot_node_rrset(apex, KNOT_RRTYPE_RRSIG);
+	if (knot_rrset_empty(&soa)) {
 		return KNOT_EINVAL;
 	}
 
 	int result;
 	size_t changes_before = knot_changeset_size(changeset);
 
-	result = remove_invalid_dnskeys(soa, dnskeys, zone_keys, changeset);
+	result = remove_invalid_dnskeys(&soa, &dnskeys, zone_keys, changeset);
 	if (result != KNOT_EOK) {
 		return result;
 	}
 
-	result = add_missing_dnskeys(soa, dnskeys, zone_keys, changeset);
+	result = add_missing_dnskeys(&soa, &dnskeys, zone_keys, changeset);
 	if (result != KNOT_EOK) {
 		return result;
 	}
-
-	knot_rrset_t *dnskey_rrsig = NULL;
-	result = knot_rrset_synth_rrsig(apex->owner, KNOT_RRTYPE_DNSKEY,
-	                                rrsigs, &dnskey_rrsig, NULL);
+	knot_rrset_t dnskey_rrsig;
+	knot_rrset_init(&dnskey_rrsig, apex->owner, KNOT_RRTYPE_RRSIG,
+	                KNOT_CLASS_IN);
+	result = knot_synth_rrsig(KNOT_RRTYPE_DNSKEY, &rrsigs.rrs,
+	                          &dnskey_rrsig.rrs, NULL);
 	if (result != KNOT_EOK) {
 		if (result != KNOT_ENOENT) {
 			return result;
@@ -1021,16 +1016,16 @@ static int update_dnskeys(const knot_zone_contents_t *zone,
 	}
 
 	bool modified = (knot_changeset_size(changeset) != changes_before);
-	bool signatures_exist = (dnskeys &&
-	                        all_signatures_exist(dnskeys, dnskey_rrsig,
+	bool signatures_exist = (!knot_rrset_empty(&dnskeys) &&
+	                        all_signatures_exist(&dnskeys, &dnskey_rrsig,
 	                                             zone_keys, policy));
-	knot_rrset_free(&dnskey_rrsig, NULL);
+	knot_rrs_clear(&dnskey_rrsig.rrs, NULL);
 	if (!modified && signatures_exist) {
 		return KNOT_EOK;
 	}
 
 	dbg_dnssec_detail("Creating new signatures for DNSKEYs\n");
-	return update_dnskeys_rrsigs(dnskeys, rrsigs, soa, zone_keys, policy, changeset);
+	return update_dnskeys_rrsigs(&dnskeys, &rrsigs, &soa, zone_keys, policy, changeset);
 }
 
 /*!
@@ -1103,7 +1098,7 @@ static int rr_already_signed(const knot_rrset_t *rrset, hattrie_t *t,
 		}
 		memset(info, 0, sizeof(signed_info_t));
 		// Store actual dname repr
-		info->dname = knot_dname_copy(rrset->owner);
+		info->dname = knot_dname_copy(rrset->owner, NULL);
 		if (info->dname == NULL) {
 			free(info);
 			return KNOT_ENOMEM;
@@ -1164,20 +1159,18 @@ static int sign_changeset_wrap(knot_rrset_t *chg_rrset, void *data)
 
 	// If node is not in zone, all its RRSIGs were dropped - no-op
 	if (node) {
-		const knot_rrset_t *rrsigs = knot_node_rrset(node,
-		                                             KNOT_RRTYPE_RRSIG);
-		const knot_rrset_t *zone_rrset =
-			knot_node_rrset(node, chg_rrset->type);
+		knot_rrset_t zone_rrset = knot_node_rrset(node, chg_rrset->type);
+		knot_rrset_t rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
 		bool should_sign = false;
 
-		int ret = knot_zone_sign_rr_should_be_signed(node, zone_rrset,
+		int ret = knot_zone_sign_rr_should_be_signed(node, &zone_rrset,
 		                                             &should_sign);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
 
 		// Check for RRSet in the 'already_signed' table
-		if (args->signed_tree && (should_sign && zone_rrset == NULL)) {
+		if (args->signed_tree && (should_sign && knot_rrset_empty(&zone_rrset))) {
 			bool already_signed = false;
 
 			int ret = rr_already_signed(chg_rrset, args->signed_tree,
@@ -1192,7 +1185,7 @@ static int sign_changeset_wrap(knot_rrset_t *chg_rrset, void *data)
 		}
 
 		if (should_sign) {
-			return force_resign_rrset(zone_rrset, rrsigs,
+			return force_resign_rrset(&zone_rrset, &rrsigs,
 			                          args->zone_keys,
 			                          args->policy,
 			                          args->changeset);
@@ -1208,7 +1201,7 @@ static int sign_changeset_wrap(knot_rrset_t *chg_rrset, void *data)
 			 * the zone. We need to drop them as well.
 			 */
 			return remove_rrset_rrsigs(chg_rrset->owner,
-			                           chg_rrset->type, rrsigs,
+			                           chg_rrset->type, &rrsigs,
 			                           args->changeset);
 		}
 	}
@@ -1230,8 +1223,8 @@ static int free_helper_trie_node(value_t *val, void *d)
 		WALK_LIST_FREE(*(info->type_list));
 	}
 	free(info->type_list);
-	knot_dname_free(&info->dname);
-	knot_dname_free(&info->hashed_dname);
+	knot_dname_free(&info->dname, NULL);
+	knot_dname_free(&info->hashed_dname, NULL);
 	free(info);
 	return KNOT_EOK;
 }
@@ -1310,10 +1303,10 @@ bool knot_zone_sign_soa_expired(const knot_zone_contents_t *zone,
 		return KNOT_EINVAL;
 	}
 
-	const knot_rrset_t *soa = knot_node_rrset(zone->apex, KNOT_RRTYPE_SOA);
-	const knot_rrset_t *rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
-	assert(soa);
-	return !all_signatures_exist(soa, rrsigs, zone_keys, policy);
+	knot_rrset_t soa = knot_node_rrset(zone->apex, KNOT_RRTYPE_SOA);
+	knot_rrset_t rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
+	assert(!knot_rrset_empty(&soa));
+	return !all_signatures_exist(&soa, &rrsigs, zone_keys, policy);
 }
 
 /*!
@@ -1326,13 +1319,13 @@ int knot_zone_sign_update_soa(const knot_rrset_t *soa,
                               uint32_t new_serial,
                               knot_changeset_t *changeset)
 {
-	if (!soa || !zone_keys || !policy || !changeset) {
+	if (knot_rrset_empty(soa) || !zone_keys || !policy || !changeset) {
 		return KNOT_EINVAL;
 	}
 
 	dbg_dnssec_verb("Updating SOA...\n");
 
-	uint32_t serial = knot_rdata_soa_serial(soa);
+	uint32_t serial = knot_rrs_soa_serial(&soa->rrs);
 	if (serial == UINT32_MAX && policy->soa_up == KNOT_SOA_SERIAL_UPDATE) {
 		// TODO: this is wrong, the value should be 'rewound' to 0 in this case
 		return KNOT_EINVAL;
@@ -1349,7 +1342,7 @@ int knot_zone_sign_update_soa(const knot_rrset_t *soa,
 
 	// remove signatures for old SOA (if there are any)
 
-	if (rrsigs) {
+	if (!knot_rrset_empty(rrsigs)) {
 		result = remove_rrset_rrsigs(soa->owner, soa->type, rrsigs,
 		                             changeset);
 		if (result != KNOT_EOK) {
@@ -1362,18 +1355,18 @@ int knot_zone_sign_update_soa(const knot_rrset_t *soa,
 	knot_rrset_t *soa_from = NULL;
 	knot_rrset_t *soa_to = NULL;
 
-	result = knot_rrset_copy(soa, &soa_from, NULL);
-	if (result != KNOT_EOK) {
-		return result;
+	soa_from = knot_rrset_copy(soa, NULL);
+	if (soa_from == NULL) {
+		return KNOT_ENOMEM;
 	}
 
-	result = knot_rrset_copy(soa, &soa_to, NULL);
-	if (result != KNOT_EOK) {
+	soa_to =  knot_rrset_copy(soa, NULL);
+	if (soa_to == NULL) {
 		knot_rrset_free(&soa_from, NULL);
-		return result;
+		return KNOT_ENOMEM;
 	}
 
-	knot_rdata_soa_serial_set(soa_to, new_serial);
+	knot_rrs_soa_serial_set(&soa_to->rrs, new_serial);
 
 	// add signatures for new SOA
 
@@ -1386,6 +1379,9 @@ int knot_zone_sign_update_soa(const knot_rrset_t *soa,
 
 	// save the result
 
+	assert(changeset->soa_from == NULL);
+	assert(changeset->soa_to == NULL);
+
 	changeset->soa_from = soa_from;
 	changeset->soa_to = soa_to;
 	changeset->serial_from = serial;
@@ -1469,7 +1465,7 @@ int knot_zone_sign_rr_should_be_signed(const knot_node_t *node,
 	}
 
 	*should_sign = false; // Only one case at the end is set to true
-	if (node == NULL || rrset == NULL) {
+	if (node == NULL || knot_rrset_empty(rrset)) {
 		return KNOT_EOK;
 	}
 
@@ -1499,12 +1495,11 @@ int knot_zone_sign_rr_should_be_signed(const knot_node_t *node,
 
 	// These RRs have their signatures stored in changeset already
 	if (knot_node_is_removed_nsec(node)
-	    && ((knot_rrset_type(rrset) == KNOT_RRTYPE_NSEC)
-	         || (knot_rrset_type(rrset) == KNOT_RRTYPE_NSEC3))) {
+	    && ((rrset->type == KNOT_RRTYPE_NSEC)
+	         || (rrset->type == KNOT_RRTYPE_NSEC3))) {
 		return KNOT_EOK;
 	}
 
 	*should_sign = true;
 	return KNOT_EOK;
 }
-
diff --git a/src/knot/modules/synth_record.c b/src/knot/modules/synth_record.c
index a14f2dcc6ead095354407ddd183e6506bad59054..b7ce6241a2b33990585acf4e11feba687eef2ea6 100644
--- a/src/knot/modules/synth_record.c
+++ b/src/knot/modules/synth_record.c
@@ -128,10 +128,10 @@ static int forward_addr_parse(struct query_data *qdata, synth_template_t *tpl, c
 	if (addr_label == NULL || addr_label[0] <= prefix_len) {
 		return KNOT_EINVAL;
 	}
-	
+
 	int addr_len = *addr_label - prefix_len;
 	memcpy(addr_str, addr_label + 1 + prefix_len, addr_len);
-	
+
 	/* Restore correct address format. */
 	char sep = str_separator(tpl->subnet.ss.ss_family);
 	str_subst(addr_str, addr_len, '-', sep);
@@ -197,7 +197,7 @@ static int reverse_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt, kn
 
 	rr->type = KNOT_RRTYPE_PTR;
 	knot_rrset_add_rr(rr, ptrname, knot_dname_size(ptrname), tpl->ttl, &pkt->mm);
-	knot_dname_free(&ptrname);
+	knot_dname_free(&ptrname, NULL);
 
 	return KNOT_EOK;
 }
@@ -227,11 +227,9 @@ static int forward_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt, kn
 
 static knot_rrset_t *synth_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt, struct query_data *qdata)
 {
-	/* Synthetize empty RR. */
-	knot_dname_t* qname = knot_dname_copy(qdata->name);
-	knot_rrset_t *rr = knot_rrset_new(qname, 0, KNOT_CLASS_IN, &pkt->mm);
+	knot_rrset_t *rr = knot_rrset_new(qdata->name, 0, KNOT_CLASS_IN,
+	                                  &pkt->mm);
 	if (rr == NULL) {
-		knot_dname_free(&qname);
 		return NULL;
 	}
 
@@ -367,7 +365,7 @@ int synth_record_load(struct query_plan *plan, struct query_module *self)
 			MODULE_ERR("invalid zone '%s'.\n", tpl->zone);
 			return KNOT_EMALF;
 		}
-		knot_dname_free(&check_name);
+		knot_dname_free(&check_name, NULL);
 	}
 
 	/* Parse TTL. */
diff --git a/src/knot/nameserver/axfr.c b/src/knot/nameserver/axfr.c
index 357719c220b5952b766509b905e2653fc5a31d4d..1fb1be0f2a70da8307d55d245022c1d18b5fdfa4 100644
--- a/src/knot/nameserver/axfr.c
+++ b/src/knot/nameserver/axfr.c
@@ -14,7 +14,6 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
 #include "knot/nameserver/axfr.h"
 #include "knot/nameserver/internet.h"
 #include "knot/nameserver/process_query.h"
@@ -36,14 +35,14 @@ static int put_rrsets(knot_pkt_t *pkt, knot_node_t *node, struct axfr_proc *stat
 	int i = state->cur_rrset;
 	int rrset_count = knot_node_rrset_count(node);
 	unsigned flags = KNOT_PF_NOTRUNC;
-	const knot_rrset_t **rrset = knot_node_rrsets_no_copy(node);
 
 	/* Append all RRs. */
 	for (;i < rrset_count; ++i) {
-		if (rrset[i]->type == KNOT_RRTYPE_SOA) {
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
+		if (rrset.type == KNOT_RRTYPE_SOA) {
 			continue;
 		}
-		ret = knot_pkt_put(pkt, 0, rrset[i], flags);
+		ret = knot_pkt_put(pkt, 0, &rrset, flags);
 
 		/* If something failed, remember the current RR for later. */
 		if (ret != KNOT_EOK) {
@@ -135,11 +134,11 @@ int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb process_item, struct query_data
 	mm_ctx_t *mm = qdata->mm;
 	struct xfr_proc *xfer = qdata->ext;
 	knot_zone_contents_t *zone = qdata->zone->contents;
-	knot_rrset_t *soa_rr = knot_node_get_rrset(zone->apex, KNOT_RRTYPE_SOA);
+	knot_rrset_t soa_rr = knot_node_rrset(zone->apex, KNOT_RRTYPE_SOA);
 
 	/* Prepend SOA on first packet. */
 	if (xfer->npkts == 0) {
-		ret = knot_pkt_put(pkt, 0, soa_rr, KNOT_PF_NOTRUNC);
+		ret = knot_pkt_put(pkt, 0, &soa_rr, KNOT_PF_NOTRUNC);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
@@ -160,7 +159,7 @@ int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb process_item, struct query_data
 
 	/* Append SOA on last packet. */
 	if (ret == KNOT_EOK) {
-		ret = knot_pkt_put(pkt, 0, soa_rr, KNOT_PF_NOTRUNC);
+		ret = knot_pkt_put(pkt, 0, &soa_rr, KNOT_PF_NOTRUNC);
 	}
 
 	/* Update counters. */
diff --git a/src/knot/nameserver/chaos.c b/src/knot/nameserver/chaos.c
index aa2cab5f7f1f4d92649acbed0f9f044205debc6b..0a3cd2b526e1025e55dfe5b760086aed4ce77cb9 100644
--- a/src/knot/nameserver/chaos.c
+++ b/src/knot/nameserver/chaos.c
@@ -50,11 +50,12 @@ static const char *get_txt_response_string(const knot_dname_t *qname)
  * \param owner     RR owner name.
  * \param response  String to be saved in RDATA. Truncated to 255 chars.
  * \param mm        Memory context.
+ * \param rrset     Store here.
  *
- * \return Allocated RRset or NULL in case of error.
+ * \return KNOT_EOK
  */
-static knot_rrset_t *create_txt_rrset(const knot_dname_t *owner,
-                                      const char *response, mm_ctx_t *mm)
+static int create_txt_rrset(knot_rrset_t *rrset, const knot_dname_t *owner,
+                            const char *response, mm_ctx_t *mm)
 {
 	// truncate response to one TXT label
 	size_t response_len = strlen(response);
@@ -62,27 +63,24 @@ static knot_rrset_t *create_txt_rrset(const knot_dname_t *owner,
 		response_len = KNOT_DNAME_MAXLEN;
 	}
 
-	knot_dname_t *rowner = knot_dname_copy(owner);
+	knot_dname_t *rowner = knot_dname_copy(owner, mm);
 	if (!rowner) {
-		return NULL;
+		return KNOT_ENOMEM;
 	}
 
-	knot_rrset_t *rrset;
-	rrset = knot_rrset_new(rowner, KNOT_RRTYPE_TXT, KNOT_CLASS_CH, mm);
-	if (!rrset) {
-		return NULL;
-	}
-
-	uint8_t *rdata = knot_rrset_create_rr(rrset, response_len + 1, 0, mm);
-	if (!rdata) {
-		knot_rrset_free(&rrset, mm);
-		return NULL;
-	}
+	knot_rrset_init(rrset, rowner, KNOT_RRTYPE_TXT, KNOT_CLASS_CH);
+	uint8_t rdata[response_len + 1];
 
 	rdata[0] = response_len;
 	memcpy(&rdata[1], response, response_len);
 
-	return rrset;
+	int ret = knot_rrset_add_rr(rrset, rdata, response_len + 1, 0, mm);
+	if (ret != KNOT_EOK) {
+		knot_dname_free(&rrset->owner, mm);
+		return ret;
+	}
+
+	return KNOT_EOK;
 }
 
 /*!
@@ -99,15 +97,15 @@ static int answer_txt(knot_pkt_t *response)
 		return KNOT_RCODE_REFUSED;
 	}
 
-	knot_rrset_t *rrset = create_txt_rrset(qname, response_str,
-	                                       &response->mm);
-	if (!rrset) {
+	knot_rrset_t rrset;
+	int ret = create_txt_rrset(&rrset, qname, response_str, &response->mm);
+	if (ret != KNOT_EOK) {
 		return KNOT_RCODE_SERVFAIL;
 	}
 
-	int result = knot_pkt_put(response, 0, rrset, KNOT_PF_FREE);
+	int result = knot_pkt_put(response, 0, &rrset, KNOT_PF_FREE);
 	if (result != KNOT_EOK) {
-		knot_rrset_free(&rrset, &response->mm);
+		knot_rrset_clear(&rrset, &response->mm);
 		return KNOT_RCODE_SERVFAIL;
 	}
 
diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c
index a1fdfb590d00ee88e41d55f3cbca8affc004ef49..fbffd55513fccecc28bcb13bc4d087a094b81fdc 100644
--- a/src/knot/nameserver/internet.c
+++ b/src/knot/nameserver/internet.c
@@ -1,4 +1,3 @@
-
 #include "knot/nameserver/internet.h"
 #include "knot/nameserver/nsec_proofs.h"
 #include "knot/nameserver/process_query.h"
@@ -8,6 +7,7 @@
 #include "common/debug.h"
 #include "common/descriptor.h"
 #include "knot/server/zones.h"
+#include "libknot/dnssec/rrset-sign.h"
 
 /*! \brief Check if given node was already visited. */
 static int wildcard_has_visited(struct query_data *qdata, const knot_node_t *node)
@@ -41,47 +41,46 @@ static int wildcard_visit(struct query_data *qdata, const knot_node_t *node, con
 }
 
 /*! \brief Synthetizes a CNAME RR from a DNAME. */
-static knot_rrset_t *dname_cname_synth(const knot_rrset_t *dname_rr,
-                                       const knot_dname_t *qname, mm_ctx_t *mm)
+static int dname_cname_synth(const knot_rrset_t *dname_rr,
+                             const knot_dname_t *qname,
+                             knot_rrset_t *cname_rrset,
+                             mm_ctx_t *mm)
 {
-	dbg_ns("%s(%p, %p)\n", __func__, dname_rr, qname);
-	knot_dname_t *owner = knot_dname_copy(qname);
-	if (owner == NULL) {
-		return NULL;
+	if (cname_rrset == NULL) {
+		return KNOT_EINVAL;
 	}
+	dbg_ns("%s(%p, %p)\n", __func__, dname_rr, qname);
 
-	knot_rrset_t *cname_rrset = knot_rrset_new(owner, KNOT_RRTYPE_CNAME,
-	                                           KNOT_CLASS_IN,
-	                                           mm);
-	if (cname_rrset == NULL) {
-		knot_dname_free(&owner);
-		return NULL;
+	knot_dname_t *owner_copy = knot_dname_copy(qname, mm);
+	if (owner_copy == NULL) {
+		return KNOT_ENOMEM;
 	}
+	knot_rrset_init(cname_rrset, owner_copy, KNOT_RRTYPE_CNAME, dname_rr->rclass);
 
 	/* Replace last labels of qname with DNAME. */
-	const knot_dname_t *dname_wire = knot_rrset_owner(dname_rr);
-	const knot_dname_t *dname_tgt = knot_rdata_dname_target(dname_rr);
+	const knot_dname_t *dname_wire = dname_rr->owner;
+	const knot_dname_t *dname_tgt = knot_rrs_dname_target(&dname_rr->rrs);
 	int labels = knot_dname_labels(dname_wire, NULL);
 	knot_dname_t *cname = knot_dname_replace_suffix(qname, labels, dname_tgt);
 	if (cname == NULL) {
-		knot_rrset_free(&cname_rrset, NULL);
-		return NULL;
+		knot_dname_free(&owner_copy, mm);
+		return KNOT_ENOMEM;
 	}
 
 	/* Store DNAME into RDATA. */
 	int cname_size = knot_dname_size(cname);
-	uint8_t *cname_rdata = knot_rrset_create_rr(cname_rrset, cname_size,
-	                                            knot_rrset_rr_ttl(dname_rr, 0),
-	                                            mm);
-	if (cname_rdata == NULL) {
-		knot_rrset_free(&cname_rrset, NULL);
-		knot_dname_free(&cname);
-		return NULL;
-	}
+	uint8_t cname_rdata[cname_size];
 	memcpy(cname_rdata, cname, cname_size);
-	knot_dname_free(&cname);
+	knot_dname_free(&cname, NULL);
 
-	return cname_rrset;
+	int ret = knot_rrset_add_rr(cname_rrset, cname_rdata, cname_size,
+	                            knot_rrset_rr_ttl(dname_rr, 0), mm);
+	if (ret != KNOT_EOK) {
+		knot_dname_free(&owner_copy, mm);
+		return ret;
+	}
+
+	return KNOT_EOK;
 }
 
 /*!
@@ -91,8 +90,8 @@ static knot_rrset_t *dname_cname_synth(const knot_rrset_t *dname_rr,
 static bool dname_cname_cannot_synth(const knot_rrset_t *rrset, const knot_dname_t *qname)
 {
 	if (knot_dname_labels(qname, NULL)
-		- knot_dname_labels(knot_rrset_owner(rrset), NULL)
-		+ knot_dname_labels(knot_rdata_dname_target(rrset), NULL)
+		- knot_dname_labels(rrset->owner, NULL)
+		+ knot_dname_labels(knot_rrs_dname_target(&rrset->rrs), NULL)
 		> KNOT_DNAME_MAXLABELS) {
 		return true;
 	} else {
@@ -113,9 +112,9 @@ static int put_rrsig(const knot_dname_t *sig_owner, uint16_t type,
                      knot_rrinfo_t *rrinfo,
                      struct query_data *qdata)
 {
-	knot_rrset_t *synth_sig = NULL;
-	int ret = knot_rrset_synth_rrsig(sig_owner, type, rrsigs,
-	                                 &synth_sig, qdata->mm);
+	knot_rrs_t synth_rrs;
+	knot_rrs_init(&synth_rrs);
+	int ret = knot_synth_rrsig(type, &rrsigs->rrs, &synth_rrs, qdata->mm);
 	if (ret == KNOT_ENOENT) {
 		// No signature
 		return KNOT_EOK;
@@ -123,14 +122,25 @@ static int put_rrsig(const knot_dname_t *sig_owner, uint16_t type,
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
-	struct rrsig_info *info = mm_alloc(qdata->mm,
-	                                   sizeof(struct rrsig_info));
+
+	/* Create rrsig info structure. */
+	struct rrsig_info *info = mm_alloc(qdata->mm, sizeof(struct rrsig_info));
 	if (info == NULL) {
-		ERR_ALLOC_FAILED;
-		knot_rrset_free(&synth_sig, qdata->mm);
+		knot_rrs_clear(&synth_rrs, qdata->mm);
+		return KNOT_ENOMEM;
+	}
+
+	/* Store RRSIG into info structure. */
+	knot_dname_t *owner_copy = knot_dname_copy(sig_owner, qdata->mm);
+	if (owner_copy == NULL) {
+		mm_free(qdata->mm, info);
+		knot_rrs_clear(&synth_rrs, qdata->mm);
 		return KNOT_ENOMEM;
 	}
-	info->synth_rrsig = synth_sig;
+	knot_rrset_init(&info->synth_rrsig, owner_copy, rrsigs->type, rrsigs->rclass);
+	/* Store filtered signature. */
+	info->synth_rrsig.rrs = synth_rrs;
+
 	info->rrinfo = rrinfo;
 	add_tail(&qdata->rrsigs, &info->n);
 
@@ -142,8 +152,8 @@ static int put_rrsig(const knot_dname_t *sig_owner, uint16_t type,
  */
 static int put_answer(knot_pkt_t *pkt, uint16_t type, struct query_data *qdata)
 {
-	const knot_rrset_t *rrset = NULL;
-	knot_rrset_t **rrsets = knot_node_get_rrsets_no_copy(qdata->node);
+	knot_rrset_t rrset;
+	knot_rrset_init_empty(&rrset);
 
 	/* Wildcard expansion or exact match, either way RRSet owner is
 	 * is QNAME. We can fake name synthesis by setting compression hint to
@@ -157,7 +167,7 @@ static int put_answer(knot_pkt_t *pkt, uint16_t type, struct query_data *qdata)
 
 	int ret = KNOT_EOK;
 	switch (type) {
-	case KNOT_RRTYPE_ANY: /* Append all RRSets. */
+	case KNOT_RRTYPE_ANY: /* Append all RRSets. */ {
 		/* If ANY not allowed, set TC bit. */
 		if ((qdata->param->proc_flags & NS_QUERY_LIMIT_ANY) &&
 		    (qdata->zone->conf->disable_any)) {
@@ -166,17 +176,19 @@ static int put_answer(knot_pkt_t *pkt, uint16_t type, struct query_data *qdata)
 			return KNOT_ESPACE;
 		}
 		for (unsigned i = 0; i < knot_node_rrset_count(qdata->node); ++i) {
-			ret = ns_put_rr(pkt, rrsets[i], NULL, compr_hint, 0, qdata);
+			rrset = knot_node_rrset_at(qdata->node, i);
+			ret = ns_put_rr(pkt, &rrset, NULL, compr_hint, 0, qdata);
 			if (ret != KNOT_EOK) {
 				break;
 			}
 		}
 		break;
+	}
 	default: /* Single RRSet of given type. */
-		rrset = knot_node_get_rrset(qdata->node, type);
-		if (rrset) {
-			const knot_rrset_t *rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
-			ret = ns_put_rr(pkt, rrset, rrsigs, compr_hint, 0, qdata);
+		rrset = knot_node_rrset(qdata->node, type);
+		if (!knot_rrset_empty(&rrset)) {
+			knot_rrset_t rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
+			ret = ns_put_rr(pkt, &rrset, &rrsigs, compr_hint, 0, qdata);
 		}
 		break;
 	}
@@ -202,10 +214,10 @@ static int put_authority_ns(knot_pkt_t *pkt, struct query_data *qdata)
 		return KNOT_EOK;
 	}
 
-	const knot_rrset_t *ns_rrset = knot_node_rrset(zone->apex, KNOT_RRTYPE_NS);
-	if (ns_rrset) {
-		const knot_rrset_t *rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
-		return ns_put_rr(pkt, ns_rrset, rrsigs, COMPR_HINT_NONE,
+	knot_rrset_t ns_rrset = knot_node_rrset(zone->apex, KNOT_RRTYPE_NS);
+	if (!knot_rrset_empty(&ns_rrset)) {
+		knot_rrset_t rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
+		return ns_put_rr(pkt, &ns_rrset, &rrsigs, COMPR_HINT_NONE,
 		                 KNOT_PF_NOTRUNC|KNOT_PF_CHECKDUP, qdata);
 	} else {
 		dbg_ns("%s: no NS RRSets in this zone, fishy...\n", __func__);
@@ -218,28 +230,35 @@ static int put_authority_soa(knot_pkt_t *pkt, struct query_data *qdata,
                              const knot_zone_contents_t *zone)
 {
 	dbg_ns("%s(%p, %p)\n", __func__, pkt, zone);
-	knot_rrset_t *soa_rrset = knot_node_get_rrset(zone->apex, KNOT_RRTYPE_SOA);
-	assert(soa_rrset);
-	const knot_rrset_t *rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
+	knot_rrset_t soa_rrset = knot_node_rrset(zone->apex, KNOT_RRTYPE_SOA);
+	knot_rrset_t rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG);
 
 	// if SOA's TTL is larger than MINIMUM, copy the RRSet and set
 	// MINIMUM as TTL
 	int ret = KNOT_EOK;
 	uint32_t flags = KNOT_PF_NOTRUNC;
-	uint32_t min = knot_rdata_soa_minimum(soa_rrset);
-	if (min < knot_rrset_rr_ttl(soa_rrset, 0)) {
-		ret = knot_rrset_copy(soa_rrset, &soa_rrset, &pkt->mm);
+	uint32_t min = knot_rrs_soa_minimum(&soa_rrset.rrs);
+	if (min < knot_rrset_rr_ttl(&soa_rrset, 0)) {
+		knot_rrset_t copy;
+		knot_dname_t *dname_cpy = knot_dname_copy(soa_rrset.owner, &pkt->mm);
+		if (dname_cpy == NULL) {
+			return KNOT_ENOMEM;
+		}
+		knot_rrset_init(&copy, dname_cpy, soa_rrset.type, soa_rrset.rclass);
+		int ret = knot_rrs_copy(&copy.rrs, &soa_rrset.rrs, &pkt->mm);
 		if (ret != KNOT_EOK) {
+			knot_dname_free(&dname_cpy, &pkt->mm);
 			return ret;
 		}
+		knot_rrset_rr_set_ttl(&copy, 0, min);
 
-		knot_rrset_rr_set_ttl(soa_rrset, 0, min);
 		flags |= KNOT_PF_FREE;
+		soa_rrset = copy;
 	}
 
-	ret = ns_put_rr(pkt, soa_rrset, rrsigs, COMPR_HINT_NONE, flags, qdata);
+	ret = ns_put_rr(pkt, &soa_rrset, &rrsigs, COMPR_HINT_NONE, flags, qdata);
 	if (ret != KNOT_EOK && (flags & KNOT_PF_FREE)) {
-		knot_rrset_free(&soa_rrset, &pkt->mm);
+		knot_rrset_clear(&soa_rrset, &pkt->mm);
 	}
 
 	return ret;
@@ -254,9 +273,9 @@ static int put_delegation(knot_pkt_t *pkt, struct query_data *qdata)
 	}
 
 	/* Insert NS record. */
-	const knot_rrset_t *rrset = knot_node_rrset(qdata->node, KNOT_RRTYPE_NS);
-	const knot_rrset_t *rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
-	return ns_put_rr(pkt, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
+	knot_rrset_t rrset = knot_node_rrset(qdata->node, KNOT_RRTYPE_NS);
+	knot_rrset_t rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
+	return ns_put_rr(pkt, &rrset, &rrsigs, COMPR_HINT_NONE, 0, qdata);
 }
 
 /*! \brief Put additional records for given RR. */
@@ -272,7 +291,6 @@ static int put_additional(knot_pkt_t *pkt, const knot_rrset_t *rr,
 	uint32_t flags = KNOT_PF_NOTRUNC|KNOT_PF_CHECKDUP;
 	uint16_t hint = COMPR_HINT_NONE;
 	const knot_node_t *node = NULL;
-	const knot_rrset_t *additional = NULL;
 
 	/* All RRs should have additional node cached or NULL. */
 	uint16_t rr_rdata_count = knot_rrset_rr_count(rr);
@@ -284,13 +302,14 @@ static int put_additional(knot_pkt_t *pkt, const knot_rrset_t *rr,
 		if (node == NULL) {
 			continue;
 		}
-		const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+
+		knot_rrset_t rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
 		for (int k = 0; k < ar_type_count; ++k) {
-			additional = knot_node_rrset(node, ar_type_list[k]);
-			if (additional == NULL) {
+			knot_rrset_t additional = knot_node_rrset(node, ar_type_list[k]);
+			if (knot_rrset_empty(&additional)) {
 				continue;
 			}
-			ret = ns_put_rr(pkt, additional, rrsigs,
+			ret = ns_put_rr(pkt, &additional, &rrsigs,
 			                hint, flags, qdata);
 			if (ret != KNOT_EOK) {
 				break;
@@ -306,18 +325,18 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, struct query_data *qda
 	dbg_ns("%s(%p, %p)\n", __func__, pkt, qdata);
 
 	const knot_node_t *cname_node = qdata->node;
-	knot_rrset_t *cname_rr = knot_node_get_rrset(qdata->node, rrtype);
-	const knot_rrset_t *rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
+	knot_rrset_t cname_rr = knot_node_rrset(qdata->node, rrtype);
+	knot_rrset_t rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
 	int ret = KNOT_EOK;
 
-	assert(cname_rr != NULL);
+	assert(!knot_rrset_empty(&cname_rr));
 
 	/* Check whether RR is already in the packet. */
 	uint16_t flags = KNOT_PF_CHECKDUP;
 
 	/* Now, try to put CNAME to answer. */
 	uint16_t rr_count_before = pkt->rrset_count;
-	ret = ns_put_rr(pkt, cname_rr, rrsigs, 0, flags, qdata);
+	ret = ns_put_rr(pkt, &cname_rr, &rrsigs, 0, flags, qdata);
 	switch (ret) {
 	case KNOT_EOK:    break;
 	case KNOT_ESPACE: return TRUNC;
@@ -327,19 +346,25 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, struct query_data *qda
 	/* Check if RR count increased. */
 	if (pkt->rrset_count <= rr_count_before) {
 		dbg_ns("%s: RR %p already inserted => CNAME loop\n",
-		       __func__, cname_rr);
+		       __func__, &cname_rr);
 		qdata->node = NULL; /* Act is if the name leads to nowhere. */
 		return HIT;
 	}
 
 	/* Synthesize CNAME if followed DNAME. */
 	if (rrtype == KNOT_RRTYPE_DNAME) {
-		if (dname_cname_cannot_synth(cname_rr, qdata->name)) {
+		if (dname_cname_cannot_synth(&cname_rr, qdata->name)) {
 			qdata->rcode = KNOT_RCODE_YXDOMAIN;
 			return ERROR;
 		}
-		cname_rr = dname_cname_synth(cname_rr, qdata->name, &pkt->mm);
-		ret = ns_put_rr(pkt, cname_rr, NULL, 0, KNOT_PF_FREE, qdata);
+		knot_rrset_t dname_rr = cname_rr;
+		ret = dname_cname_synth(&dname_rr, qdata->name, &cname_rr,
+		                        &pkt->mm);
+		if (ret != KNOT_EOK) {
+			qdata->rcode = KNOT_RCODE_SERVFAIL;
+			return ERROR;
+		}
+		ret = ns_put_rr(pkt, &cname_rr, NULL, 0, KNOT_PF_FREE, qdata);
 		switch (ret) {
 		case KNOT_EOK:    break;
 		case KNOT_ESPACE: return TRUNC;
@@ -367,7 +392,7 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, struct query_data *qda
 	}
 
 	/* Now follow the next CNAME TARGET. */
-	qdata->name = knot_rdata_cname_name(cname_rr);
+	qdata->name = knot_rrs_cname_name(&cname_rr.rrs);
 
 #ifdef KNOT_NS_DEBUG
 	char *cname_str = knot_dname_to_str(cname_node->owner);
@@ -385,7 +410,7 @@ static int name_found(knot_pkt_t *pkt, struct query_data *qdata)
 	uint16_t qtype = knot_pkt_qtype(pkt);
 	dbg_ns("%s(%p, %p)\n", __func__, pkt, qdata);
 
-	if (knot_node_rrset(qdata->node, KNOT_RRTYPE_CNAME) != NULL
+	if (knot_node_rrtype_exists(qdata->node, KNOT_RRTYPE_CNAME)
 	    && qtype != KNOT_RRTYPE_CNAME
 	    && qtype != KNOT_RRTYPE_RRSIG
 	    && qtype != KNOT_RRTYPE_ANY) {
@@ -445,9 +470,8 @@ static int name_not_found(knot_pkt_t *pkt, struct query_data *qdata)
 	}
 
 	/* 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_rr_count(dname_rrset) > 0) {
+	knot_rrset_t dname_rrset = knot_node_rrset(qdata->encloser, KNOT_RRTYPE_DNAME);
+	if (!knot_rrset_empty(&dname_rrset)) {
 		dbg_ns("%s: solving DNAME for name %p\n", __func__, qdata->name);
 		qdata->node = qdata->encloser; /* Follow encloser as new node. */
 		return follow_cname(pkt, KNOT_RRTYPE_DNAME, qdata);
@@ -566,7 +590,6 @@ static int solve_authority_dnssec(int state, knot_pkt_t *pkt, struct query_data
 
 	int ret = KNOT_ERROR;
 
-
 	/* Authenticated denial of existence. */
 	switch (state) {
 	case HIT:    ret = KNOT_EOK; break;
@@ -609,11 +632,11 @@ static int solve_additional(int state, knot_pkt_t *pkt, struct query_data *qdata
 	/* Scan all RRs in ANSWER/AUTHORITY. */
 	for (uint16_t i = 0; i < pkt->rrset_count; ++i) {
 		/* Skip types for which it doesn't apply. */
-		if (!rrset_additional_needed(pkt->rr[i]->type)) {
+		if (!knot_rrtype_additional_needed(pkt->rr[i].type)) {
 			continue;
 		}
 		/* Put additional records for given type. */
-		ret = put_additional(pkt, pkt->rr[i], qdata, &pkt->rr_info[i]);
+		ret = put_additional(pkt, &pkt->rr[i], qdata, &pkt->rr_info[i]);
 		if (ret != KNOT_EOK) {
 			break;
 		}
@@ -646,10 +669,8 @@ int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr,
               const knot_rrset_t *rrsigs, uint16_t compr_hint,
               uint32_t flags, struct query_data *qdata)
 {
-	/* RFC3123 s.6 - empty APL is valid, ignore other empty RRs. */
-	if (knot_rrset_rr_count(rr) < 1 &&
-	    knot_rrset_type(rr) != KNOT_RRTYPE_APL) {
-		dbg_ns("%s: refusing to put empty RR of type %u\n", __func__, knot_rrset_type(rr));
+	if (knot_rrset_rr_count(rr) < 1) {
+		dbg_ns("%s: refusing to put empty RR of type %u\n", __func__, rr->type);
 		return KNOT_EMALF;
 	}
 
@@ -664,27 +685,33 @@ int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr,
 	 * we can just insert RRSet and fake synthesis by using compression
 	 * hint. */
 	int ret = KNOT_EOK;
+	knot_rrset_t to_add;
 	if (compr_hint == COMPR_HINT_NONE && expand) {
-		ret = knot_rrset_copy(rr, (knot_rrset_t **)&rr, &pkt->mm);
-		if (ret != KNOT_EOK) {
+		knot_dname_t *qname_cpy = knot_dname_copy(qdata->name, &pkt->mm);
+		if (qname_cpy == NULL) {
 			return KNOT_ENOMEM;
 		}
-
-		knot_rrset_set_owner((knot_rrset_t *)rr, qdata->name);
+		knot_rrset_init(&to_add, qname_cpy, rr->type, rr->rclass);
+		int ret = knot_rrs_copy(&to_add.rrs, &rr->rrs, &pkt->mm);
+		if (ret != KNOT_EOK) {
+			knot_dname_free(&qname_cpy, &pkt->mm);
+		}
+		to_add.additional = rr->additional;
 		flags |= KNOT_PF_FREE;
+	} else {
+		to_add = *rr;
 	}
 
 	uint16_t prev_count = pkt->rrset_count;
-	ret = knot_pkt_put(pkt, compr_hint, rr, flags);
-	if (ret != KNOT_EOK) {
-		if (flags & KNOT_PF_FREE) {
-			knot_rrset_free((knot_rrset_t **)&rr, &pkt->mm);
-		}
+	ret = knot_pkt_put(pkt, compr_hint, &to_add, flags);
+	if (ret != KNOT_EOK && (flags & KNOT_PF_FREE)) {
+		knot_rrset_clear(&to_add, &pkt->mm);
 		return ret;
 	}
 
-	bool inserted = (prev_count != pkt->rrset_count);
-	if (inserted && rrsigs && rr->type != KNOT_RRTYPE_RRSIG) {
+	const bool inserted = (prev_count != pkt->rrset_count);
+	if (inserted &&
+	    !knot_rrset_empty(rrsigs) && rr->type != KNOT_RRTYPE_RRSIG) {
 		// Get rrinfo of just inserted RR.
 		knot_rrinfo_t *rrinfo = &pkt->rr_info[pkt->rrset_count - 1];
 		ret = put_rrsig(rr->owner, rr->type, rrsigs, rrinfo, qdata);
diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c
index 83ffa1d9db42b53b55b26e33afc156d2da8ebc34..422d7aab0e758a2193d31a5888c9ecf1a03b73ca 100644
--- a/src/knot/nameserver/ixfr.c
+++ b/src/knot/nameserver/ixfr.c
@@ -1,4 +1,3 @@
-
 #include "knot/nameserver/ixfr.h"
 #include "knot/nameserver/axfr.h"
 #include "knot/nameserver/internet.h"
@@ -130,10 +129,8 @@ static int ixfr_load_chsets(knot_changesets_t **chgsets, const zone_t *zone,
 	assert(zone);
 
 	/* Compare serials. */
-	const knot_node_t *apex = zone->contents->apex;
-	const knot_rrset_t *our_soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
-	uint32_t serial_to = knot_rdata_soa_serial(our_soa);
-	uint32_t serial_from = knot_rdata_soa_serial(their_soa);
+	uint32_t serial_to = knot_zone_serial(zone->contents);
+	uint32_t serial_from = knot_rrs_soa_serial(&their_soa->rrs);
 	int ret = knot_serial_compare(serial_to, serial_from);
 	if (ret <= 0) { /* We have older/same age zone. */
 		return KNOT_EUPTODATE;
@@ -162,8 +159,8 @@ static int ixfr_query_check(struct query_data *qdata)
 	NS_NEED_QTYPE(qdata, KNOT_RRTYPE_IXFR, KNOT_RCODE_FORMERR);
 	/* Need SOA authority record. */
 	const knot_pktsection_t *authority = knot_pkt_section(qdata->query, KNOT_AUTHORITY);
-	const knot_rrset_t *their_soa = authority->rr[0];
-	if (authority->count < 1 || knot_rrset_type(their_soa) != KNOT_RRTYPE_SOA) {
+	const knot_rrset_t *their_soa = &authority->rr[0];
+	if (authority->count < 1 || their_soa->type != KNOT_RRTYPE_SOA) {
 		qdata->rcode = KNOT_RCODE_FORMERR;
 		return NS_PROC_FAIL;
 	}
@@ -203,7 +200,7 @@ static int ixfr_answer_init(struct query_data *qdata)
 	}
 
 	/* Compare serials. */
-	const knot_rrset_t *their_soa = knot_pkt_section(qdata->query, KNOT_AUTHORITY)->rr[0];
+	const knot_rrset_t *their_soa = &knot_pkt_section(qdata->query, KNOT_AUTHORITY)->rr[0];
 	knot_changesets_t *chgsets = NULL;
 	int ret = ixfr_load_chsets(&chgsets, qdata->zone, their_soa);
 	if (ret != KNOT_EOK) {
@@ -265,8 +262,11 @@ static int ixfr_answer_soa(knot_pkt_t *pkt, struct query_data *qdata)
 
 	/* Guaranteed to have zone contents. */
 	const knot_node_t *apex = qdata->zone->contents->apex;
-	const knot_rrset_t *soa_rr = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
-	int ret = knot_pkt_put(pkt, 0, soa_rr, 0);
+	knot_rrset_t soa_rr = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
+	if (knot_rrset_empty(&soa_rr)) {
+		return NS_PROC_FAIL;
+	}
+	int ret = knot_pkt_put(pkt, 0, &soa_rr, 0);
 	if (ret != KNOT_EOK) {
 		qdata->rcode = KNOT_RCODE_SERVFAIL;
 		return NS_PROC_FAIL;
@@ -297,8 +297,8 @@ int ixfr_answer(knot_pkt_t *pkt, struct query_data *qdata)
 		case KNOT_EOK:      /* OK */
 			ixfr = (struct ixfr_proc*)qdata->ext;
 			IXFR_LOG(LOG_INFO, "Started (serial %u -> %u).",
-			         knot_rdata_soa_serial(ixfr->soa_from),
-			         knot_rdata_soa_serial(ixfr->soa_to));
+			         knot_rrs_soa_serial(&ixfr->soa_from->rrs),
+			         knot_rrs_soa_serial(&ixfr->soa_to->rrs));
 			break;
 		case KNOT_EUPTODATE: /* Our zone is same age/older, send SOA. */
 			IXFR_LOG(LOG_INFO, "Zone is up-to-date.");
@@ -385,20 +385,10 @@ int ixfr_process_answer(knot_pkt_t *pkt, knot_ns_xfr_t *xfr)
 		case XFRIN_RES_SOA_ONLY: {
 			// compare the SERIAL from the changeset with the zone's
 			// serial
-			const knot_node_t *apex = zone->contents->apex;
-			if (apex == NULL) {
-				return KNOT_ERROR;
-			}
-
-			const knot_rrset_t *zone_soa = knot_node_rrset(
-					apex, KNOT_RRTYPE_SOA);
-			if (zone_soa == NULL) {
-				return KNOT_ERROR;
-			}
-
+			uint32_t zone_serial = knot_zone_serial(zone->contents);
 			if (knot_serial_compare(
-			      knot_rdata_soa_serial(chgsets->first_soa),
-			      knot_rdata_soa_serial(zone_soa))
+			      knot_rrs_soa_serial(&chgsets->first_soa->rrs),
+			      zone_serial)
 			    > 0) {
 				if ((xfr->flags & XFR_FLAG_UDP) != 0) {
 					// IXFR over UDP
diff --git a/src/knot/nameserver/nsec_proofs.c b/src/knot/nameserver/nsec_proofs.c
index cdf53938a016433b812272c2bf5e33f0401c9217..1fa9e4cd71ad0e0539e94ba0d95c29469c002b1b 100644
--- a/src/knot/nameserver/nsec_proofs.c
+++ b/src/knot/nameserver/nsec_proofs.c
@@ -1,4 +1,3 @@
-
 #include "knot/nameserver/nsec_proofs.h"
 #include "knot/nameserver/process_query.h"
 #include "knot/nameserver/internet.h"
@@ -44,7 +43,7 @@ static knot_dname_t *ns_next_closer(const knot_dname_t *closest_encloser,
 		name = knot_wire_next_label(name, NULL);
 	}
 
-	return knot_dname_copy(name);
+	return knot_dname_copy(name, NULL);
 }
 
 /*----------------------------------------------------------------------------*/
@@ -59,18 +58,15 @@ static int ns_put_nsec3_from_node(const knot_node_t *node,
                                   struct query_data *qdata,
                                   knot_pkt_t *resp)
 {
-	knot_rrset_t *rrset = knot_node_get_rrset(node, KNOT_RRTYPE_NSEC3);
-	const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
-	if (rrset == NULL) {
+	knot_rrset_t rrset = knot_node_rrset(node, KNOT_RRTYPE_NSEC3);
+	knot_rrset_t rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+	if (knot_rrset_empty(&rrset)) {
 		// bad zone, ignore
 		return KNOT_EOK;
 	}
 
-	int res = KNOT_EOK;
-	if (knot_rrset_rr_count(rrset)) {
-		res = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE,
-		                KNOT_PF_CHECKDUP, qdata);
-	}
+	int res = ns_put_rr(resp, &rrset, &rrsigs, COMPR_HINT_NONE,
+	                    KNOT_PF_CHECKDUP, qdata);
 
 	/*! \note TC bit is already set, if something went wrong. */
 
@@ -230,7 +226,7 @@ dbg_ns_exec_verb(
 );
 		ret = ns_put_covering_nsec3(zone, new_next_closer, qdata, resp);
 
-		knot_dname_free(&new_next_closer);
+		knot_dname_free(&new_next_closer, NULL);
 	} else {
 		ret = ns_put_covering_nsec3(zone, next_closer, qdata, resp);
 	}
@@ -301,14 +297,13 @@ static int ns_put_nsec_wildcard(const knot_zone_contents_t *zone,
 		}
 	}
 
-	const knot_rrset_t *rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC);
-	const knot_rrset_t *rrsigs = knot_node_rrset(previous, KNOT_RRTYPE_RRSIG);
-
+	knot_rrset_t rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC);
 	int ret = KNOT_EOK;
 
-	if (rrset != NULL && knot_rrset_rr_count(rrset) > 0) {
+	if (!knot_rrset_empty(&rrset)) {
+		knot_rrset_t rrsigs = knot_node_rrset(previous, KNOT_RRTYPE_RRSIG);
 		// NSEC proving that there is no node with the searched name
-		ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
+		ret = ns_put_rr(resp, &rrset, &rrsigs, COMPR_HINT_NONE, 0, qdata);
 	}
 
 	return ret;
@@ -347,7 +342,7 @@ static int ns_put_nsec3_no_wildcard_child(const knot_zone_contents_t *zone,
 		ret = ns_put_covering_nsec3(zone, wildcard, qdata, resp);
 
 		/* Directly discard wildcard. */
-		knot_dname_free(&wildcard);
+		knot_dname_free(&wildcard, NULL);
 	}
 
 	return ret;
@@ -404,7 +399,7 @@ dbg_ns_exec_verb(
 	int ret = ns_put_covering_nsec3(zone, next_closer, qdata, resp);
 
 	/* Duplicate from ns_next_close(), safe to discard. */
-	knot_dname_free(&next_closer);
+	knot_dname_free(&next_closer, NULL);
 
 	return ret;
 }
@@ -478,8 +473,8 @@ static int ns_put_nsec_nxdomain(const knot_dname_t *qname,
                                 struct query_data *qdata,
                                 knot_pkt_t *resp)
 {
-	knot_rrset_t *rrset = NULL;
-	const knot_rrset_t *rrsigs = NULL;
+	knot_rrset_t rrset = { 0 };
+	knot_rrset_t rrsigs = { 0 };
 
 	// check if we have previous; if not, find one using the tree
 	if (previous == NULL) {
@@ -503,16 +498,15 @@ dbg_ns_exec_verb(
 );
 
 	// 1) NSEC proving that there is no node with the searched name
-	rrset = knot_node_get_rrset(previous, KNOT_RRTYPE_NSEC);
-	rrsigs = knot_node_get_rrset(previous, KNOT_RRTYPE_RRSIG);
-	if (rrset == NULL) {
+	rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC);
+	rrsigs = knot_node_rrset(previous, KNOT_RRTYPE_RRSIG);
+	if (knot_rrset_empty(&rrset)) {
 		// no NSEC records
 		//return NS_ERR_SERVFAIL;
 		return KNOT_EOK;
-
 	}
 
-	int ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
+	int ret = ns_put_rr(resp, &rrset, &rrsigs, COMPR_HINT_NONE, 0, qdata);
 	if (ret != KNOT_EOK) {
 		dbg_ns("Failed to add NSEC for NXDOMAIN to response: %s\n",
 		       knot_strerror(ret));
@@ -553,16 +547,16 @@ dbg_ns_exec_verb(
 );
 
 	/* Directly discard dname. */
-	knot_dname_free(&wildcard);
+	knot_dname_free(&wildcard, NULL);
 
 	if (prev_new != previous) {
-		rrset = knot_node_get_rrset(prev_new, KNOT_RRTYPE_NSEC);
-		rrsigs = knot_node_get_rrset(prev_new, KNOT_RRTYPE_RRSIG);
-		if (rrset == NULL) {
+		rrset = knot_node_rrset(prev_new, KNOT_RRTYPE_NSEC);
+		rrsigs = knot_node_rrset(prev_new, KNOT_RRTYPE_RRSIG);
+		if (knot_rrset_empty(&rrset)) {
 			// bad zone, ignore
 			return KNOT_EOK;
 		}
-		ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
+		ret = ns_put_rr(resp, &rrset, &rrsigs, COMPR_HINT_NONE, 0, qdata);
 		if (ret != KNOT_EOK) {
 			dbg_ns("Failed to add second NSEC for NXDOMAIN to "
 			       "response: %s\n", knot_strerror(ret));
@@ -681,8 +675,6 @@ static int ns_put_nsec_nsec3_nodata(const knot_node_t *node,
 	/*! \todo Maybe distinguish different errors. */
 	int ret = KNOT_ERROR;
 
-	knot_rrset_t *rrset = NULL;
-
 	if (knot_is_nsec3_enabled(zone)) {
 
 		/* RFC5155 7.2.5 Wildcard No Data Responses */
@@ -710,11 +702,11 @@ static int ns_put_nsec_nsec3_nodata(const knot_node_t *node,
 		}
 	} else {
 		dbg_ns("%s: adding NSEC NODATA\n", __func__);
-		if ((rrset = knot_node_get_rrset(node, KNOT_RRTYPE_NSEC))
-		    != NULL) {
+		knot_rrset_t rrset = knot_node_rrset(node, KNOT_RRTYPE_NSEC);
+		if (!knot_rrset_empty(&rrset)) {
 			dbg_ns_detail("Putting the RRSet to Authority\n");
-			const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
-			ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
+			knot_rrset_t rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+			ret = ns_put_rr(resp, &rrset, &rrsigs, COMPR_HINT_NONE, 0, qdata);
 		}
 	}
 
@@ -766,10 +758,10 @@ int nsec_prove_dp_security(knot_pkt_t *pkt, struct query_data *qdata)
 	dbg_ns("%s(%p, %p)\n", __func__, pkt, qdata);
 
 	/* Add DS record if present. */
-	knot_rrset_t *rrset = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_DS);
-	if (rrset != NULL) {
-		const knot_rrset_t *rrsigs = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
-		return ns_put_rr(pkt, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata);
+	knot_rrset_t rrset = knot_node_rrset(qdata->node, KNOT_RRTYPE_DS);
+	if (!knot_rrset_empty(&rrset)) {
+		knot_rrset_t rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG);
+		return ns_put_rr(pkt, &rrset, &rrsigs, COMPR_HINT_NONE, 0, qdata);
 	}
 
 	/* DS doesn't exist => NODATA proof. */
@@ -791,13 +783,14 @@ int nsec_append_rrsigs(knot_pkt_t *pkt, struct query_data *qdata, bool optional)
 	/* Append RRSIGs for section. */
 	struct rrsig_info *info = NULL;
 	WALK_LIST(info, qdata->rrsigs) {
-		knot_rrset_t *rrsig = info->synth_rrsig;
+		knot_rrset_t *rrsig = &info->synth_rrsig;
 		uint16_t compr_hint = info->rrinfo->compress_ptr[COMPR_HINT_OWNER];
 		ret = knot_pkt_put(pkt, compr_hint, rrsig, flags);
 		if (ret != KNOT_EOK) {
 			break;
 		}
-		info->synth_rrsig = NULL; /* RRSIG is owned by packet now. */
+		/* RRSIG is owned by packet now. */
+		knot_rrs_init(&info->synth_rrsig.rrs);
 	};
 
 	/* Clear the list. */
@@ -814,8 +807,8 @@ void nsec_clear_rrsigs(struct query_data *qdata)
 
 	struct rrsig_info *info = NULL;
 	WALK_LIST(info, qdata->rrsigs) {
-		knot_rrset_t *rrsig = info->synth_rrsig;
-		knot_rrset_free(&rrsig, qdata->mm);
+		knot_rrset_t *rrsig = &info->synth_rrsig;
+		knot_rrset_clear(rrsig, qdata->mm);
 	};
 
 	ptrlist_free(&qdata->rrsigs, qdata->mm);
diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c
index a462028416c1697f214dbc9b22a9c0760c0ec759..f7592f6491c732c7a35656bcca13bff74270cc00 100644
--- a/src/knot/nameserver/process_query.c
+++ b/src/knot/nameserver/process_query.c
@@ -232,7 +232,7 @@ bool process_query_acl_check(acl_t *acl, struct query_data *qdata)
 
 	/* Authenticate with NOKEY if the packet isn't signed. */
 	if (query->tsig_rr) {
-		key_name = knot_rrset_owner(query->tsig_rr);
+		key_name = query->tsig_rr->owner;
 		key_alg = tsig_rdata_alg(query->tsig_rr);
 	}
 	acl_match_t *match = acl_find(acl, query_source, key_name);
diff --git a/src/knot/nameserver/process_query.h b/src/knot/nameserver/process_query.h
index 7868d5e680a98ef786851fd3d9946e665864e2d2..eadbc664835f904fc162627567feb8627a957d49 100644
--- a/src/knot/nameserver/process_query.h
+++ b/src/knot/nameserver/process_query.h
@@ -108,7 +108,7 @@ struct wildcard_hit {
 /*! \brief RRSIG info node list. */
 struct rrsig_info {
 	node_t n;
-	knot_rrset_t *synth_rrsig;  /* Synthesized RRSIG. */
+	knot_rrset_t synth_rrsig;  /* Synthesized RRSIG. */
 	knot_rrinfo_t *rrinfo;      /* RR info. */
 };
 
diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index afa4e8199c82741a60c4bce0ae70b4328a59887d..43698da6ed474213b298e3a93d562db82d32af79 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -1,4 +1,3 @@
-
 #include "knot/nameserver/update.h"
 #include "knot/nameserver/internet.h"
 #include "knot/nameserver/process_query.h"
@@ -84,21 +83,8 @@ static int update_prereq_check(struct query_data *qdata)
 {
 	knot_pkt_t *query = qdata->query;
 	const knot_zone_contents_t *contents = qdata->zone->contents;
-
-	/*
-	 * 2) DDNS Prerequisities Section processing (RFC2136, Section 3.2).
-	 *
-	 * \note Permissions section means probably policies and fine grained
-	 *       access control, not transaction security.
-	 */
-	knot_ddns_prereq_t *prereqs = NULL;
-	int ret = knot_ddns_process_prereqs(query, &prereqs, &qdata->rcode);
-	if (ret == KNOT_EOK) {
-		ret = knot_ddns_check_prereqs(contents, &prereqs, &qdata->rcode);
-		knot_ddns_prereqs_free(&prereqs);
-	}
-
-	return ret;
+	// DDNS Prerequisities Section processing (RFC2136, Section 3.2).
+	return knot_ddns_process_prereqs(query, contents, &qdata->rcode);
 }
 
 static int update_process(knot_pkt_t *resp, struct query_data *qdata)
@@ -186,71 +172,24 @@ int update_answer(knot_pkt_t *pkt, struct query_data *qdata)
 	}
 }
 
-/*
- * This function should:
- * 1) Create zone shallow copy and the changes structure.
- * 2) Call knot_ddns_process_update().
- *    - If something went bad, call xfrin_rollback_update() and return an error.
- *    - If everything went OK, continue.
- * 3) Finalize the updated zone.
- *
- * NOTE: Mostly copied from xfrin_apply_changesets(). Should be refactored in
- *       order to get rid of duplicate code.
- */
 static int knot_ns_process_update(const knot_pkt_t *query,
-                                  knot_zone_contents_t *old_contents,
-                                  knot_zone_contents_t **new_contents,
+                                  zone_t *old_zone,
                                   knot_changesets_t *chgs, uint16_t *rcode,
                                   uint32_t new_serial)
 {
-	if (query == NULL || old_contents == NULL || chgs == NULL ||
-	    EMPTY_LIST(chgs->sets) || new_contents == NULL || rcode == NULL) {
+	if (query == NULL || old_zone == NULL || chgs == NULL ||
+	    EMPTY_LIST(chgs->sets) || rcode == NULL) {
 		return KNOT_EINVAL;
 	}
 
 	dbg_ns("Applying UPDATE to zone...\n");
 
-	// 1) Create zone shallow copy.
-	dbg_ns_verb("Creating shallow copy of the zone...\n");
-	knot_zone_contents_t *contents_copy = NULL;
-	int ret = xfrin_prepare_zone_copy(old_contents, &contents_copy);
-	if (ret != KNOT_EOK) {
-		dbg_ns("Failed to prepare zone copy: %s\n",
-		          knot_strerror(ret));
-		*rcode = KNOT_RCODE_SERVFAIL;
-		return ret;
-	}
-
-	// 2) Apply the UPDATE and create changesets.
+	// Create changesets from UPDATE
 	dbg_ns_verb("Applying the UPDATE and creating changeset...\n");
-	ret = knot_ddns_process_update(contents_copy, query,
-	                               knot_changesets_get_last(chgs),
-	                               chgs->changes, rcode, new_serial);
-	if (ret != KNOT_EOK) {
-		dbg_ns("Failed to apply UPDATE to the zone copy or no update"
-		       " made: %s\n", (ret < 0) ? knot_strerror(ret)
-		                                : "No change made.");
-		xfrin_rollback_update(old_contents, &contents_copy,
-		                      chgs->changes);
-		return ret;
-	}
-
-	// 3) Finalize zone
-	dbg_ns_verb("Finalizing updated zone...\n");
-	ret = xfrin_finalize_updated_zone(contents_copy, false);
-	if (ret != KNOT_EOK) {
-		dbg_ns("Failed to finalize updated zone: %s\n",
-		       knot_strerror(ret));
-		xfrin_rollback_update(old_contents, &contents_copy,
-		                      chgs->changes);
-		*rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR
-		                             : KNOT_RCODE_SERVFAIL;
-		return ret;
-	}
-
-	*new_contents = contents_copy;
-
-	return KNOT_EOK;
+	int ret = knot_ddns_process_update(old_zone->contents, query,
+	                                   knot_changesets_get_last(chgs),
+	                                   rcode, new_serial);
+	return ret;
 }
 
 static int replan_zone_sign_after_ddns(zone_t *zone, uint32_t refresh_at)
@@ -311,7 +250,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 		return ret;
 	}
 
-	// Process the UPDATE packet, apply to zone, create changesets.
+	// Process the UPDATE packet, create changesets.
 	if (knot_changesets_create_changeset(chgsets) == NULL) {
 		knot_changesets_free(&chgsets);
 		free(msg);
@@ -321,21 +260,36 @@ static int zones_process_update_auth(struct query_data *qdata)
 
 	uint32_t new_serial = zones_next_serial(zone);
 
-	knot_zone_contents_t *new_contents = NULL;
 	knot_zone_contents_t *old_contents = zone->contents;
-	ret = knot_ns_process_update(qdata->query, old_contents, &new_contents,
-	                             chgsets, &qdata->rcode, new_serial);
+	ret = knot_ns_process_update(qdata->query, zone, chgsets, &qdata->rcode, new_serial);
 	if (ret != KNOT_EOK) {
-		if (ret > 0) {
-			log_zone_notice("%s: No change to zone made.\n", msg);
-			qdata->rcode = KNOT_RCODE_NOERROR;
-		}
+		knot_changesets_free(&chgsets);
+		free(msg);
+		return ret;
+	}
 
+	knot_zone_contents_t *new_contents = NULL;
+	const bool change_made =
+		!knot_changeset_is_empty(knot_changesets_get_last(chgsets));
+	if (!change_made) {
+		log_zone_notice("%s: No change to zone made.\n", msg);
+		qdata->rcode = KNOT_RCODE_NOERROR;
 		knot_changesets_free(&chgsets);
 		free(msg);
-		return (ret < 0) ? ret : KNOT_EOK;
+		return KNOT_EOK;
+	} else {
+		ret = xfrin_apply_changesets(zone, chgsets, &new_contents);
+		if (ret != KNOT_EOK) {
+			log_zone_notice("%s: Failed to process: %s.\n", msg, knot_strerror(ret));
+			qdata->rcode = KNOT_RCODE_SERVFAIL;
+			knot_changesets_free(&chgsets);
+			free(msg);
+			return ret;
+		}
 	}
 
+	assert(new_contents);
+
 	knot_changesets_t *sec_chs = NULL;
 	knot_changeset_t *sec_ch = NULL;
 	uint32_t refresh_at = 0;
@@ -344,8 +298,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 		sec_chs = knot_changesets_create();
 		sec_ch = knot_changesets_create_changeset(sec_chs);
 		if (sec_chs == NULL || sec_ch == NULL) {
-			xfrin_rollback_update(old_contents, &new_contents,
-			                      chgsets->changes);
+			xfrin_rollback_update(chgsets, &new_contents);
 			knot_changesets_free(&chgsets);
 			free(msg);
 			return KNOT_ENOMEM;
@@ -368,17 +321,15 @@ static int zones_process_update_auth(struct query_data *qdata)
 		} else {
 			// Sign the created changeset
 			ret = knot_dnssec_sign_changeset(new_contents, zone_config,
-			                      knot_changesets_get_last(chgsets),
-			                      sec_ch, KNOT_SOA_SERIAL_KEEP,
-			                      &refresh_at,
-			                      new_serial);
+			                                 knot_changesets_get_last(chgsets),
+			                                 sec_ch, KNOT_SOA_SERIAL_KEEP,
+			                                 &refresh_at, new_serial);
 		}
 
 		if (ret != KNOT_EOK) {
 			log_zone_error("%s: Failed to sign incoming update (%s)"
 			               "\n", msg, knot_strerror(ret));
-			xfrin_rollback_update(old_contents, &new_contents,
-					      chgsets->changes);
+			xfrin_rollback_update(chgsets, &new_contents);
 			knot_changesets_free(&chgsets);
 			knot_changesets_free(&sec_chs);
 			free(msg);
@@ -393,8 +344,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 	if (ret != KNOT_EOK) {
 		log_zone_error("%s: Failed to save new entry to journal (%s)\n",
 		               msg, knot_strerror(ret));
-		xfrin_rollback_update(old_contents, &new_contents,
-		                      chgsets->changes);
+		xfrin_rollback_update(chgsets, &new_contents);
 		zones_free_merged_changesets(chgsets, sec_chs);
 		free(msg);
 		return ret;
@@ -403,15 +353,18 @@ static int zones_process_update_auth(struct query_data *qdata)
 	bool new_signatures = !knot_changeset_is_empty(sec_ch);
 	// Apply DNSSEC changeset
 	if (new_signatures) {
-		ret = xfrin_apply_changesets_dnssec_ddns(old_contents,
-		                                    new_contents,
-		                                    sec_chs,
-		                                    chgsets);
+		ret = xfrin_apply_changesets_dnssec_ddns(zone,
+		                                         new_contents,
+		                                         sec_chs,
+		                                         chgsets);
 		if (ret != KNOT_EOK) {
 			log_zone_error("%s: Failed to sign incoming update (%s)"
 			               "\n", msg, knot_strerror(ret));
 			zones_store_changesets_rollback(transaction);
+			xfrin_rollback_update(chgsets, &new_contents);
+			xfrin_rollback_update(sec_chs, &new_contents);
 			zones_free_merged_changesets(chgsets, sec_chs);
+			free(msg);
 			return ret;
 		}
 
@@ -422,7 +375,10 @@ static int zones_process_update_auth(struct query_data *qdata)
 			log_zone_error("%s: Failed to replan zone sign (%s)\n",
 			               msg, knot_strerror(ret));
 			zones_store_changesets_rollback(transaction);
+			xfrin_rollback_update(chgsets, &new_contents);
+			xfrin_rollback_update(sec_chs, &new_contents);
 			zones_free_merged_changesets(chgsets, sec_chs);
+			free(msg);
 			return ret;
 		}
 	} else {
@@ -430,9 +386,8 @@ static int zones_process_update_auth(struct query_data *qdata)
 		ret = knot_zone_contents_adjust_nsec3_pointers(new_contents);
 		if (ret != KNOT_EOK) {
 			zones_store_changesets_rollback(transaction);
+			xfrin_rollback_update(chgsets, &new_contents);
 			zones_free_merged_changesets(chgsets, sec_chs);
-			xfrin_rollback_update(old_contents, &new_contents,
-			                      chgsets->changes);
 			free(msg);
 			return ret;
 		}
@@ -444,8 +399,8 @@ static int zones_process_update_auth(struct query_data *qdata)
 		if (ret != KNOT_EOK) {
 			log_zone_error("%s: Failed to commit new journal entry "
 			               "(%s).\n", msg, knot_strerror(ret));
-			xfrin_rollback_update(old_contents, &new_contents,
-			                      chgsets->changes);
+			xfrin_rollback_update(chgsets, &new_contents);
+			xfrin_rollback_update(sec_chs, &new_contents);
 			zones_free_merged_changesets(chgsets, sec_chs);
 			free(msg);
 			return ret;
@@ -458,8 +413,8 @@ static int zones_process_update_auth(struct query_data *qdata)
 		log_zone_error("%s: Failed to replace current zone (%s)\n",
 		               msg, knot_strerror(ret));
 		// Cleanup old and new contents
-		xfrin_rollback_update(old_contents, &new_contents,
-		                      chgsets->changes);
+		xfrin_rollback_update(chgsets, &new_contents);
+		xfrin_rollback_update(sec_chs, &new_contents);
 
 		/* Free changesets, but not the data. */
 		zones_free_merged_changesets(chgsets, sec_chs);
@@ -467,10 +422,8 @@ static int zones_process_update_auth(struct query_data *qdata)
 	}
 
 	// Cleanup.
-	xfrin_cleanup_successful_update(chgsets->changes);
-	if (sec_chs) {
-		xfrin_cleanup_successful_update(sec_chs->changes);
-	}
+	xfrin_cleanup_successful_update(chgsets);
+	xfrin_cleanup_successful_update(sec_chs);
 
 	// Free changesets, but not the data.
 	zones_free_merged_changesets(chgsets, sec_chs);
diff --git a/src/knot/server/notify.c b/src/knot/server/notify.c
index 1102404428ab623edbc8ec1835db04a26699c051..5efc59c9debc5c48b7c06102cc3e217733d103ef 100644
--- a/src/knot/server/notify.c
+++ b/src/knot/server/notify.c
@@ -51,8 +51,9 @@ int notify_create_request(const zone_t *zone, knot_pkt_t *pkt)
 	knot_wire_set_aa(pkt->wire);
 	knot_wire_set_opcode(pkt->wire, KNOT_OPCODE_NOTIFY);
 
-	const knot_rrset_t *soa_rr = knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA);
-	return knot_pkt_put_question(pkt, soa_rr->owner, soa_rr->rclass, soa_rr->type);
+	knot_rrset_t soa_rr = knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA);
+	assert(!knot_rrset_empty(&soa_rr));
+	return knot_pkt_put_question(pkt, soa_rr.owner, soa_rr.rclass, soa_rr.type);
 }
 
 int notify_process_response(knot_pkt_t *notify, int msgid)
@@ -93,9 +94,9 @@ int internet_notify(knot_pkt_t *pkt, struct query_data *qdata)
 	unsigned serial = 0;
 	const knot_pktsection_t *answer = knot_pkt_section(qdata->query, KNOT_ANSWER);
 	if (answer->count > 0) {
-		const knot_rrset_t *soa = answer->rr[0];
-		if (knot_rrset_type(soa) == KNOT_RRTYPE_SOA) {
-			serial = knot_rdata_soa_serial(soa);
+		const knot_rrset_t *soa = &answer->rr[0];
+		if (soa->type == KNOT_RRTYPE_SOA) {
+			serial = knot_rrs_soa_serial(&soa->rrs);
 			dbg_ns("%s: received serial %u\n", __func__, serial);
 		} else { /* Ignore */
 			dbg_ns("%s: NOTIFY answer != SOA_RR\n", __func__);
diff --git a/src/knot/server/serialization.c b/src/knot/server/serialization.c
new file mode 100644
index 0000000000000000000000000000000000000000..097b0eff07176d07a72b22f0c04c64b2b9d14908
--- /dev/null
+++ b/src/knot/server/serialization.c
@@ -0,0 +1,196 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+
+#include "knot/server/serialization.h"
+#include "common/errcode.h"
+
+static size_t rr_binary_size(const knot_rrset_t *rrset, size_t rdata_pos)
+{
+	const knot_rr_t *rr = knot_rrs_rr(&rrset->rrs, rdata_pos);
+	if (rr) {
+		// RR size + TTL
+		return knot_rr_rdata_size(rr) + sizeof(uint32_t);
+	} else {
+		return 0;
+	}
+}
+
+static uint64_t rrset_binary_size(const knot_rrset_t *rrset)
+{
+	if (rrset == NULL || knot_rrset_rr_count(rrset) == 0) {
+		return 0;
+	}
+	uint64_t size = sizeof(uint64_t) + // size at the beginning
+	              knot_dname_size(rrset->owner) + // owner data
+	              sizeof(uint16_t) + // type
+	              sizeof(uint16_t) + // class
+	              sizeof(uint16_t);  //RR count
+	uint16_t rdata_count = knot_rrset_rr_count(rrset);
+	for (uint16_t i = 0; i < rdata_count; i++) {
+		/* Space to store length of one RR. */
+		size += sizeof(uint32_t);
+		/* Actual data. */
+		size += rr_binary_size(rrset, i);
+	}
+
+	return size;
+}
+
+static void serialize_rr(const knot_rrset_t *rrset, size_t rdata_pos,
+                         uint8_t *stream)
+{
+	const knot_rr_t *rr = knot_rrs_rr(&rrset->rrs, rdata_pos);
+	assert(rr);
+	uint32_t ttl = knot_rr_ttl(rr);
+	memcpy(stream, &ttl, sizeof(uint32_t));
+	memcpy(stream + sizeof(uint32_t), knot_rr_rdata(rr), knot_rr_rdata_size(rr));
+}
+
+static int deserialize_rr(knot_rrset_t *rrset, const uint8_t *stream, uint32_t rdata_size)
+{
+	uint32_t ttl;
+	memcpy(&ttl, stream, sizeof(uint32_t));
+	return knot_rrset_add_rr(rrset, stream + sizeof(uint32_t),
+	                         rdata_size - sizeof(uint32_t), ttl, NULL);
+}
+
+int changeset_binary_size(const knot_changeset_t *chgset, size_t *size)
+{
+	if (chgset == NULL || size == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	size_t soa_from_size = rrset_binary_size(chgset->soa_from);
+	size_t soa_to_size = rrset_binary_size(chgset->soa_to);
+
+	size_t remove_size = 0;
+	knot_rr_ln_t *rr_node = NULL;
+	WALK_LIST(rr_node, chgset->remove) {
+		knot_rrset_t *rrset = rr_node->rr;
+		remove_size += rrset_binary_size(rrset);
+	}
+
+	size_t add_size = 0;
+	WALK_LIST(rr_node, chgset->add) {
+		knot_rrset_t *rrset = rr_node->rr;
+		add_size += rrset_binary_size(rrset);
+	}
+
+	*size = soa_from_size + soa_to_size + remove_size + add_size;
+	/* + Changeset flags. */
+	*size += sizeof(uint32_t);
+
+	return KNOT_EOK;
+}
+
+int rrset_serialize(const knot_rrset_t *rrset, uint8_t *stream, size_t *size)
+{
+	if (rrset == NULL || rrset->rrs.data == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	uint64_t rrset_length = rrset_binary_size(rrset);
+	memcpy(stream, &rrset_length, sizeof(uint64_t));
+
+	size_t offset = sizeof(uint64_t);
+	/* Save RR count. */
+	const uint16_t rr_count = knot_rrset_rr_count(rrset);
+	memcpy(stream + offset, &rr_count, sizeof(uint16_t));
+	offset += sizeof(uint16_t);
+	/* Save owner. */
+	offset += knot_dname_to_wire(stream + offset, rrset->owner, rrset_length - offset);
+
+	/* Save static data. */
+	memcpy(stream + offset, &rrset->type, sizeof(uint16_t));
+	offset += sizeof(uint16_t);
+	memcpy(stream + offset, &rrset->rclass, sizeof(uint16_t));
+	offset += sizeof(uint16_t);
+
+	/* Copy RDATA. */
+	for (uint16_t i = 0; i < rr_count; i++) {
+		uint32_t knot_rr_size = rr_binary_size(rrset, i);
+		memcpy(stream + offset, &knot_rr_size, sizeof(uint32_t));
+		offset += sizeof(uint32_t);
+		serialize_rr(rrset, i, stream + offset);
+		offset += knot_rr_size;
+	}
+
+	*size = offset;
+	assert(*size == rrset_length);
+	return KNOT_EOK;
+}
+
+int rrset_deserialize(const uint8_t *stream, size_t *stream_size,
+                      knot_rrset_t **rrset)
+{
+	if (sizeof(uint64_t) > *stream_size) {
+		return KNOT_ESPACE;
+	}
+	uint64_t rrset_length = 0;
+	memcpy(&rrset_length, stream, sizeof(uint64_t));
+	if (rrset_length > *stream_size) {
+		return KNOT_ESPACE;
+	}
+
+	size_t offset = sizeof(uint64_t);
+	uint16_t rdata_count = 0;
+	memcpy(&rdata_count, stream + offset, sizeof(uint16_t));
+	offset += sizeof(uint16_t);
+	/* Read owner from the stream. */
+	unsigned owner_size = knot_dname_size(stream + offset);
+	knot_dname_t *owner = knot_dname_copy_part(stream + offset, owner_size, NULL);
+	assert(owner);
+	offset += owner_size;
+	/* Read type. */
+	uint16_t type = 0;
+	memcpy(&type, stream + offset, sizeof(uint16_t));
+	offset += sizeof(uint16_t);
+	/* Read class. */
+	uint16_t rclass = 0;
+	memcpy(&rclass, stream + offset, sizeof(uint16_t));
+	offset += sizeof(uint16_t);
+
+	/* Create new RRSet. */
+	*rrset = knot_rrset_new(owner, type, rclass, NULL);
+	knot_dname_free(&owner, NULL);
+	if (*rrset == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	/* Read RRs. */
+	for (uint16_t i = 0; i < rdata_count; i++) {
+		/*
+		 * There's always size of rdata in the beginning.
+		 * Needed because of remainders.
+		 */
+		uint32_t rdata_size = 0;
+		memcpy(&rdata_size, stream + offset, sizeof(uint32_t));
+		offset += sizeof(uint32_t);
+		int ret = deserialize_rr((*rrset), stream + offset, rdata_size);
+		if (ret != KNOT_EOK) {
+			knot_rrset_free(rrset, NULL);
+			return ret;
+		}
+		offset += rdata_size;
+	}
+
+	*stream_size = *stream_size - offset;
+
+	return KNOT_EOK;
+}
+
diff --git a/src/knot/server/serialization.h b/src/knot/server/serialization.h
new file mode 100644
index 0000000000000000000000000000000000000000..4627d60697e27876abd691a9876d24b25412f86e
--- /dev/null
+++ b/src/knot/server/serialization.h
@@ -0,0 +1,65 @@
+/*!
+ * \file rr.h
+ *
+ * \author Jan Kadlec <jan.kadlec@nic.cz>
+ *
+ * \brief API for changeset serialization.
+ *
+ * \addtogroup server
+ * @{
+ */
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include "libknot/rrset.h"
+#include "knot/updates/changesets.h"
+
+/*!
+ * \brief Returns size of changeset in serialized form.
+ *
+ * \param chgset  Changeset whose size we want to compute.
+ * \param size    Output size parameter.
+ *
+ * \return KNOT_E*
+ */
+int changeset_binary_size(const knot_changeset_t *chgset, size_t *size);
+
+/*!
+ * \brief Serializes one RRSet into given stream.
+ *
+ * \param rrset   RRSet to be serialized.
+ * \param stream  Stream to store RRSet into.
+ * \param size    Output size of serialized RRSet in the stream.
+ *
+ * \return KNOT_E*
+ */
+int rrset_serialize(const knot_rrset_t *rrset, uint8_t *stream, size_t *size);
+
+/*!
+ * \brief Deserializes RRSet from given stream.
+ *
+ * \param stream       Stream containing serialized RRSet.
+ * \param stream_size  Output stream size after RRSet has been deserialized.
+ * \param rrset        Output deserialized rrset.
+ *
+ * \return KNOT_E*
+ */
+int rrset_deserialize(const uint8_t *stream, size_t *stream_size,
+                      knot_rrset_t **rrset);
+
diff --git a/src/knot/server/zone-load.c b/src/knot/server/zone-load.c
index 226df65026ff5d8239a5598ba565e7eb0a0539b8..6edf5f32284f91277a78b74131e93665f0979521 100644
--- a/src/knot/server/zone-load.c
+++ b/src/knot/server/zone-load.c
@@ -203,9 +203,9 @@ static void log_zone_load_info(const zone_t *zone, const char *zone_name,
 
 	int64_t serial = 0;
 	if (zone->contents && zone->contents->apex) {
-		const knot_rrset_t *soa;
-		soa = knot_node_rrset(zone->contents->apex, KNOT_RRTYPE_SOA);
-		serial = knot_rdata_soa_serial(soa);
+		const knot_rrs_t *soa = knot_node_rrs(zone->contents->apex,
+		                                      KNOT_RRTYPE_SOA);
+		serial = knot_rrs_soa_serial(soa);
 	}
 
 	log_zone_info("Zone '%s' %s (serial %" PRId64 ")\n", zone_name, action, serial);
@@ -386,7 +386,7 @@ static int zone_loader_thread(dthread_t *thread)
 			return KNOT_ENOMEM;
 		}
 		zone_t *old_zone = knot_zonedb_find(ctx->db_old, apex);
-		knot_dname_free(&apex);
+		knot_dname_free(&apex, NULL);
 
 		/* Update the zone. */
 		zone = update_zone(old_zone, zone_config, ctx->server);
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index 3ae97a2d6027c4b9c5a02da0edc28b9069fc8bca..bd3538966f09d52ebc93d33ab19b62c1add7ea1f 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -27,6 +27,7 @@
 #include "knot/server/xfr-handler.h"
 #include "knot/server/zone-load.h"
 #include "knot/server/zones.h"
+#include "knot/server/serialization.h"
 #include "knot/zone/zone-dump.h"
 #include "libknot/dname.h"
 #include "libknot/dnssec/random.h"
@@ -64,7 +65,7 @@ static uint32_t zones_jitter(uint32_t interval)
  * \param rr_func RDATA specificator.
  * \return Timer in miliseconds.
  */
-static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrset_t*))
+static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrs_t*))
 {
 	if (!zone) {
 		dbg_zones_verb("zones: zones_soa_timer() called "
@@ -75,7 +76,7 @@ static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrs
 	uint32_t ret = 0;
 
 	/* Retrieve SOA RDATA. */
-	const knot_rrset_t *soa_rrs = 0;
+	const knot_rrs_t *soa_rrs = NULL;
 
 	rcu_read_lock();
 
@@ -85,7 +86,7 @@ static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrs
 		return 0;
 	}
 
-	soa_rrs = knot_node_rrset(zc->apex, KNOT_RRTYPE_SOA);
+	soa_rrs = knot_node_rrs(zc->apex, KNOT_RRTYPE_SOA);
 	assert(soa_rrs != NULL);
 	ret = rr_func(soa_rrs);
 
@@ -103,7 +104,7 @@ static uint32_t zones_soa_timer(zone_t *zone, uint32_t (*rr_func)(const knot_rrs
  */
 static uint32_t zones_soa_refresh(zone_t *zone)
 {
-	return zones_soa_timer(zone, knot_rdata_soa_refresh);
+	return zones_soa_timer(zone, knot_rrs_soa_refresh);
 }
 
 /*!
@@ -114,7 +115,7 @@ static uint32_t zones_soa_refresh(zone_t *zone)
  */
 static uint32_t zones_soa_retry(zone_t *zone)
 {
-	return zones_soa_timer(zone, knot_rdata_soa_retry);
+	return zones_soa_timer(zone, knot_rrs_soa_retry);
 }
 
 /*!
@@ -125,7 +126,7 @@ static uint32_t zones_soa_retry(zone_t *zone)
  */
 static uint32_t zones_soa_expire(zone_t *zone)
 {
-	return zones_soa_timer(zone, knot_rdata_soa_expire);
+	return zones_soa_timer(zone, knot_rrs_soa_expire);
 }
 
 /*!
@@ -506,9 +507,9 @@ int zones_changesets_from_binary(knot_changesets_t *chgsets)
 		 * be set, check it.
 		 */
 		dbg_xfr_verb("xfr: reading RRSets to REMOVE, first RR is %hu\n",
-		             knot_rrset_type(rrset));
-		assert(knot_rrset_type(rrset) == KNOT_RRTYPE_SOA);
-		assert(chs->serial_from == knot_rdata_soa_serial(rrset));
+		             rrset->type);
+		assert(rrset->type == KNOT_RRTYPE_SOA);
+		assert(chs->serial_from == knot_rrs_soa_serial(&rrset->rrs));
 		knot_changeset_add_soa(chs, rrset, KNOT_CHANGESET_REMOVE);
 
 		/* Read remaining RRSets */
@@ -527,7 +528,7 @@ int zones_changesets_from_binary(knot_changesets_t *chgsets)
 			}
 
 			/* Check for next SOA. */
-			if (knot_rrset_type(rrset) == KNOT_RRTYPE_SOA) {
+			if (rrset->type == KNOT_RRTYPE_SOA) {
 
 				/* Move to ADD section if in REMOVE. */
 				if (in_remove_section) {
@@ -710,14 +711,8 @@ void zones_free_merged_changesets(knot_changesets_t *diff_chs,
 			knot_changesets_free(&sec_chs);
 			knot_changesets_free(&diff_chs);
 		} else {
-			/*!
-			 * Ending SOA from the merged changeset was used in
-			 * zone (same as in DNSSEC changeset). It must not
-			 * be freed.
-			 */
 			assert(knot_changesets_get_last(diff_chs)->serial_to ==
 			       knot_changesets_get_last(sec_chs)->serial_to);
-			knot_changesets_get_last(diff_chs)->soa_to = NULL;
 			knot_changesets_free(&diff_chs);
 
 			/*!
@@ -831,14 +826,14 @@ static bool apex_rr_changed(const knot_zone_contents_t *old_contents,
                             const knot_zone_contents_t *new_contents,
                             uint16_t type)
 {
-	const knot_rrset_t *old_rr = knot_node_rrset(old_contents->apex, type);
-	const knot_rrset_t *new_rr = knot_node_rrset(new_contents->apex, type);
-	if (old_rr== NULL) {
-		return new_rr != NULL;
-	} else if (new_rr == NULL) {
-		return old_rr != NULL;
-	}
-	return !knot_rrset_equal(old_rr, new_rr, KNOT_RRSET_COMPARE_WHOLE);
+	knot_rrset_t old_rr = knot_node_rrset(old_contents->apex, type);
+	knot_rrset_t new_rr = knot_node_rrset(new_contents->apex, type);
+	if (knot_rrset_empty(&old_rr)) {
+		return !knot_rrset_empty(&new_rr);
+	} else if (knot_rrset_empty(&new_rr)) {
+		return !knot_rrset_empty(&old_rr);
+	}
+	return !knot_rrset_equal(&old_rr, &new_rr, KNOT_RRSET_COMPARE_WHOLE);
 }
 
 bool zones_dnskey_changed(const knot_zone_contents_t *old_contents,
@@ -853,8 +848,6 @@ bool zones_nsec3param_changed(const knot_zone_contents_t *old_contents,
 	return apex_rr_changed(old_contents, new_contents, KNOT_RRTYPE_NSEC3PARAM);
 }
 
-
-
 /*----------------------------------------------------------------------------*/
 
 static int zones_open_free_filename(const char *old_name, char **new_name)
@@ -958,11 +951,11 @@ int zones_zonefile_sync(zone_t *zone, journal_t *journal)
 	}
 
 	/* Latest zone serial. */
-	const knot_rrset_t *soa_rrs = 0;
-	soa_rrs = knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA);
+	const knot_rrs_t *soa_rrs = knot_node_rrs(contents->apex,
+	                                          KNOT_RRTYPE_SOA);
 	assert(soa_rrs != NULL);
 
-	int64_t serial_ret = knot_rdata_soa_serial(soa_rrs);
+	int64_t serial_ret = knot_rrs_soa_serial(soa_rrs);
 	if (serial_ret < 0) {
 		rcu_read_unlock();
 		pthread_mutex_unlock(&zone->lock);
@@ -1161,7 +1154,7 @@ int zones_save_zone(const knot_ns_xfr_t *xfr)
 	const knot_dname_t *new_name = NULL;
 	new_name = knot_node_owner(knot_zone_contents_apex(new_zone));
 	int r = knot_dname_cmp(cur_name, new_name);
-	knot_dname_free(&cur_name);
+	knot_dname_free(&cur_name, NULL);
 	if (r != 0) {
 		rcu_read_unlock();
 		return KNOT_EINVAL;
@@ -1175,40 +1168,6 @@ int zones_save_zone(const knot_ns_xfr_t *xfr)
 	return ret;
 }
 
-/*----------------------------------------------------------------------------*/
-/* Counting size of changeset in serialized form.                             */
-/*----------------------------------------------------------------------------*/
-
-int zones_changeset_binary_size(const knot_changeset_t *chgset, size_t *size)
-{
-	if (chgset == NULL || size == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	size_t soa_from_size = rrset_binary_size(chgset->soa_from);
-	size_t soa_to_size = rrset_binary_size(chgset->soa_to);
-
-	size_t remove_size = 0;
-	knot_rr_ln_t *rr_node = NULL;
-	WALK_LIST(rr_node, chgset->remove) {
-		knot_rrset_t *rrset = rr_node->rr;
-		remove_size += rrset_binary_size(rrset);
-	}
-
-	size_t add_size = 0;
-	WALK_LIST(rr_node, chgset->add) {
-		knot_rrset_t *rrset = rr_node->rr;
-		add_size += rrset_binary_size(rrset);
-	}
-
-	/*! \todo How is the changeset serialized? Any other parts? */
-	*size = soa_from_size + soa_to_size + remove_size + add_size;
-	/* + Changeset flags. */
-	*size += sizeof(uint32_t);
-
-	return KNOT_EOK;
-}
-
 /*----------------------------------------------------------------------------*/
 /* Changeset serialization and storing (new)                                  */
 /*----------------------------------------------------------------------------*/
@@ -1239,7 +1198,7 @@ static int zones_serialize_and_store_chgset(const knot_changeset_t *chs,
 	int ret = zones_rrset_write_to_mem(chs->soa_from, &entry, &max_size);
 	if (ret != KNOT_EOK) {
 		dbg_zones("%s:%d ret = %s\n", __func__, __LINE__, knot_strerror(ret));
-		return KNOT_ERROR;  /*! \todo Other code? */
+		return ret;
 	}
 
 	/* Serialize RRSets from the 'remove' section. */
@@ -1249,7 +1208,7 @@ static int zones_serialize_and_store_chgset(const knot_changeset_t *chs,
 		ret = zones_rrset_write_to_mem(rrset, &entry, &max_size);
 		if (ret != KNOT_EOK) {
 			dbg_zones("%s:%d ret = %s\n", __func__, __LINE__, knot_strerror(ret));
-			return KNOT_ERROR;  /*! \todo Other code? */
+			return ret;
 		}
 	}
 
@@ -1257,7 +1216,7 @@ static int zones_serialize_and_store_chgset(const knot_changeset_t *chs,
 	ret = zones_rrset_write_to_mem(chs->soa_to, &entry, &max_size);
 	if (ret != KNOT_EOK) {
 		dbg_zones("%s:%d ret = %s\n", __func__, __LINE__, knot_strerror(ret));
-		return KNOT_ERROR;  /*! \todo Other code? */
+		return ret;
 	}
 
 	/* Serialize RRSets from the 'add' section. */
@@ -1266,7 +1225,7 @@ static int zones_serialize_and_store_chgset(const knot_changeset_t *chs,
 		ret = zones_rrset_write_to_mem(rrset, &entry, &max_size);
 		if (ret != KNOT_EOK) {
 			dbg_zones("%s:%d ret = %s\n", __func__, __LINE__, knot_strerror(ret));
-			return KNOT_ERROR;  /*! \todo Other code? */
+			return ret;
 		}
 
 	}
@@ -1290,7 +1249,7 @@ static int zones_store_changeset(const knot_changeset_t *chs, journal_t *j,
 	/* Count the size of the entire changeset in serialized form. */
 	size_t entry_size = 0;
 
-	int ret = zones_changeset_binary_size(chs, &entry_size);
+	int ret = changeset_binary_size(chs, &entry_size);
 	assert(ret == KNOT_EOK);
 
 	dbg_xfr_verb("Size in serialized form: %zu\n", entry_size);
@@ -1478,8 +1437,7 @@ int zones_store_and_apply_chgsets(knot_changesets_t *chs,
 	/* Commit transaction. */
 	ret = zones_store_changesets_commit(transaction);
 	if (ret != KNOT_EOK) {
-		xfrin_rollback_update(zone->contents, new_contents,
-		                      chs->changes);
+		xfrin_rollback_update(chs, new_contents);
 		log_zone_error("%s Failed to commit stored changesets.\n", msgpref);
 		knot_changesets_free(&chs);
 		return ret;
@@ -1495,15 +1453,14 @@ int zones_store_and_apply_chgsets(knot_changesets_t *chs,
 	if (switch_ret != KNOT_EOK) {
 		log_zone_error("%s Failed to replace current zone.\n", msgpref);
 		// Cleanup old and new contents
-		xfrin_rollback_update(zone->contents, new_contents,
-		                      chs->changes);
+		xfrin_rollback_update(chs, new_contents);
 
 		/* Free changesets, but not the data. */
 		knot_changesets_free(&chs);
 		return KNOT_ERROR;
 	}
 
-	xfrin_cleanup_successful_update(chs->changes);
+	xfrin_cleanup_successful_update(chs);
 
 	/* Free changesets, but not the data. */
 	knot_changesets_free(&chs);
@@ -1779,7 +1736,7 @@ int zones_verify_tsig_query(const knot_pkt_t *query,
 		return KNOT_TSIG_EBADKEY;
 	}
 
-	const knot_dname_t *kname = knot_rrset_owner(query->tsig_rr);
+	const knot_dname_t *kname = query->tsig_rr->owner;
 	assert(kname != NULL);
 
 	/*
@@ -1879,10 +1836,10 @@ int zones_journal_apply(zone_t *zone)
 	}
 
 	/* Fetch SOA serial. */
-	const knot_rrset_t *soa_rrs = 0;
-	soa_rrs = knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA);
+	const knot_rrs_t *soa_rrs = 0;
+	soa_rrs = knot_node_rrs(contents->apex, KNOT_RRTYPE_SOA);
 	assert(soa_rrs != NULL);
-	int64_t serial_ret = knot_rdata_soa_serial(soa_rrs);
+	int64_t serial_ret = knot_rrs_soa_serial(soa_rrs);
 	if (serial_ret < 0) {
 		rcu_read_unlock();
 		return KNOT_EINVAL;
@@ -1930,8 +1887,7 @@ int zones_journal_apply(zone_t *zone)
 							      XFR_TYPE_IIN);
 				rcu_read_lock();
 				if (apply_ret == KNOT_EOK) {
-					xfrin_cleanup_successful_update(
-							chsets->changes);
+					xfrin_cleanup_successful_update(chsets);
 				} else {
 					log_zone_error("Failed to apply "
 					               "changesets to '%s' - Switch failed: "
@@ -1940,9 +1896,8 @@ int zones_journal_apply(zone_t *zone)
 					ret = KNOT_ERROR;
 
 					// Cleanup old and new contents
-					xfrin_rollback_update(zone->contents,
-					                      &contents,
-					                      chsets->changes);
+					xfrin_rollback_update(chsets,
+					                      &contents);
 				}
 			}
 		}
@@ -2023,10 +1978,7 @@ static int diff_after_load(zone_t *zone, zone_t *old_zone,
 	if (*diff_chs != NULL) {
 		assert(!zones_changesets_empty(*diff_chs));
 		/* Apply DNSSEC changeset to the new zone. */
-		ret = xfrin_apply_changesets_directly(zone->contents,
-		                                      (*diff_chs)->changes,
-		                                      *diff_chs);
-
+		ret = xfrin_apply_changesets_directly(zone->contents, *diff_chs);
 		if (ret == KNOT_EOK) {
 			ret = xfrin_finalize_updated_zone(
 			                        zone->contents, true);
@@ -2042,7 +1994,7 @@ static int diff_after_load(zone_t *zone, zone_t *old_zone,
 			return ret;
 		}
 
-		xfrin_cleanup_successful_update((*diff_chs)->changes);
+		xfrin_cleanup_successful_update(*diff_chs);
 		knot_changesets_free(diff_chs);
 		assert(zone->conf->build_diffs);
 	}
@@ -2113,7 +2065,6 @@ static int store_chgsets_after_load(zone_t *old_zone, zone_t *zone,
 			assert(!old_zone ||
 			       old_zone->contents != zone->contents);
 			ret = xfrin_apply_changesets_directly(zone->contents,
-			                                      diff_chs->changes,
 			                                      diff_chs);
 			if (ret == KNOT_EOK) {
 				ret = xfrin_finalize_updated_zone(
@@ -2137,7 +2088,7 @@ static int store_chgsets_after_load(zone_t *old_zone, zone_t *zone,
 			return ret;
 		}
 
-		xfrin_cleanup_successful_update(diff_chs->changes);
+		xfrin_cleanup_successful_update(diff_chs);
 	}
 
 	/* Commit transaction. */
@@ -2166,8 +2117,7 @@ static int store_chgsets_after_load(zone_t *old_zone, zone_t *zone,
 			               "switching zone (%s).\n",
 			               zone->conf->name, knot_strerror(ret));
 			// Cleanup old and new contents
-			xfrin_rollback_update(zone->contents, &new_contents,
-			                      diff_chs->changes);
+			xfrin_rollback_update(diff_chs, &new_contents);
 			return ret;
 		}
 
diff --git a/src/knot/updates/changesets.c b/src/knot/updates/changesets.c
index 23d21100bc84901668d135a0f7ce7bcf640ec272..0acb99476a8b1e9062ee1b2e537f8cb4e5af75a8 100644
--- a/src/knot/updates/changesets.c
+++ b/src/knot/updates/changesets.c
@@ -27,12 +27,6 @@
 #include "common/debug.h"
 #include "libknot/rdata.h"
 
-static int knot_changeset_rrsets_match(const knot_rrset_t *rrset1,
-                                         const knot_rrset_t *rrset2)
-{
-	return knot_rrset_equal(rrset1, rrset2, KNOT_RRSET_COMPARE_HEADER);
-}
-
 int knot_changesets_init(knot_changesets_t **changesets)
 {
 	// Create new changesets structure
@@ -54,14 +48,6 @@ int knot_changesets_init(knot_changesets_t **changesets)
 	// Init list with changesets
 	init_list(&(*changesets)->sets);
 
-	// Init changes structure
-	(*changesets)->changes = xmalloc(sizeof(knot_changes_t));
-	// Init changes' allocator (storing RRs)
-	(*changesets)->changes->mem_ctx = (*changesets)->mmc_rr;
-	// Init changes' lists
-	init_list(&(*changesets)->changes->new_rrsets);
-	init_list(&(*changesets)->changes->old_rrsets);
-
 	return KNOT_EOK;
 }
 
@@ -98,6 +84,10 @@ knot_changeset_t *knot_changesets_create_changeset(knot_changesets_t *ch)
 	init_list(&set->add);
 	init_list(&set->remove);
 
+	// Init change lists
+	init_list(&set->new_data);
+	init_list(&set->old_data);
+
 	// Insert into list of sets
 	add_tail(&ch->sets, (node_t *)set);
 
@@ -115,24 +105,6 @@ knot_changeset_t *knot_changesets_get_last(const knot_changesets_t *chs)
 	return (knot_changeset_t *)(TAIL(chs->sets));
 }
 
-const knot_rrset_t *knot_changeset_last_rr(const knot_changeset_t *ch,
-                                           knot_changeset_part_t part)
-{
-	if (ch == NULL) {
-		return NULL;
-	}
-
-	if (part == KNOT_CHANGESET_ADD) {
-		knot_rr_ln_t *n = TAIL(ch->add);
-		return n ? n->rr : NULL;
-	} else if (part == KNOT_CHANGESET_REMOVE) {
-		knot_rr_ln_t *n = TAIL(ch->remove);
-		return n ? n->rr : NULL;
-	}
-
-	return NULL;
-}
-
 int knot_changeset_add_rrset(knot_changeset_t *chgs, knot_rrset_t *rrset,
                              knot_changeset_part_t part)
 {
@@ -155,60 +127,11 @@ int knot_changeset_add_rrset(knot_changeset_t *chgs, knot_rrset_t *rrset,
 	return KNOT_EOK;
 }
 
-int knot_changeset_add_rr(knot_changeset_t *chgs, knot_rrset_t *rr,
-                          knot_changeset_part_t part)
-{
-	// Just check the last RRSet. If the RR belongs to it, merge it,
-	// otherwise just add the RR to the end of the list
-	list_t *l = part == KNOT_CHANGESET_ADD ? &(chgs->add) : &(chgs->remove);
-	knot_rrset_t *tail_rr =
-		EMPTY_LIST(*l) ? NULL : ((knot_rr_ln_t *)(TAIL(*l)))->rr;
-
-	if (tail_rr && knot_changeset_rrsets_match(tail_rr, rr)) {
-		// Create changesets exactly as they came, with possibly
-		// duplicate records
-		if (knot_rrset_merge(tail_rr, rr, NULL) != KNOT_EOK) {
-			return KNOT_ERROR;
-		}
-
-		knot_rrset_free(&rr, NULL);
-		return KNOT_EOK;
-	} else {
-		return knot_changeset_add_rrset(chgs, rr, part);
-	}
-}
-
-int knot_changes_add_rrset(knot_changes_t *ch, knot_rrset_t *rrset,
-                           knot_changes_part_t part)
-{
-	if (ch == NULL || rrset == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	knot_rr_ln_t *rr_node =
-		ch->mem_ctx.alloc(ch->mem_ctx.ctx, sizeof(knot_rr_ln_t));
-	if (rr_node == NULL) {
-		// This will not happen with mp_alloc, but allocator can change
-		ERR_ALLOC_FAILED;
-		return KNOT_ENOMEM;
-	}
-	rr_node->rr = rrset;
-
-	if (part == KNOT_CHANGES_NEW) {
-		add_tail(&ch->new_rrsets, (node_t *)rr_node);
-	} else {
-		assert(part == KNOT_CHANGES_OLD);
-		add_tail(&ch->old_rrsets, (node_t *)rr_node);
-	}
-
-	return KNOT_EOK;
-}
-
 static void knot_changeset_store_soa(knot_rrset_t **chg_soa,
                                      uint32_t *chg_serial, knot_rrset_t *soa)
 {
 	*chg_soa = soa;
-	*chg_serial = knot_rdata_soa_serial(soa);
+	*chg_serial = knot_rrs_soa_serial(&soa->rrs);
 }
 
 void knot_changeset_add_soa(knot_changeset_t *changeset, knot_rrset_t *soa,
@@ -337,7 +260,6 @@ void knot_changesets_free(knot_changesets_t **changesets)
 
 	knot_rrset_free(&(*changesets)->first_soa, NULL);
 
-	free((*changesets)->changes);
 	free(*changesets);
 	*changesets = NULL;
 }
diff --git a/src/knot/updates/changesets.h b/src/knot/updates/changesets.h
index f1a893ac74c9bf60aa0517285f48cb01cf478963..e17819a8e3c35ae89dcaed5e92034a1ed1024f20 100644
--- a/src/knot/updates/changesets.h
+++ b/src/knot/updates/changesets.h
@@ -41,7 +41,6 @@ typedef enum {
 	KNOT_CHANGESET_TYPE_DNSSEC = 1 << 2
 } knot_changeset_flag_t;
 
-
 /*! \brief One changeset received from wire, with parsed RRs. */
 typedef struct knot_changeset {
 	node_t n; /*!< List node. */
@@ -55,6 +54,8 @@ typedef struct knot_changeset {
 	uint32_t serial_from; /*!< SOA start serial. */
 	uint32_t serial_to; /*!< SOA destination serial. */
 	uint32_t flags; /*!< DDNS / IXFR flags. */
+	list_t old_data; /*!< Old data, to be freed after succesfull update. */
+	list_t new_data; /*!< New data, to be freed after failed update. */
 } knot_changeset_t;
 
 /*----------------------------------------------------------------------------*/
@@ -65,23 +66,6 @@ typedef struct knot_rr_ln {
 	knot_rrset_t *rr; /*!< Actual usable data. */
 } knot_rr_ln_t;
 
-/*! \brief Partial changes done to zones - used for update/transfer rollback. */
-typedef struct {
-	/*!
-	 * Memory context. Ideally a pool allocator since there is a possibility
-	 * of many changes in one transfer/update.
-	 */
-	mm_ctx_t mem_ctx;
-	/*!
-	 * Deleted after successful update.
-	 */
-	list_t old_rrsets;
-	/*!
-	 * Deleted after failed update.
-	 */
-	list_t new_rrsets;
-} knot_changes_t;
-
 /*----------------------------------------------------------------------------*/
 
 /*!
@@ -95,7 +79,6 @@ typedef struct {
 	size_t count; /*!< Changeset count. */
 	knot_rrset_t *first_soa; /*!< First received SOA. */
 	uint32_t flags; /*!< DDNS / IXFR flags. */
-	knot_changes_t *changes; /*!< Partial changes. */
 } knot_changesets_t;
 
 /*----------------------------------------------------------------------------*/
@@ -105,11 +88,6 @@ typedef enum {
 	KNOT_CHANGESET_REMOVE
 } knot_changeset_part_t;
 
-typedef enum {
-	KNOT_CHANGES_OLD,
-	KNOT_CHANGES_NEW,
-} knot_changes_part_t;
-
 /*----------------------------------------------------------------------------*/
 
 /*!
@@ -156,9 +134,6 @@ knot_changeset_t *knot_changesets_create_changeset(knot_changesets_t *ch);
  */
 knot_changeset_t *knot_changesets_get_last(const knot_changesets_t *ch);
 
-const knot_rrset_t *knot_changeset_last_rr(const knot_changeset_t *ch,
-                                           knot_changeset_part_t part);
-
 /*!
  * \brief Add RRSet to changeset. RRSet is either inserted to 'add' or to
  *        'remove' list. Will *not* try to merge with previous RRSets.
@@ -173,20 +148,6 @@ const knot_rrset_t *knot_changeset_last_rr(const knot_changeset_t *ch,
 int knot_changeset_add_rrset(knot_changeset_t *chgs,
                              knot_rrset_t *rrset, knot_changeset_part_t part);
 
-/*!
- * \brief Add RRSet to changeset. RRSet is either inserted to 'add' or to
- *        'remove' list. *Will* try to merge with previous RRSets.
- *
- * \param chgs Changeset to add RRSet into.
- * \param rrset RRSet to be added.
- * \param part Add to 'add' or 'remove'?
- *
- * \retval KNOT_EOK on success.
- * \retval Error code on failure.
- */
-int knot_changeset_add_rr(knot_changeset_t *chgs,
-                          knot_rrset_t *rrset, knot_changeset_part_t part);
-
 /*!
  * \brief Adds a source/destination SOA RRSet to changeset.
  *
@@ -246,20 +207,6 @@ int knot_changeset_apply(knot_changeset_t *changeset,
  */
 void knot_changesets_free(knot_changesets_t **changesets);
 
-/*!
- * \brief Add RRSet to changes structure.
- *        RRSet is either inserted to 'old' or to 'new' list.
- *
- * \param chgs Change to add RRSet into.
- * \param rrset RRSet to be added.
- * \param part Add to 'old' or 'new'?
- *
- * \retval KNOT_EOK on success.
- * \retval Error code on failure.
- */
-int knot_changes_add_rrset(knot_changes_t *ch, knot_rrset_t *rrset,
-                           knot_changes_part_t part);
-
 /*!
  * \brief Merges two changesets together, second changeset's lists are kept.
  *
@@ -275,11 +222,6 @@ int knot_changes_add_rrset(knot_changes_t *ch, knot_rrset_t *rrset,
  */
 int knot_changeset_merge(knot_changeset_t *ch1, knot_changeset_t *ch2);
 
-/*!
- * \param changes Double pointer of changes structure to be freed.
- */
-void knot_changes_free(knot_changes_t **changes);
-
 #endif /* _KNOT_CHANGESETS_H_ */
 
 /*! @} */
diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c
index a6ffa47f65a5930b5fef20432202b9b7895d8276..f8637a7f36d7863f1e38bc0eb2d2de9ce6c52579 100644
--- a/src/knot/updates/ddns.c
+++ b/src/knot/updates/ddns.c
@@ -29,199 +29,56 @@
 #include "libknot/consts.h"
 #include "common/mempattern.h"
 #include "common/descriptor.h"
+#include "common/lists.h"
 
-static bool rrset_empty(const knot_rrset_t *rrset)
-{
-	uint16_t rr_count = knot_rrset_rr_count(rrset);
-	if (rr_count == 0) {
-		return true;
-	}
-	if (rr_count == 1) {
-		return knot_rrset_rr_size(rrset, 0) == 0;
-	}
-	return false;
-}
+/* ----------------------------- prereq check ------------------------------- */
 
-/*----------------------------------------------------------------------------*/
-// Copied from XFR - maybe extract somewhere else
-static int knot_ddns_prereq_check_rrsets(knot_rrset_t ***rrsets,
-                                         size_t *count, size_t *allocated)
+/*!< \brief Clears prereq RRSet list. */
+static void rrset_list_clear(list_t *l)
 {
-	/* This is really confusing, it's ptr -> array of "knot_rrset_t*" */
-	char *arr = (char*)*rrsets;
-	int ret = 0;
-	ret = mreserve(&arr, sizeof(knot_rrset_t*), *count + 1, 0, allocated);
-	if (ret < 0) {
-		return KNOT_ENOMEM;
-	}
-
-	*rrsets = (knot_rrset_t**)arr;
-
-	return KNOT_EOK;
+	node_t *n, *nxt;
+	WALK_LIST_DELSAFE(n, nxt, *l) {
+		ptrnode_t *ptr_n = (ptrnode_t *)n;
+		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
+		knot_rrset_free(&rrset, NULL);
+		free(n);
+	};
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_prereq_check_dnames(knot_dname_t ***dnames,
-                                         size_t *count, size_t *allocated)
+/*!< \brief Adds RR to prereq RRSet list, merges RRs into RRSets. */
+static int add_rr_to_list(list_t *l, const knot_rrset_t *rr)
 {
-	/* This is really confusing, it's ptr -> array of "knot_dname_t*" */
-	char *arr = (char*)*dnames;
-	int ret = 0;
-	ret = mreserve(&arr, sizeof(knot_dname_t*), *count + 1, 0, allocated);
-	if (ret < 0) {
-		return KNOT_ENOMEM;
-	}
-
-	*dnames = (knot_dname_t**)arr;
-
-	return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_add_prereq_rrset(const knot_rrset_t *rrset,
-                                      knot_rrset_t ***rrsets,
-                                      size_t *count, size_t *allocd)
-{
-	// check if such RRSet is not already there and merge if needed
-	int ret;
-	for (int i = 0; i < *count; ++i) {
-		if (knot_rrset_equal(rrset, (*rrsets)[i],
-		                     KNOT_RRSET_COMPARE_HEADER)) {
-			ret = knot_rrset_merge((*rrsets)[i], rrset, NULL);
-			if (ret != KNOT_EOK) {
-				return ret;
-			} else {
-				return KNOT_EOK;
-			}
+	node_t *n;
+	WALK_LIST(n, *l) {
+		ptrnode_t *ptr_n = (ptrnode_t *)n;
+		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
+		if (knot_rrset_equal(rr, rrset, KNOT_RRSET_COMPARE_HEADER)) {
+			return knot_rrs_merge(&rrset->rrs, &rr->rrs, NULL);
 		}
-	}
-
-	// if we are here, the RRSet was not found
-	ret = knot_ddns_prereq_check_rrsets(rrsets, count, allocd);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-
-	knot_rrset_t *new_rrset = NULL;
-	ret = knot_rrset_copy(rrset, &new_rrset, NULL);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-
-	(*rrsets)[(*count)++] = new_rrset;
-
-	return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
+	};
 
-static int knot_ddns_add_prereq_dname(const knot_dname_t *dname,
-                                      knot_dname_t ***dnames,
-                                      size_t *count, size_t *allocd)
-{
-	// we do not have to check if the name is not already there
-	// if it is, we will just check it twice in the zone
-
-	int ret = knot_ddns_prereq_check_dnames(dnames, count, allocd);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-
-	knot_dname_t *dname_new = knot_dname_copy(dname);
-	if (dname_new == NULL) {
+	knot_rrset_t *rr_copy = knot_rrset_copy(rr, NULL);
+	if (rr_copy == NULL) {
 		return KNOT_ENOMEM;
 	}
-
-	(*dnames)[(*count)++] = dname_new;
-
-	return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_add_prereq(knot_ddns_prereq_t *prereqs,
-                                const knot_rrset_t *rrset, uint16_t qclass)
-{
-	assert(prereqs != NULL);
-	assert(rrset != NULL);
-
-	if (knot_rrset_rr_ttl(rrset, 0) != 0) {
-		dbg_ddns("ddns: add_prereq: Wrong TTL.\n");
-		return KNOT_EMALF;
-	}
-
-	int ret;
-
-	if (knot_rrset_class(rrset) == KNOT_CLASS_ANY) {
-		if (!rrset_empty(rrset)) {
-			dbg_ddns("ddns: add_prereq: Extra data\n");
-			return KNOT_EMALF;
-		}
-		if (knot_rrset_type(rrset) == KNOT_RRTYPE_ANY) {
-			ret = knot_ddns_add_prereq_dname(
-				knot_rrset_owner(rrset), &prereqs->in_use,
-				&prereqs->in_use_count,
-				&prereqs->in_use_allocd);
-		} else {
-			ret = knot_ddns_add_prereq_rrset(rrset,
-			                                &prereqs->exist,
-			                                &prereqs->exist_count,
-			                                &prereqs->exist_allocd);
-		}
-	} else if (knot_rrset_class(rrset) == KNOT_CLASS_NONE) {
-		if (!rrset_empty(rrset)) {
-			dbg_ddns("ddns: add_prereq: Extra data\n");
-			return KNOT_EMALF;
-		}
-		if (knot_rrset_type(rrset) == KNOT_RRTYPE_ANY) {
-			ret = knot_ddns_add_prereq_dname(
-				knot_rrset_owner(rrset), &prereqs->not_in_use,
-				&prereqs->not_in_use_count,
-				&prereqs->not_in_use_allocd);
-		} else {
-			ret = knot_ddns_add_prereq_rrset(rrset,
-			                            &prereqs->not_exist,
-			                            &prereqs->not_exist_count,
-			                            &prereqs->not_exist_allocd);
-		}
-	} else if (knot_rrset_class(rrset) == qclass) {
-		ret = knot_ddns_add_prereq_rrset(rrset,
-		                                 &prereqs->exist_full,
-		                                 &prereqs->exist_full_count,
-		                                 &prereqs->exist_full_allocd);
-	} else {
-		dbg_ddns("ddns: add_prereq: Bad class.\n");
-		return KNOT_EMALF;
-	}
-
-	return ret;
+	return ptrlist_add(l, rr_copy, NULL) != NULL ? KNOT_EOK : KNOT_ENOMEM;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_check_exist(const knot_zone_contents_t *zone,
-                                 const knot_rrset_t *rrset, uint16_t *rcode)
+/*!< \brief Checks whether RR type exists in the zone. */
+static int check_type_exist(const knot_zone_contents_t *zone,
+                            const knot_rrset_t *rrset, uint16_t *rcode)
 {
 	assert(zone != NULL);
 	assert(rrset != NULL);
 	assert(rcode != NULL);
-	assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY);
-	assert(knot_rrset_class(rrset) == KNOT_CLASS_ANY);
-
-	if (!knot_dname_is_sub(knot_rrset_owner(rrset),
-	    knot_node_owner(knot_zone_contents_apex(zone)))) {
-		*rcode = KNOT_RCODE_NOTZONE;
-		return KNOT_EOUTOFZONE;
-	}
+	assert(rrset->type != KNOT_RRTYPE_ANY);
+	assert(rrset->rclass == KNOT_CLASS_ANY);
 
-	const knot_node_t *node;
-	node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset));
+	const knot_node_t *node = knot_zone_contents_find_node(zone, rrset->owner);
 	if (node == NULL) {
 		*rcode = KNOT_RCODE_NXRRSET;
 		return KNOT_ENONODE;
-	} else if (knot_node_rrset(node, knot_rrset_type(rrset)) == NULL) {
+	} else if (!knot_node_rrtype_exists(node, rrset->type)) {
 		*rcode = KNOT_RCODE_NXRRSET;
 		return KNOT_ENORRSET;
 	}
@@ -229,40 +86,26 @@ static int knot_ddns_check_exist(const knot_zone_contents_t *zone,
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone,
-                                      const knot_rrset_t *rrset,
-                                      uint16_t *rcode)
+/*!< \brief Checks whether RRSet exists in the zone. */
+static int check_rrset_exists(const knot_zone_contents_t *zone,
+                              const knot_rrset_t *rrset, uint16_t *rcode)
 {
 	assert(zone != NULL);
 	assert(rrset != NULL);
 	assert(rcode != NULL);
-	assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY);
-
-	if (!knot_dname_is_sub(knot_rrset_owner(rrset),
-	    knot_node_owner(knot_zone_contents_apex(zone)))) {
-		*rcode = KNOT_RCODE_NOTZONE;
-		return KNOT_EOUTOFZONE;
-	}
-
-	const knot_node_t *node;
-	const knot_rrset_t *found;
+	assert(rrset->type != KNOT_RRTYPE_ANY);
 
-	node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset));
+	const knot_node_t *node = knot_zone_contents_find_node(zone, rrset->owner);
 	if (node == NULL) {
 		*rcode = KNOT_RCODE_NXRRSET;
 		return KNOT_EPREREQ;
-	} else if ((found = knot_node_rrset(node, knot_rrset_type(rrset)))
-	            == NULL) {
+	} else if (!knot_node_rrtype_exists(node, rrset->type)) {
 		*rcode = KNOT_RCODE_NXRRSET;
 		return KNOT_EPREREQ;
 	} else {
-		// do not have to compare the header, it is already done
-		assert(knot_rrset_type(found) == knot_rrset_type(rrset));
-		assert(knot_dname_cmp(knot_rrset_owner(found),
-		                          knot_rrset_owner(rrset)) == 0);
-		if (!knot_rrset_equal(found, rrset, KNOT_RRSET_COMPARE_WHOLE)) {
+		knot_rrset_t found = knot_node_rrset(node, rrset->type);
+		assert(!knot_rrset_empty(&found));
+		if (!knot_rrset_equal(&found, rrset, KNOT_RRSET_COMPARE_WHOLE)) {
 			*rcode = KNOT_RCODE_NXRRSET;
 			return KNOT_EPREREQ;
 		}
@@ -271,58 +114,53 @@ static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone,
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
+/*!< \brief Checks whether RRSets in the list exist in the zone. */
+static int check_stored_rrsets(list_t *l, const knot_zone_contents_t *zone,
+                               uint16_t *rcode)
+{
+	node_t *n;
+	WALK_LIST(n, *l) {
+		ptrnode_t *ptr_n = (ptrnode_t *)n;
+		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
+		int ret = check_rrset_exists(zone, rrset, rcode);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+	};
 
-static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone,
-                                     const knot_rrset_t *rrset,
-                                     uint16_t *rcode)
+	return KNOT_EOK;
+}
+
+/*!< \brief Checks whether RR type is not in the zone. */
+static int check_type_not_exist(const knot_zone_contents_t *zone,
+                                const knot_rrset_t *rrset, uint16_t *rcode)
 {
 	assert(zone != NULL);
 	assert(rrset != NULL);
 	assert(rcode != NULL);
-	assert(knot_rrset_type(rrset) != KNOT_RRTYPE_ANY);
-	assert(knot_rrset_class(rrset) == KNOT_CLASS_NONE);
-
-	if (!knot_dname_is_sub(knot_rrset_owner(rrset),
-	    knot_node_owner(knot_zone_contents_apex(zone)))) {
-		*rcode = KNOT_RCODE_NOTZONE;
-		return KNOT_EOUTOFZONE;
-	}
-
-	const knot_node_t *node;
+	assert(rrset->type != KNOT_RRTYPE_ANY);
+	assert(rrset->rclass == KNOT_CLASS_NONE);
 
-	node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset));
+	const knot_node_t *node = knot_zone_contents_find_node(zone, rrset->owner);
 	if (node == NULL) {
 		return KNOT_EOK;
-	} else if (knot_node_rrset(node, knot_rrset_type(rrset)) == NULL) {
+	} else if (!knot_node_rrtype_exists(node, rrset->type)) {
 		return KNOT_EOK;
 	}
 
-	/* RDATA is always empty for simple RRset checks. */
-
 	*rcode = KNOT_RCODE_YXRRSET;
 	return KNOT_EPREREQ;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_check_in_use(const knot_zone_contents_t *zone,
-                                  const knot_dname_t *dname,
-                                  uint16_t *rcode)
+/*!< \brief Checks whether DNAME is in the zone. */
+static int check_in_use(const knot_zone_contents_t *zone,
+                        const knot_dname_t *dname, uint16_t *rcode)
 {
 	assert(zone != NULL);
 	assert(dname != NULL);
 	assert(rcode != NULL);
 
-	if (!knot_dname_is_sub(dname,
-	    knot_node_owner(knot_zone_contents_apex(zone)))) {
-		*rcode = KNOT_RCODE_NOTZONE;
-		return KNOT_EOUTOFZONE;
-	}
-
-	const knot_node_t *node;
-
-	node = knot_zone_contents_find_node(zone, dname);
+	const knot_node_t *node = knot_zone_contents_find_node(zone, dname);
 	if (node == NULL) {
 		*rcode = KNOT_RCODE_NXDOMAIN;
 		return KNOT_EPREREQ;
@@ -334,25 +172,15 @@ static int knot_ddns_check_in_use(const knot_zone_contents_t *zone,
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone,
-                                      const knot_dname_t *dname,
-                                      uint16_t *rcode)
+/*!< \brief Checks whether DNAME is not in the zone. */
+static int check_not_in_use(const knot_zone_contents_t *zone,
+                            const knot_dname_t *dname, uint16_t *rcode)
 {
 	assert(zone != NULL);
 	assert(dname != NULL);
 	assert(rcode != NULL);
 
-	if (!knot_dname_is_sub(dname,
-	    knot_node_owner(knot_zone_contents_apex(zone)))) {
-		*rcode = KNOT_RCODE_NOTZONE;
-		return KNOT_EOUTOFZONE;
-	}
-
-	const knot_node_t *node;
-
-	node = knot_zone_contents_find_node(zone, dname);
+	const knot_node_t *node = knot_zone_contents_find_node(zone, dname);
 	if (node == NULL) {
 		return KNOT_EOK;
 	} else if (knot_node_rrset_count(node) == 0) {
@@ -363,1439 +191,832 @@ static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone,
 	return KNOT_EPREREQ;
 }
 
-/*----------------------------------------------------------------------------*/
-/* API functions                                                              */
-/*----------------------------------------------------------------------------*/
-
-int knot_ddns_check_zone(const knot_zone_contents_t *zone,
-                         const knot_pkt_t *query, uint16_t *rcode)
-{
-	if (zone == NULL || query == NULL || rcode == NULL) {
-		if (rcode != NULL) {
-			*rcode = KNOT_RCODE_SERVFAIL;
-		}
-		return KNOT_EINVAL;
-	}
-
-	if (knot_pkt_qtype(query) != KNOT_RRTYPE_SOA) {
-		*rcode = KNOT_RCODE_FORMERR;
-		return KNOT_EMALF;
-	}
-
-	// check zone CLASS
-	if (knot_zone_contents_class(zone) !=
-	    knot_pkt_qclass(query)) {
-		*rcode = KNOT_RCODE_NOTAUTH;
-		return KNOT_ENOZONE;
-	}
-
-	return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-int knot_ddns_process_prereqs(const knot_pkt_t *query,
-                              knot_ddns_prereq_t **prereqs, uint16_t *rcode)
+/*!< \brief Returns true if rrset has 0 data or RDATA of size 0 (we need TTL). */
+static bool rrset_empty(const knot_rrset_t *rrset)
 {
-	if (query == NULL || prereqs == NULL || rcode == NULL) {
-		return KNOT_EINVAL;
+	uint16_t rr_count = knot_rrset_rr_count(rrset);
+	if (rr_count == 0) {
+		return true;
 	}
-
-	dbg_ddns("Processing prerequisities.\n");
-
-	// allocate space for the prerequisities
-	*prereqs = (knot_ddns_prereq_t *)calloc(1, sizeof(knot_ddns_prereq_t));
-	CHECK_ALLOC_LOG(*prereqs, KNOT_ENOMEM);
-
-	int ret;
-
-	const knot_pktsection_t *answer = knot_pkt_section(query, KNOT_ANSWER);
-	for (int i = 0; i < answer->count; ++i) {
-		// we must copy the RRSets, because all those stored in the
-		// packet will be destroyed
-		dbg_ddns_detail("Creating prereqs from following RRSet:\n");
-		ret = knot_ddns_add_prereq(*prereqs,
-		                           answer->rr[i],
-		                           knot_pkt_qclass(query));
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to add prerequisity RRSet:%s\n",
-			                knot_strerror(ret));
-			*rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR
-			                             : KNOT_RCODE_SERVFAIL;
-			knot_ddns_prereqs_free(prereqs);
-			return ret;
-		}
+	if (rr_count == 1) {
+		return knot_rrset_rr_size(rrset, 0) == 0;
 	}
-
-	return KNOT_EOK;
+	return false;
 }
 
-/*----------------------------------------------------------------------------*/
-
-int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
-                            knot_ddns_prereq_t **prereqs, uint16_t *rcode)
+/*!< \brief Checks prereq for given packet RR. */
+static int process_prereq(const knot_rrset_t *rrset, uint16_t qclass,
+                          const knot_zone_contents_t *zone, uint16_t *rcode,
+                          list_t *rrset_list)
 {
-	int i, ret;
-
-	dbg_ddns("Checking 'exist' prerequisities.\n");
-
-	for (i = 0; i < (*prereqs)->exist_count; ++i) {
-		ret = knot_ddns_check_exist(zone, (*prereqs)->exist[i], rcode);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	dbg_ddns("Checking 'exist full' prerequisities.\n");
-	for (i = 0; i < (*prereqs)->exist_full_count; ++i) {
-		ret = knot_ddns_check_exist_full(zone,
-		                                (*prereqs)->exist_full[i],
-		                                 rcode);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	dbg_ddns("Checking 'not exist' prerequisities.\n");
-	for (i = 0; i < (*prereqs)->not_exist_count; ++i) {
-		ret = knot_ddns_check_not_exist(zone, (*prereqs)->not_exist[i],
-		                                rcode);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	dbg_ddns("Checking 'in use' prerequisities.\n");
-	for (i = 0; i < (*prereqs)->in_use_count; ++i) {
-		ret = knot_ddns_check_in_use(zone, (*prereqs)->in_use[i],
-		                             rcode);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	dbg_ddns("Checking 'not in use' prerequisities.\n");
-	for (i = 0; i < (*prereqs)->not_in_use_count; ++i) {
-		ret = knot_ddns_check_not_in_use(zone,
-		                                 (*prereqs)->not_in_use[i],
-		                                 rcode);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
+	if (knot_rrset_rr_ttl(rrset, 0) != 0) {
+		return KNOT_EMALF;
 	}
 
-	return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_check_update(const knot_rrset_t *rrset,
-                                  const knot_pkt_t *query,
-                                  uint16_t *rcode)
-{
-	/* Accept both subdomain and dname match. */
-	dbg_ddns("Checking UPDATE packet.\n");
-	const knot_dname_t *owner = knot_rrset_owner(rrset);
-	const knot_dname_t *qname = knot_pkt_qname(query);
-	int is_sub = knot_dname_is_sub(owner, qname);
-	if (!is_sub && knot_dname_cmp(owner, qname) != 0) {
+	if (!knot_dname_is_sub(rrset->owner, zone->apex->owner)) {
 		*rcode = KNOT_RCODE_NOTZONE;
 		return KNOT_EOUTOFZONE;
 	}
 
-	if (knot_rrtype_is_ddns_forbidden(rrset->type)) {
-		*rcode = KNOT_RCODE_REFUSED;
-		log_zone_error("Refusing to update DNSSEC-related record!\n");
-		return KNOT_EDENIED;
-	}
-
-	if (knot_rrset_class(rrset) == knot_pkt_qclass(query)) {
-		if (knot_rrtype_is_metatype(knot_rrset_type(rrset))) {
-			*rcode = KNOT_RCODE_FORMERR;
+	if (rrset->rclass == KNOT_CLASS_ANY) {
+		if (!rrset_empty(rrset)) {
 			return KNOT_EMALF;
 		}
-	} else if (knot_rrset_class(rrset) == KNOT_CLASS_ANY) {
-		if (!rrset_empty(rrset)
-		    || (knot_rrtype_is_metatype(knot_rrset_type(rrset))
-		        && knot_rrset_type(rrset) != KNOT_RRTYPE_ANY)) {
-			*rcode = KNOT_RCODE_FORMERR;
-			return KNOT_EMALF;
+		if (rrset->type == KNOT_RRTYPE_ANY) {
+			return check_in_use(zone, rrset->owner, rcode);
+		} else {
+			return check_type_exist(zone, rrset, rcode);
 		}
-	} else if (knot_rrset_class(rrset) == KNOT_CLASS_NONE) {
-		if (knot_rrset_rr_ttl(rrset, 0) != 0
-		    || knot_rrtype_is_metatype(knot_rrset_type(rrset))) {
-			*rcode = KNOT_RCODE_FORMERR;
+	} else if (rrset->rclass == KNOT_CLASS_NONE) {
+		if (!rrset_empty(rrset)) {
 			return KNOT_EMALF;
 		}
+		if (rrset->type == KNOT_RRTYPE_ANY) {
+			return check_not_in_use(zone, rrset->owner, rcode);
+		} else {
+			return check_type_not_exist(zone, rrset, rcode);
+		}
+	} else if (rrset->rclass == qclass) {
+		// Store RRs for full check into list
+		return add_rr_to_list(rrset_list, rrset);
 	} else {
-		*rcode = KNOT_RCODE_FORMERR;
 		return KNOT_EMALF;
 	}
-
-	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
+/* --------------------------- DDNS processing ------------------------------ */
 
-void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq)
-{
-	dbg_ddns("Freeing prerequisities.\n");
-
-	int i;
+/* ----------------------- changeset lists helpers -------------------------- */
 
-	for (i = 0; i < (*prereq)->exist_count; ++i) {
-		knot_rrset_free(&(*prereq)->exist[i], NULL);
-	}
-	free((*prereq)->exist);
-
-	for (i = 0; i < (*prereq)->exist_full_count; ++i) {
-		knot_rrset_free(&(*prereq)->exist_full[i], NULL);
-	}
-	free((*prereq)->exist_full);
+#warning TODO: Store changesets as a lookup structure
 
-	for (i = 0; i < (*prereq)->not_exist_count; ++i) {
-		knot_rrset_free(&(*prereq)->not_exist[i], NULL);
-	}
-	free((*prereq)->not_exist);
+/*!< \brief Returns true if \a cmp code returns true for one RR in list. */
+#define LIST_MATCH(l, cmp) \
+	knot_rr_ln_t *_n; \
+	WALK_LIST(_n, l) { \
+		const knot_rrset_t *list_rr = _n->rr; \
+		if (cmp) { \
+			return true; \
+		} \
+	}; \
+	return false;
 
-	for (i = 0; i < (*prereq)->in_use_count; ++i) {
-		knot_dname_free(&(*prereq)->in_use[i]);
-	}
-	free((*prereq)->in_use);
+/*!< \brief Deletes RR from list if \a cmp code returns true for it. */
+#define LIST_DEL_MATCH(l, cmp) \
+	knot_rr_ln_t *_n; \
+	node_t *_nxt; \
+	WALK_LIST_DELSAFE(_n, _nxt, l) { \
+		knot_rrset_t *list_rr = _n->rr; \
+		if (cmp) { \
+			knot_rrset_free(&list_rr, NULL); \
+			rem_node((node_t *)_n); \
+		} \
+	};
+
+/*!< \brief Checks whether RR was already removed. */
+static bool removed_rr(const knot_changeset_t *changeset, const knot_rrset_t *rr)
+{
+	LIST_MATCH(changeset->remove,
+	           knot_rrset_equal(rr, list_rr, KNOT_RRSET_COMPARE_WHOLE));
+}
 
-	for (i = 0; i < (*prereq)->not_in_use_count; ++i) {
-		knot_dname_free(&(*prereq)->not_in_use[i]);
-	}
-	free((*prereq)->not_in_use);
+/*!< \brief Checks whether any CNAME RR under dname was added. */
+static bool cname_added(const knot_changeset_t *changeset, const knot_dname_t *d)
+{
+	knot_rrset_t mock_cname;
+	knot_rrset_init(&mock_cname, (knot_dname_t *)d,
+	                KNOT_RRTYPE_CNAME, KNOT_CLASS_IN);
+	LIST_MATCH(changeset->add,
+	           knot_rrset_equal(&mock_cname, list_rr, KNOT_RRSET_COMPARE_HEADER));
+}
 
-	free(*prereq);
-	*prereq = NULL;
+/*!< \brief Checks whether any RR under given name was added. */
+static bool name_added(const knot_changeset_t *changeset, const knot_dname_t *d)
+{
+	LIST_MATCH(changeset->add, knot_dname_is_equal(d, list_rr->owner));
 }
 
-/*----------------------------------------------------------------------------*/
-/* New DDNS processing                                                      - */
-/*----------------------------------------------------------------------------*/
+/*!< \brief Removes RR from list, full equality check. */
+static void remove_rr_from_list(list_t *l, const knot_rrset_t *rr)
+{
+	LIST_DEL_MATCH(*l, knot_rrset_equal(list_rr, rr, KNOT_RRSET_COMPARE_WHOLE));
+}
 
-static int knot_ddns_check_remove_rr(knot_changeset_t *changeset,
-                                     const knot_dname_t *owner,
-                                     const knot_rrset_t *rr,
-                                     knot_rrset_t ***removed,
-                                     size_t *removed_count)
+/*!< \brief Removes RR from list, owner and type check. */
+static void remove_header_from_list(list_t *l, const knot_rrset_t *rr)
 {
-	assert(changeset != NULL);
-	assert(removed != NULL);
-	assert(removed_count != NULL);
-
-	/*!< \todo This seems like a waste of memory to me. Also, list_size takes a long time. */
-	*removed_count = 0;
-	*removed = (knot_rrset_t **)malloc(list_size(&changeset->add)
-	                                  * sizeof(knot_rrset_t *));
-	if (*removed == NULL) {
-		ERR_ALLOC_FAILED;
-		return KNOT_ENOMEM;
-	}
+	LIST_DEL_MATCH(*l, knot_rrset_equal(list_rr, rr, KNOT_RRSET_COMPARE_HEADER));
+}
 
-	knot_rrset_t *remove = NULL;
+/*!< \brief Removes RR from list, owner check. */
+static void remove_owner_from_list(list_t *l, const knot_dname_t *owner)
+{
+	LIST_DEL_MATCH(*l, knot_dname_is_equal(list_rr->owner, owner));
+}
 
-	/*
-	 * We assume that each RR in the ADD section of the changeset is in its
-	 * own RRSet. It should be, as this is how they are stored there by the
-	 * ddns_process_add() function.
-	 */
+/* --------------------- true/false helper functions ------------------------ */
 
-	dbg_ddns_verb("Removing possible redundant RRs from changeset.\n");
-	knot_rr_ln_t *rr_node = NULL;
-	node_t *nxt = NULL;
-	WALK_LIST_DELSAFE(rr_node, nxt, changeset->add) {
-		knot_rrset_t *rrset = rr_node->rr;
-		// Removing RR(s) from this owner
-dbg_ddns_exec_detail(
-		char *name = knot_dname_to_str(rrset->owner);
-		dbg_ddns_detail("ddns: remove_rr: Removing RR of type=%u owned by %s\n",
-		                knot_rrset_type(rrset), name);
-		free(name);
-);
-		if (knot_dname_is_equal(knot_rrset_owner(rrset), owner)) {
-			// Removing one or all RRSets
-			if (rrset_empty(rr)
-			    && (knot_rrset_type(rr) == knot_rrset_type(rrset)
-			        || knot_rrset_type(rr) == KNOT_RRTYPE_ANY)) {
-				dbg_ddns_detail("Removing one or all RRSets\n");
-				remove = rrset;
-				rem_node((node_t *)rr_node);
-				(*removed)[(*removed_count)++] = remove;
-			} else if (knot_rrset_type(rr) ==
-			           knot_rrset_type(rrset)) {
-				// Removing specific RR
-				assert(knot_rrset_rr_count(rr) != 0);
-
-				// We must check if the RDATA match
-				if (knot_rrset_equal(rr, rrset,
-				                     KNOT_RRSET_COMPARE_WHOLE)) {
-					remove = rrset;
-					rem_node((node_t *)rr_node);
-					(*removed)[(*removed_count)++] = remove;
-				}
-			}
-		}
-	}
+static inline bool is_addition(const knot_rrset_t *rr)
+{
+	return rr->rclass == KNOT_CLASS_IN;
+}
 
-	return KNOT_EOK;
+static inline bool is_removal(const knot_rrset_t *rr)
+{
+	return rr->rclass == KNOT_CLASS_NONE || rr->rclass == KNOT_CLASS_ANY;
 }
 
-/*----------------------------------------------------------------------------*/
+static inline bool is_rr_removal(const knot_rrset_t *rr)
+{
+	return rr->rclass == KNOT_CLASS_NONE;
+}
 
-static void knot_ddns_check_add_rr(knot_changeset_t *changeset,
-                                   const knot_rrset_t *rr,
-                                   knot_rrset_t **removed)
+static inline bool is_rrset_removal(const knot_rrset_t *rr)
 {
-	assert(changeset != NULL);
-	assert(rr != NULL);
-	assert(removed != NULL);
+	return rr->rclass == KNOT_CLASS_ANY && rr->type != KNOT_RRTYPE_ANY;
+}
 
-	*removed = NULL;
+static inline bool is_node_removal(const knot_rrset_t *rr)
+{
+	return rr->rclass == KNOT_CLASS_ANY && rr->type == KNOT_RRTYPE_ANY;
+}
 
-	dbg_ddns_verb("Removing possible redundant RRs from changeset.\n");
-	knot_rr_ln_t *rr_node = NULL;
-	node_t *nxt = NULL;
-	WALK_LIST_DELSAFE(rr_node, nxt, changeset->remove) {
-		knot_rrset_t *rrset = rr_node->rr;
-		assert(rrset);
-		/* Just check exact match, the changeset contains only
-		 * whole RRs that have been removed.
-		 */
-		if (knot_rrset_equal(rr, rrset,
-		                     KNOT_RRSET_COMPARE_WHOLE) == 1) {
-			*removed = rrset;
-			rem_node((node_t *)rr_node);
-			break;
-		}
+/*!< \brief Returns true if last addition of certain types is to be replaced. */
+static bool should_replace(const knot_rrset_t *chg_rrset,
+                           const knot_rrset_t *rrset)
+{
+	if (rrset->type != KNOT_RRTYPE_CNAME &&
+	    rrset->type != KNOT_RRTYPE_NSEC3PARAM) {
+		return false;
+	} else {
+		return chg_rrset->type == rrset->type;
 	}
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_process_add_cname(knot_node_t *node,
-                                       const knot_rrset_t *rr,
-                                       knot_changeset_t *changeset,
-                                       knot_changes_t *changes)
+/*!< \brief Returns true if node will be empty after update application. */
+static bool node_empty(const knot_node_t *node, knot_dname_t *owner,
+                       const knot_changeset_t *changeset)
 {
-	assert(node != NULL);
-	assert(rr != NULL);
-	assert(changeset != NULL);
-	assert(changes != NULL);
-
-	dbg_ddns_detail("Adding CNAME RR.\n");
-
-	int ret = 0;
-
-	/* Get the current CNAME RR from the node. */
-	knot_rrset_t *removed = knot_node_get_rrset(node, KNOT_RRTYPE_CNAME);
-
-	if (removed != NULL) {
-		/* If they are identical, ignore. */
-		if (knot_rrset_equal(removed, rr, KNOT_RRSET_COMPARE_WHOLE)
-		    == 1) {
-			dbg_ddns_verb("CNAME identical to one in the node.\n");
-			return 1;
-		}
-
-		/* b) Store it to 'changes' */
-		ret = knot_changes_add_rrset(changes, removed, KNOT_CHANGES_OLD);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to add removed RRSet to "
-			         "'changes': %s\n", knot_strerror(ret));
-			return ret;
-		}
-
-		/* c) And remove it from the node. */
-		UNUSED(knot_node_remove_rrset(node, KNOT_RRTYPE_CNAME));
-
-		/* d) Check if this CNAME was not previously added by
-		 *    the UPDATE. If yes, remove it from the ADD
-		 *    section and do not add it to the REMOVE section.
-		 */
-		knot_rrset_t **from_chgset = NULL;
-		size_t from_chgset_count = 0;
-		ret = knot_ddns_check_remove_rr(
-		                   changeset, knot_rrset_owner(removed),
-		                   removed, &from_chgset, &from_chgset_count);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to remove possible redundant "
-			         "RRs from ADD section: %s.\n",
-			         knot_strerror(ret));
-			free(from_chgset);
-			return ret;
-		}
-
-		assert(from_chgset_count <= 1);
+	if (node == NULL && name_added(changeset, owner)) {
+		// Node created in update.
+		return false;
+	}
 
-		if (from_chgset_count == 1) {
-			/* Just delete the RRSet. */
-			knot_rrset_free(&(from_chgset[0]), NULL);
-		} else {
-			/* Otherwise copy the removed CNAME and add it
-			 * to the REMOVE section.
-			 */
-			knot_rrset_t *removed_copy;
-			ret = knot_rrset_copy(removed, &removed_copy, NULL);
-			if (ret != KNOT_EOK) {
-				dbg_ddns("Failed to copy removed RRSet:"
-				         " %s\n", knot_strerror(ret));
-				free(from_chgset);
-				return ret;
-			}
+	if (node == NULL) {
+		// Node empty.
+		return true;
+	}
 
-			ret = knot_changeset_add_rrset(
-				changeset, removed_copy, KNOT_CHANGESET_REMOVE);
-			if (ret != KNOT_EOK) {
-				knot_rrset_free(&removed_copy, NULL);
-				dbg_ddns("Failed to add removed CNAME "
-				         "to changeset: %s\n",
-				         knot_strerror(ret));
-				free(from_chgset);
-				return ret;
+	for (uint16_t i = 0; i < node->rrset_count; ++i) {
+		knot_rrset_t node_rrset = knot_node_rrset_at(node, i);
+		knot_rrset_t node_rr;
+		knot_rrset_init(&node_rr, node->owner, node_rrset.type, KNOT_CLASS_IN);
+		for (uint16_t j = 0; j < node_rrset.rrs.rr_count; ++j) {
+			knot_rr_t *add_rr = knot_rrs_rr(&node_rrset.rrs, j);
+			knot_rrs_add_rr(&node_rr.rrs, add_rr, NULL);
+			if (!removed_rr(changeset, &node_rr)) {
+				// One of the RRs from node was not removed.
+				knot_rrs_clear(&node_rr.rrs, NULL);
+				return false;
 			}
+			knot_rrs_clear(&node_rr.rrs, NULL);
 		}
-		free(from_chgset);
-	} else if (knot_node_rrset_count(node) != 0) {
-		/* 2) Other occupied node => ignore. */
-		return 1;
 	}
 
-	return KNOT_EOK;
+	return true;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_process_add_soa(knot_node_t *node,
-                                     const knot_rrset_t *rr,
-                                     knot_changes_t *changes)
+/*!< \brief Returns true if node contains given RR in its RRSets. */
+static bool node_contains_rr(const knot_node_t *node,
+                             const knot_rrset_t *rr)
 {
-	assert(node != NULL);
-	assert(rr != NULL);
-	assert(changes != NULL);
-
-	dbg_ddns_detail("Adding SOA RR.\n");
-
-	int ret = 0;
-
-	/*
-	 * Just remove the SOA from the node, together with its RRSIGs.
-	 * Adding the RR is done in the caller function. Note that only SOA
-	 * with larger SERIAL than the current one will get to these functions,
-	 * so we don't have to check the SERIALS again. But an assert won't
-	 * hurt.
-	 */
-
-	/* Get the current SOA RR from the node. */
-	knot_rrset_t *removed = knot_node_get_rrset(node, KNOT_RRTYPE_SOA);
-
-	if (removed != NULL) {
-		dbg_ddns_detail("Found SOA in the node.\n");
-		/* If they are identical, ignore. */
-		if (knot_rrset_equal(removed, rr, KNOT_RRSET_COMPARE_WHOLE)
-		    == 1) {
-			dbg_ddns_detail("Old and new SOA identical.\n");
-			return 1;
-		}
-
-		/* Check that the serial is indeed larger than the current one*/
-		assert(knot_serial_compare(knot_rdata_soa_serial(removed),
-		                         knot_rdata_soa_serial(rr)) < 0);
-
-		/* 1) Store it to 'changes', together with its RRSIGs. */
-		ret = knot_changes_add_rrset(changes, removed, KNOT_CHANGES_OLD);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to add removed RRSet to "
-			         "'changes': %s\n", knot_strerror(ret));
-			return ret;
-		}
-
-		/* 2) And remove it from the node. */
-		UNUSED(knot_node_remove_rrset(node, KNOT_RRTYPE_SOA));
-
-		/* No changeset processing needed in this case. */
+	const knot_rrs_t *zone_rrs = knot_node_rrs(node, rr->type);
+	if (zone_rrs) {
+		assert(rr->rrs.rr_count == 1);
+		const bool compare_ttls = false;
+		return knot_rrs_member(zone_rrs, knot_rrs_rr(&rr->rrs, 0),
+		                       compare_ttls);
 	} else {
-		dbg_ddns_detail("No SOA in node, ignoring.\n");
-		/* If there is no SOA in the node, ignore. */
-		return 1;
+		return false;
 	}
-
-	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_add_rr_new_normal(knot_node_t *node, knot_rrset_t *rr_copy,
-                                       knot_changes_t *changes)
+/*!< \brief Returns true if CNAME is in this node. */
+static bool adding_to_cname(const knot_dname_t *owner,
+                            const knot_node_t *node,
+                            knot_changeset_t *changeset)
 {
-	assert(node != NULL);
-	assert(rr_copy != NULL);
-	assert(changes != NULL);
-
-	dbg_ddns_verb("Adding normal RR.\n");
+	if (cname_added(changeset, owner)) {
+		// Added a CNAME in this update.
+		return true;
+	}
 
-	/* Add the RRSet to 'changes'. */
+	if (node == NULL) {
+		// Node did not exist before update.
+		return false;
+	}
 
-	int ret = knot_changes_add_rrset(changes, rr_copy, KNOT_CHANGES_NEW);
-	if (ret != KNOT_EOK) {
-		knot_rrset_free(&rr_copy, NULL);
-		dbg_ddns("Failed to store copy of the added RR: "
-		         "%s\n", knot_strerror(ret));
-		return ret;
+	knot_rrset_t cname = knot_node_rrset(node, KNOT_RRTYPE_CNAME);
+	if (knot_rrset_empty(&cname)) {
+		// Node did not contain CNAME before update.
+		return false;
 	}
 
-	/* Add the RRSet to the node. */
-	ret = knot_node_add_rrset_no_merge(node, rr_copy);
-	if (ret != KNOT_EOK) {
-		dbg_ddns("Failed to add RR to node: %s\n", knot_strerror(ret));
-		return ret;
+	if (removed_rr(changeset, &cname)) {
+		// Node did contain CNAME, but it was removed in this update.
+		return false;
 	}
 
-	return KNOT_EOK;
+	// CNAME present
+	return true;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_add_rr_merge_normal(knot_rrset_t *node_rrset_copy,
-                                         knot_rrset_t **rr_copy)
+/*!< \brief Used to ignore SOA deletions and SOAs with lower serial than zone. */
+static bool skip_soa(const knot_rrset_t *rr, int64_t sn)
 {
-	assert(node_rrset_copy != NULL);
-	assert(rr_copy != NULL);
-	assert(*rr_copy != NULL);
-
-	dbg_ddns_verb("Merging normal RR to existing RRSet.\n");
-
-	/* First check if the TTL of the new RR is equal to that of the first
-	 * RR in the node's RRSet. If not, refuse the UPDATE.
-	 */
-	if (!knot_rrset_ttl_equal(*rr_copy, node_rrset_copy)) {
-		assert(knot_rrset_type(*rr_copy) != KNOT_RRTYPE_RRSIG);
-		char type_str[16] = { '\0' };
-		knot_rrtype_to_string(knot_rrset_type(*rr_copy), type_str,
-		                      sizeof(type_str));
-		char *name = knot_dname_to_str(knot_rrset_owner(*rr_copy));
-		log_zone_error("UPDATE: TTL mismatch in %s, type %s\n",
-		               name, type_str);
-		free(name);
-		return KNOT_ETTL;
+	if (rr->type == KNOT_RRTYPE_SOA
+	    && (rr->rclass == KNOT_CLASS_NONE
+	        || rr->rclass == KNOT_CLASS_ANY
+	        || knot_serial_compare(knot_rrs_soa_serial(&rr->rrs),
+	                               sn) <= 0)) {
+		return true;
 	}
 
-	int rdata_in_copy = knot_rrset_rr_count(*rr_copy);
-	int merged = 0, deleted_rrs = 0;
-	int ret = knot_rrset_merge_sort(node_rrset_copy, *rr_copy, &merged,
-	                                &deleted_rrs, NULL);
-	dbg_ddns_detail("Merge returned: %d\n", ret);
-
-	if (ret != KNOT_EOK) {
-		dbg_ddns("Failed to merge UPDATE RR to node RRSet: %s."
-		         "\n", knot_strerror(ret));
-		return ret;
-	}
+	return false;
+}
 
-	knot_rrset_free(rr_copy, NULL);
+/* ---------------------- changeset manipulation ---------------------------- */
 
-	if (rdata_in_copy == deleted_rrs) {
-		/* All RDATA have been removed, because they were duplicates
-		 * or there were none (0). In general this means, that no
-		 * change was made.
-		 */
-		return 1;
+/*!< \brief Checks whether record should be added or replaced. */
+static bool skip_record_addition(knot_changeset_t *changeset,
+                                 knot_rrset_t *rr)
+{
+	knot_rr_ln_t *rr_node = NULL;
+	WALK_LIST(rr_node, changeset->add) {
+		knot_rrset_t *rrset = rr_node->rr;
+		if (should_replace(rr, rrset)) {
+			// Replacing singleton RR.
+			knot_rrset_free(&rrset, NULL);
+			rr_node->rr = rr;
+			return true;
+		} else if (knot_rrset_equal(rr, rrset, KNOT_RRSET_COMPARE_WHOLE)) {
+			// Freeing duplication.
+			knot_rrset_free(&rr, NULL);
+			return true;
+		}
 	}
 
-	return KNOT_EOK;
+	return false;
 }
 
-/*----------------------------------------------------------------------------*/
-/*!
- * \todo We should check, how it's possible that IXFR is not leaking due to the
- * same issue with merge. Or maybe it is, we should try it!!
- */
+/*!< \brief Adds RR into add section of changeset if it is deemed worthy. */
+static int add_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset,
+                            int *apex_ns_rem)
+{
+	knot_rrset_t *rr_copy = knot_rrset_copy(rr, NULL);
+	if (rr_copy == NULL) {
+		return KNOT_ENOMEM;
+	}
 
-/*----------------------------------------------------------------------------*/
+	rr_copy->rclass = KNOT_CLASS_IN;
 
-static int knot_ddns_add_rr(knot_node_t *node, const knot_rrset_t *rr,
-                            knot_changes_t *changes, knot_rrset_t **rr_copy)
-{
-	assert(node != NULL);
-	assert(rr != NULL);
-	assert(changes != NULL);
-	assert(rr_copy != NULL);
-
-	/* Copy the RRSet from the packet. */
-	//knot_rrset_t *rr_copy;
-	int ret = knot_rrset_copy(rr, rr_copy, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_ddns("Failed to copy RR: %s\n", knot_strerror(ret));
-		return ret;
+	if (skip_record_addition(changeset, rr_copy)) {
+		return KNOT_EOK;
 	}
 
-	/* If the RR belongs to a RRSet already present in the node, we must
-	 * take this RRSet from the node, copy it, and merge this RR into it.
-	 *
-	 * This code is more or less copied from xfr-in.c.
-	 */
-	knot_rrset_t *node_rrset_copy = NULL;
-	ret = xfrin_copy_rrset(node, rr->type, &node_rrset_copy, changes, 0);
-	if (ret < 0) {
-		dbg_ddns("Failed to copy RRSet: %s\n", knot_strerror(ret));
-		knot_rrset_free(rr_copy, NULL);
-		return ret;
+	if (apex_ns_rem) {
+		// Increase post update apex NS count.
+		(*apex_ns_rem)--;
 	}
 
-	if (node_rrset_copy == NULL) {
-		/* No such RRSet in the node. Add the whole UPDATE RRSet. */
-		dbg_ddns_detail("Adding whole UPDATE RR to the zone.\n");
-		ret = knot_ddns_add_rr_new_normal(node, *rr_copy,
-		                                  changes);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to add new RR to node.\n");
-			return ret;
-		}
-		dbg_ddns_detail("RRSet added successfully.\n");
-	} else {
-		/* We have copied the RRSet from the node. */
-		ret = knot_ddns_add_rr_merge_normal(node_rrset_copy, rr_copy);
-
-		if (ret < KNOT_EOK) {
-			dbg_ddns("Failed to merge UPDATE RR to node RRSet.\n");
-			knot_rrset_free(rr_copy, NULL);
-			knot_rrset_free(&node_rrset_copy, NULL);
-			return ret;
-		}
+	return knot_changeset_add_rrset(changeset, rr_copy, KNOT_CHANGESET_ADD);
+}
 
-		// save the new RRSet together with the new RDATA to 'changes'
-		// do not overwrite 'ret', it have to be returned
-		int r = knot_changes_add_rrset(changes, node_rrset_copy,
-		                               KNOT_CHANGES_NEW);
-		if (r != KNOT_EOK) {
-			dbg_ddns("Failed to store RRSet copy to 'changes'\n");
-			knot_rrset_free(&node_rrset_copy, NULL);
-			return r;
+/*!< \brief Checks whether record should be removed (duplicate check). */
+static bool skip_record_removal(knot_changeset_t *changeset, knot_rrset_t *rr)
+{
+	knot_rr_ln_t *rr_node = NULL;
+	WALK_LIST(rr_node, changeset->remove) {
+		knot_rrset_t *rrset = rr_node->rr;
+		if (knot_rrset_equal(rr, rrset, KNOT_RRSET_COMPARE_WHOLE)) {
+			// Removing the same RR, drop.
+			knot_rrset_free(&rr, NULL);
+			return true;
 		}
 	}
 
-	assert(ret >= 0);
-	return ret;
+	return false;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_final_soa_to_chgset(const knot_rrset_t *soa,
-                                         knot_changeset_t *changeset)
+/*!< \brief Adds RR into remove section of changeset if it is deemed worthy. */
+static int rem_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset,
+                            int *apex_ns_rem)
 {
-	assert(soa != NULL);
-	assert(changeset != NULL);
+	knot_rrset_t *rr_copy = knot_rrset_copy(rr, NULL);
+	if (rr_copy == NULL) {
+		return KNOT_ENOMEM;
+	}
 
-	knot_rrset_t *soa_copy = NULL;
-	int ret = knot_rrset_copy(soa, &soa_copy, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_ddns("Failed to copy SOA RR to the changeset: "
-			 "%s\n", knot_strerror(ret));
-		return ret;
+	rr_copy->rclass = KNOT_CLASS_IN;
+
+	if (skip_record_removal(changeset, rr_copy)) {
+		return KNOT_EOK;
 	}
 
-	knot_changeset_add_soa(changeset, soa_copy, KNOT_CHANGESET_ADD);
+	if (apex_ns_rem) {
+		// Decrease post update apex NS count.
+		(*apex_ns_rem)++;
+	}
 
-	return KNOT_EOK;
+	return knot_changeset_add_rrset(changeset, rr_copy, KNOT_CHANGESET_REMOVE);
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_add_rr_to_chgset(const knot_rrset_t *rr,
-                                      knot_changeset_t *changeset)
+/*!< \brief Adds all RRs from RRSet into remove section of changeset. */
+static int rem_rrset_to_chgset(const knot_rrset_t *rrset,
+                               knot_changeset_t *changeset,
+                               int *apex_ns_rem)
 {
-	assert(rr != NULL);
-	assert(changeset != NULL);
-
-	int ret = 0;
-	knot_rrset_t *chgset_rr = NULL;
-	knot_ddns_check_add_rr(changeset, rr, &chgset_rr);
-	if (chgset_rr == NULL) {
-		ret = knot_rrset_copy(rr, &chgset_rr, NULL);
+	knot_rrset_t rr;
+	knot_rrset_init(&rr, rrset->owner, rrset->type, rrset->rclass);
+	for (uint16_t i = 0; i < rrset->rrs.rr_count; ++i) {
+		knot_rr_t *rr_add = knot_rrs_rr(&rrset->rrs, i);
+		int ret = knot_rrs_add_rr(&rr.rrs, rr_add, NULL);
 		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to copy RR to the changeset: "
-				 "%s\n", knot_strerror(ret));
 			return ret;
 		}
-		/* No such RR in the changeset, add it. */
-		ret = knot_changeset_add_rrset(changeset, chgset_rr,
-		                               KNOT_CHANGESET_ADD);
+		ret = rem_rr_to_chgset(&rr, changeset, apex_ns_rem);
+		knot_rrs_clear(&rr.rrs, NULL);
 		if (ret != KNOT_EOK) {
-			knot_rrset_free(&chgset_rr, NULL);
-			dbg_ddns("Failed to add RR to changeset: %s.\n",
-				 knot_strerror(ret));
 			return ret;
 		}
-	} else {
-		knot_rrset_free(&chgset_rr, NULL);
 	}
 
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
+/* ------------------------ RR processing logic ----------------------------- */
 
-static int knot_ddns_process_add(const knot_rrset_t *rr,
-                                 knot_node_t **node,
-                                 knot_zone_contents_t *zone,
-                                 knot_changeset_t *changeset,
-                                 knot_changes_t *changes,
-                                 knot_rrset_t **rr_copy)
-{
-	assert(rr != NULL);
-	assert(node != NULL);
-	assert(zone != NULL);
-	assert(changeset != NULL);
-	assert(changes != NULL);
-	assert(rr_copy != NULL);
+/* --------------------------- RR additions --------------------------------- */
 
-	dbg_ddns_verb("Adding RR.\n");
+/*!< \brief Processes CNAME addition (replace or ignore) */
+static int process_add_cname(const knot_node_t *node,
+                             const knot_rrset_t *rr,
+                             knot_changeset_t *changeset)
+{
+	knot_rrset_t cname = knot_node_rrset(node, KNOT_RRTYPE_CNAME);
+	if (!knot_rrset_empty(&cname)) {
+		// If they are identical, ignore.
+		if (knot_rrset_equal(&cname, rr, KNOT_RRSET_COMPARE_WHOLE)) {
+			return KNOT_EOK;
+		}
 
-	if (*node == NULL) {
-		// create new node, connect it to the zone nodes
-		dbg_ddns_detail("Node not found. Creating new.\n");
-		int ret = knot_zone_contents_create_node(zone, rr, node);
+		int ret = rem_rr_to_chgset(&cname, changeset, NULL);
 		if (ret != KNOT_EOK) {
-			dbg_xfrin("Failed to create new node in zone.\n");
 			return ret;
 		}
-	}
-	assert(*node);
-
-	uint16_t type = knot_rrset_type(rr);
-	*rr_copy = NULL;
-	int ret = 0;
-
-	/*
-	 * First, rule out special cases: CNAME, SOA and adding to CNAME node.
-	 */
-	if (type == KNOT_RRTYPE_CNAME) {
-		ret = knot_ddns_process_add_cname(*node, rr, changeset, changes);
-	} else if (type == KNOT_RRTYPE_SOA) {
-		ret = knot_ddns_process_add_soa(*node, rr, changes);
-	} else if (type == KNOT_RRTYPE_NSEC3PARAM) {
-		if (!knot_dname_is_equal((*node)->owner, zone->apex->owner)) {
-			log_zone_error("NSEC3PARAM RR may be added under apex name only!\n");
-			return KNOT_EDENIED;
-		}
-		if (knot_node_rrset(*node, KNOT_RRTYPE_NSEC3PARAM)) {
-			/* Ignore if there is one already in the zone.*/
-			log_zone_warning("NSEC3PARAM already present in the zone. "
-			                 "Ignoring NSEC3PARAM from the UPDATE.\n");
-			return KNOT_EOK;
-		}
-	} else if (knot_node_rrset(*node, KNOT_RRTYPE_CNAME) != NULL) {
-		/*
-		 * Adding RR to CNAME node. Ignore the UPDATE RR.
-		 *
-		 * TODO: This may or may not be according to the RFC, it's quite
-		 * unclear (see 3.4.2.2)
-		 */
-		return KNOT_EOK;
-	}
 
-	if (ret == 1) {
-		dbg_ddns_detail("Ignoring the added RR.\n");
-		// Ignore
+		return add_rr_to_chgset(rr, changeset, NULL);
+	} else if (!node_empty(node, rr->owner, changeset)) {
+		// Other occupied node => ignore.
 		return KNOT_EOK;
-	} else if (ret != KNOT_EOK) {
-		dbg_ddns_detail("Adding RR failed.\n");
-		return ret;
+	} else {
+		// Can add.
+		return add_rr_to_chgset(rr, changeset, NULL);
 	}
+}
 
-	/*
-	 * In all other cases, the RR should just be added to the node.
-	 */
-
-	/* Add the RRSet to the node (RRSIGs handled in the function). */
-	dbg_ddns_detail("Adding RR to the node.\n");
-	ret = knot_ddns_add_rr(*node, rr, changes, rr_copy);
-	if (ret < 0) {
-		dbg_ddns("Failed to add RR to the node.\n");
-		return ret;
+/*!< \brief Processes CNAME addition (ignore when not removed, or non-apex) */
+static int process_add_nsec3param(const knot_node_t *node,
+                                  const knot_rrset_t *rr,
+                                  knot_changeset_t *changeset)
+{
+	if (node == NULL || !knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA)) {
+		// Ignore non-apex additions
+		char *owner = knot_dname_to_str(rr->owner);
+		log_server_warning("Refusing to add NSEC3PARAM owned "
+		                   "by %s to non-apex node.\n", owner);
+		free(owner);
+		return KNOT_EDENIED;
 	}
-
-	/*
-	 * If adding SOA, it should not be stored in the changeset.
-	 * (This is done in the calling function, and the SOA is stored in the
-	 * soa_final field.)
-	 */
-	if (type == KNOT_RRTYPE_SOA) {
-		return KNOT_EOK;
+	knot_rrset_t param = knot_node_rrset(node, KNOT_RRTYPE_NSEC3PARAM);
+	if (knot_rrset_empty(&param) || removed_rr(changeset, &param)) {
+		return add_rr_to_chgset(rr, changeset, NULL);
 	}
 
-	/* Add the RR to ADD section of the changeset. */
-	/* If the RR was previously removed, do not add it to the
-	 * changeset, and remove the entry from the REMOVE section.
-	 *
-	 * If there was no change (i.e. all RDATA were duplicates), do not add
-	 * the RR to the changeset.
-	 */
-	if (ret == KNOT_EOK) {
-		dbg_ddns_detail("Adding RR to the changeset.\n");
-		ret = knot_ddns_add_rr_to_chgset(rr, changeset);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to add the UPDATE RR to the changeset."
-			         "\n");
-			return ret;
-		}
-	}
+	char *owner = knot_dname_to_str(rr->owner);
+	log_server_warning("Refusing to add NSEC3PARAM owned "
+	                   "by %s. NSEC3PARAM already present, remove it first.\n",
+	                   owner);
+	free(owner);
 
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
 /*!
- * \todo Geez, this is soooooo long even I don't exactly know what it does...
- *       Refactor!
+ * \brief Processes SOA addition (ignore when non-apex), lower serials
+ *        dropped before.
  */
-static int knot_ddns_process_rem_rr(const knot_rrset_t *rr,
-                                    knot_node_t *node,
-                                    knot_zone_contents_t *zone,
-                                    knot_changeset_t *changeset,
-                                    knot_changes_t *changes, uint16_t qclass)
+static int process_add_soa(const knot_node_t *node,
+                           const knot_rrset_t *rr,
+                           knot_changeset_t *changeset)
 {
-	assert(rr != NULL);
-	assert(node != NULL);
-	assert(zone != NULL);
-	assert(changeset != NULL);
-	assert(changes != NULL);
-
-	uint16_t type = knot_rrset_type(rr);
-	dbg_ddns_verb("Removing one RR.\n");
-
-	/*
-	 * When doing changes to RRSets, we must:
-	 * 1) Copy the RRSet (same as in IXFR changeset applying, maybe the
-	 *    function xfrin_copy_rrset() may be used for this).
-	 * 2) Remove the RDATA (in this case only one). Check if it is not the
-	 *    last NS RR in the zone.
-	 * 3) Store the removed RDATA in 'changes'.
-	 * 4) If the RRSet is empty, remove it and store in 'changes'.
-	 * 5) Check redundant RRs in changeset.
-	 * 6) Store the RRSet containing the one RDATA in the changeset. We may
-	 *    use the RRSet from the packet for this - copy it, set CLASS
-	 *    and TTL.
-	 *
-	 */
-
-	assert(type != KNOT_RRTYPE_SOA);
-	int is_apex = knot_node_rrset(node, KNOT_RRTYPE_SOA) != NULL;
-
-	/* If removing NS from an apex and there is only one NS left, ignore
-	 * this removal right away. We do not have to check if the RRs match:
-	 * - if they don't match, the removal will be ignored
-	 * - if they match, the last NS cannot be removed anyway.
-	 */
-	if (is_apex && type == KNOT_RRTYPE_NS
-	    && knot_rrset_rr_count(knot_node_rrset(node, type)) == 1) {
+	if (node == NULL || !knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA)) {
+		// Adding SOA to non-apex node, ignore
 		return KNOT_EOK;
 	}
 
-	/*
-	 * 1) Copy the RRSet.
-	 */
-	knot_rrset_t *rrset_copy = NULL;
-	int ret = xfrin_copy_rrset(node, type, &rrset_copy, changes, 1);
-	if (ret < 0) {
-		dbg_ddns("Failed to copy RRSet for removal: %s\n",
-		         knot_strerror(ret));
-		return ret;
-	}
-
-	if (rrset_copy == NULL) {
-		dbg_ddns_verb("RRSet not found.\n");
+	// Get current SOA RR.
+	knot_rrset_t removed = knot_node_rrset(node, KNOT_RRTYPE_SOA);
+	if (knot_rrset_equal(&removed, rr, KNOT_RRSET_COMPARE_WHOLE)) {
+		// If they are identical, ignore.
 		return KNOT_EOK;
 	}
 
-	/*
-	 * Set some variables needed, according to the modified RR type.
-	 */
-
-	knot_rrset_t *to_modify = rrset_copy;
-
-	/*
-	 * 2) Remove the proper RDATA from the RRSet copy
-	 */
-	knot_rrset_t *rr_remove = NULL;
-	ret = knot_rrset_remove_rr_using_rrset(to_modify, rr, &rr_remove, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_ddns("ddns: proces_rem_rr: Could not remove RDATA from"
-		         " RRSet (%s).\n", knot_strerror(ret));
-		return ret;
+	// Store SOA copy into changeset.
+	if (changeset->soa_to != NULL) {
+		// Discard previous SOA - "There can be only one!"
+		knot_rrset_free(&changeset->soa_to, NULL);
 	}
-
-	/* No such RR in the RRSet. */
-	if (knot_rrset_rr_count(rr_remove) == 0) {
-		knot_rrset_free(&rr_remove, NULL);
-		dbg_ddns_detail("No such RR found to be removed.\n");
-		return KNOT_EOK;
+	knot_rrset_t *soa_cpy = knot_rrset_copy(rr, NULL);
+	if (soa_cpy == NULL) {
+		return KNOT_ENOMEM;
 	}
 
-	/* If we removed NS from apex, there should be at least one more. */
-	assert(!is_apex || type != KNOT_RRTYPE_NS
-	       || knot_rrset_rr_count(rrset_copy));
+	knot_changeset_add_soa(changeset, soa_cpy, KNOT_CHANGESET_ADD);
+	return KNOT_EOK;
+}
 
-	/*
-	 * 3) If the RRSet is empty, remove it and store in 'changes'.
-	 */
-	if (knot_rrset_rr_count(to_modify) == 0) {
-		// The RRSet should not be empty if we were removing NSs from
-		// apex in case of DDNS
-//		assert(!is_apex);
-		// add the removed RRSet to list of old RRSets
-		ret = knot_changes_add_rrset(changes, rrset_copy,
-		                             KNOT_CHANGES_OLD);
-		if (ret != KNOT_EOK) {
-		knot_rrset_free(&rr_remove, NULL);
-			dbg_ddns("Failed to add RRSet to changes.\n");
-			return ret;
-		}
+/*!< \brief Adds normal RR, ignores when CNAME exists in node. */
+static int process_add_normal(const knot_node_t *node,
+                              const knot_rrset_t *rr,
+                              knot_changeset_t *changeset,
+                              int *apex_ns_rem)
+{
+	if (adding_to_cname(rr->owner, node, changeset)) {
+		// Adding RR to CNAME node, ignore.
+		return KNOT_EOK;
+	}
 
-		knot_rrset_t *tmp = knot_node_remove_rrset(node, type);
-		dbg_xfrin_detail("Removed whole RRSet (%p).\n", tmp);
-		assert(tmp == rrset_copy);
+	if (node && node_contains_rr(node, rr)) {
+		// Adding existing RR, remove removal from changeset if it's there.
+		remove_rr_from_list(&changeset->remove, rr);
+		// And ignore.
+		return KNOT_EOK;
 	}
 
-	/*
-	 * 4) Check if the RR is not in the ADD section. If yes, remove it
-	 *    from there and do not add it to the REMOVE section.
+	/* First check if the TTL of the new RR is equal to that of the first
+	 * RR in the node's RRSet. If not, refuse the UPDATE.
 	 */
-	knot_rrset_t **from_chgset = NULL;
-	size_t from_chgset_count = 0;
-	ret = knot_ddns_check_remove_rr(changeset, knot_node_owner(node),
-	                                rr, &from_chgset, &from_chgset_count);
-	if (ret != KNOT_EOK) {
-	knot_rrset_free(&rr_remove, NULL);
-		dbg_ddns("Failed to remove possible redundant RRs from ADD "
-		         "section: %s.\n", knot_strerror(ret));
-		free(from_chgset);
-		return ret;
+	knot_rrset_t rr_in_zone = knot_node_rrset(node, rr->type);
+	if (knot_node_rrtype_exists(node, rr->type) &&
+	    knot_rrset_rr_ttl(rr, 0) != knot_rrset_rr_ttl(&rr_in_zone, 0)) {
+		return KNOT_ETTL;
 	}
 
-	assert(from_chgset_count <= 1);
+	const bool apex_ns = knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA) &&
+	                     rr->type == KNOT_RRTYPE_NS;
+	return add_rr_to_chgset(rr, changeset, apex_ns ? apex_ns_rem : NULL);
+}
 
-	if (from_chgset_count == 1) {
-		knot_rrset_free(&(from_chgset[0]), NULL);
-		knot_rrset_free(&rr_remove, NULL);
-		/* Finish processing, no adding to changeset. */
-		free(from_chgset);
-		return KNOT_EOK;
+/*!< \brief Decides what to do with RR addition. */
+static int process_add(const knot_rrset_t *rr,
+                       const knot_node_t *node,
+                       knot_changeset_t *changeset,
+                       int *apex_ns_rem)
+{
+	switch(rr->type) {
+	case KNOT_RRTYPE_CNAME:
+		return process_add_cname(node, rr, changeset);
+	case KNOT_RRTYPE_SOA:
+		return process_add_soa(node, rr, changeset);
+	case KNOT_RRTYPE_NSEC3PARAM:
+		return process_add_nsec3param(node, rr, changeset);
+	default:
+		return process_add_normal(node, rr, changeset, apex_ns_rem);
 	}
+}
 
-	free(from_chgset);
-
+/* --------------------------- RR deletions --------------------------------- */
 
-	/*
-	 * 5) Store the removed data in 'changes'.
-	 */
-	ret = knot_changes_add_rrset(changes, rr_remove, KNOT_CHANGES_OLD);
-	if (ret != KNOT_EOK) {
-		knot_rrset_free(&rr_remove, NULL);
-		dbg_ddns_detail("Failed to add data to changes.\n");
-		return ret;
+/*!< \brief Removes single RR from zone. */
+static int process_rem_rr(const knot_rrset_t *rr,
+                          const knot_node_t *node,
+                          knot_changeset_t *changeset,
+                          int *apex_ns_rem)
+{
+	const bool apex_ns = knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA) &&
+	                     rr->type == KNOT_RRTYPE_NS;
+	if (apex_ns) {
+		const knot_rrs_t *ns_rrs = knot_node_rrs(node, KNOT_RRTYPE_NS);
+		if (*apex_ns_rem == ns_rrs->rr_count - 1) {
+			// Cannot remove last apex NS RR
+			return KNOT_EOK;
+		}
 	}
 
-	/*
-	 * 6) Store the RRSet containing the one RDATA in the changeset. We may
-	 *    use the RRSet from the packet for this - copy it, set CLASS
-	 *    and TTL.
-	 */
-	knot_rrset_t *to_chgset = NULL;
-	ret = knot_rrset_copy(rr, &to_chgset, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_ddns("Failed to copy RRSet from packet to changeset.\n");
-		return ret;
+	// Remove possible previously added RR
+	remove_rr_from_list(&changeset->add, rr);
+	if (node == NULL) {
+		// Removing from node that did not exists before update
+		return KNOT_EOK;
 	}
-	to_chgset->rclass = qclass;
-	uint16_t rr_count = knot_rrset_rr_count(to_chgset);
-	for (uint16_t i = 0; i < rr_count; ++i) {
-		knot_rrset_rr_set_ttl(to_chgset,
-		                      knot_rrset_rr_ttl(to_modify, i), i);
+
+	knot_rrset_t to_modify = knot_node_rrset(node, rr->type);
+	if (knot_rrset_empty(&to_modify)) {
+		// No such RRSet
+		return KNOT_EOK;
 	}
 
-	ret = knot_changeset_add_rrset(changeset, to_chgset,
-	                               KNOT_CHANGESET_REMOVE);
+	knot_rrset_t intersection;
+	knot_rrset_init(&intersection, to_modify.owner, to_modify.type, KNOT_CLASS_IN);
+	int ret = knot_rrs_intersect(&to_modify.rrs, &rr->rrs, &intersection.rrs,
+	                             NULL);
 	if (ret != KNOT_EOK) {
-		dbg_ddns("Failed to store the RRSet copy to changeset: %s.\n",
-		         knot_strerror(ret));
-		knot_rrset_free(&to_chgset, NULL);
 		return ret;
 	}
 
-	return KNOT_EOK;
+	if (knot_rrset_empty(&intersection)) {
+		// No such RR
+		return KNOT_EOK;
+	}
+	assert(intersection.rrs.rr_count == 1);
+	ret = rem_rr_to_chgset(&intersection, changeset,
+	                       apex_ns ? apex_ns_rem : NULL);
+	knot_rrs_clear(&intersection.rrs, NULL);
+	return ret;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_process_rem_rrset(const knot_rrset_t *rrset,
-                                       knot_node_t *node,
-                                       knot_changeset_t *changeset,
-                                       knot_changes_t *changes)
+/*!< \brief Removes RRSet from zone. */
+static int process_rem_rrset(const knot_rrset_t *rrset,
+                             const knot_node_t *node,
+                             knot_changeset_t *changeset)
 {
-	assert(node != NULL);
-	assert(rrset != NULL);
-	assert(changeset != NULL);
-	assert(changes != NULL);
-
-	uint16_t type = knot_rrset_type(rrset);
-
-	// this should be ruled out before
-	assert(type != KNOT_RRTYPE_SOA);
-
-	if (knot_node_rrset(node, KNOT_RRTYPE_SOA) != NULL
-	    && type == KNOT_RRTYPE_NS) {
-		// if removing NS from apex, ignore
+	if (rrset->type == KNOT_RRTYPE_SOA ||
+	    knot_rrtype_is_ddns_forbidden(rrset->type)) {
+		// Ignore SOA and DNSSEC removals.
 		return KNOT_EOK;
 	}
 
-	knot_rrset_t **removed = NULL;
-	size_t removed_count = 0;
-	int ret = 0;
-
-	/* Remove the RRSet from the node. */
-	removed = malloc(sizeof(knot_rrset_t *));
-	if (!removed) {
-		ERR_ALLOC_FAILED;
-		return KNOT_ENOMEM;
+	if (knot_node_rrtype_exists(node, KNOT_RRTYPE_SOA) &&
+	    rrset->type == KNOT_RRTYPE_NS) {
+		// Ignore NS apex RRSet removals.
+		return KNOT_EOK;
 	}
 
-	dbg_ddns_detail("Removing RRSet of type: %d\n", type);
+	// Remove all previously added RRs with this owner and type from changeset
+	remove_header_from_list(&changeset->add, rrset);
+	if (node == NULL) {
+		return KNOT_EOK;
+	}
 
-	*removed = knot_node_remove_rrset(node, type);
-	if (*removed != NULL) {
-		removed_count = 1;
-	} else {
-		removed_count = 0;
+	if (!knot_node_rrtype_exists(node, rrset->type)) {
+		// no such RR, ignore
+		return KNOT_EOK;
 	}
 
-	dbg_ddns_detail("Removed: %p (first item: %p), removed count: %zu\n",
-	                removed, (removed == NULL) ? (void *)"none" : *removed,
-	                removed_count);
+	knot_rrset_t to_remove = knot_node_rrset(node, rrset->type);
+	return rem_rrset_to_chgset(&to_remove, changeset, NULL);
+}
+
+/*!< \brief Removes node from zone. */
+static int process_rem_node(const knot_rrset_t *rr,
+                            const knot_node_t *node, knot_changeset_t *changeset)
+{
+	// Remove all previously added records with given owner from changeset
+	remove_owner_from_list(&changeset->add, rr->owner);
 
-	// no such RR
-	if (removed_count == 0) {
-		// ignore
-		free(removed);
+	if (node == NULL) {
 		return KNOT_EOK;
 	}
 
-	/* 2) Store them to 'changes' for later deallocation, together with
-	 *    their RRSIGs.
-	 */
-	for (uint i = 0; i < removed_count; ++i) {
-		ret = knot_changes_add_rrset(changes, removed[i],
-		                             KNOT_CHANGES_OLD);
+	// Remove all RRSets from node
+	for (int i = 0; i < node->rrset_count; ++i) {
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
+		int ret = process_rem_rrset(&rrset, node, changeset);
 		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to add removed "
-			         "RRSet to 'changes': %s.\n",
-			         knot_strerror(ret));
-			free(removed);
 			return ret;
 		}
 	}
 
-	/* 3) Copy the RRSets, so that they can be stored to the changeset. */
-	knot_rrset_t **to_chgset = malloc(removed_count
-	                                  * sizeof(knot_rrset_t *));
-	if (to_chgset == NULL) {
-		dbg_ddns("Failed to allocate space for RRSets going to "
-		         "changeset.\n");
-		free(removed);
-		return KNOT_ENOMEM;
-	}
+	return KNOT_EOK;
+}
 
-	for (int i = 0; i < removed_count; ++i) {
-		ret = knot_rrset_copy(removed[i], &to_chgset[i], NULL);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to copy the removed RRSet: %s.\n",
-			         knot_strerror(ret));
-			for (int j = 0; j < i; ++j) {
-				knot_rrset_free(&to_chgset[j], NULL);
-			}
-			free(to_chgset);
-			free(removed);
-			return ret;
-		}
+/*!< \brief Decides what to with removal. */
+static int process_remove(const knot_rrset_t *rr,
+                          const knot_node_t *node,
+                          knot_changeset_t *changeset,
+                          int *apex_ns_rem)
+{
+	if (is_rr_removal(rr)) {
+		return process_rem_rr(rr, node, changeset, apex_ns_rem);
+	} else if (is_rrset_removal(rr)) {
+		return process_rem_rrset(rr, node, changeset);
+	} else if (is_node_removal(rr)) {
+		return process_rem_node(rr, node, changeset);
+	} else {
+		return KNOT_EINVAL;
 	}
+}
 
-	free(removed);
+/* --------------------------- validity checks ------------------------------ */
 
-	/* 4) But we must check if some of the RRs were not previously added
-	 *    by the same UPDATE. If yes, these must be removed from the ADD
-	 *    section of the changeset and also from this RRSet copy (so they
-	 *    are neither stored in the REMOVE section of the changeset).
-	 */
-	knot_rrset_t **from_chgset = NULL;
-	size_t from_chgset_count = 0;
-
-	/* 4 a) Remove redundant RRs from the ADD section of the changeset. */
-	knot_dname_t *owner_copy = knot_dname_copy(rrset->owner);
-	knot_rrset_t *empty_rrset =
-		knot_rrset_new(owner_copy, type, rrset->rclass, NULL);
-	if (empty_rrset == NULL) {
-		free(to_chgset);
-		knot_dname_free(&owner_copy);
-		return KNOT_ENOMEM;
-	}
-	ret = knot_ddns_check_remove_rr(changeset, knot_node_owner(node),
-	                                 empty_rrset, &from_chgset,
-	                                 &from_chgset_count);
-	if (ret != KNOT_EOK) {
-		dbg_ddns("Failed to remove possible redundant RRs from ADD "
-		         "section: %s.\n", knot_strerror(ret));
-		for (int i = 0; i < removed_count; ++i) {
-			knot_rrset_free(&to_chgset[i], NULL);
-		}
-		free(from_chgset);
-		free(to_chgset);
-		knot_rrset_free(&empty_rrset, NULL);
-		return ret;
-	}
-	knot_rrset_free(&empty_rrset, NULL);
-
-	/* 4 b) Remove these RRs from the copy of the RRSets removed from zone*/
-	for (int j = 0; j < removed_count; ++j) {
-		/* In each RRSet removed from the node (each can have more
-		 * RDATAs) ...
-		 */
-		for (int i = 0; i < from_chgset_count; ++i) {
-			/* ...try to remove redundant RDATA. Each RRSet in
-			 * 'from_chgset' contains only one RDATA.
-			 */
-			knot_rrset_t *removed = NULL;
-			ret = knot_rrset_remove_rr_using_rrset(to_chgset[j],
-			                                  from_chgset[i], &removed, NULL);
-			if (ret != KNOT_EOK) {
-				dbg_ddns("Failed to remove RR from RRSet"
-				         "(%s).\n", knot_strerror(ret));
-				free(from_chgset);
-				free(to_chgset);
-				return ret;
-			}
-			knot_rrset_free(&removed, NULL);
-		}
+/*!< \brief Checks whether addition has not violated DNAME rules. */
+static bool sem_check(const knot_rrset_t *rr,
+                      const knot_node_t *zone_node,
+                      const knot_zone_contents_t *zone)
+{
+	// Check that we have not added DNAME child
+	const knot_dname_t *parent_dname = knot_wire_next_label(rr->owner, NULL);
+	const knot_node_t *parent =
+		knot_zone_contents_find_node(zone, parent_dname);
+	if (parent == NULL) {
+		return true;
 	}
 
-	/* The array is cleared, we may delete the redundant RRs. */
-	for (int i = 0; i < from_chgset_count; ++i) {
-		knot_rrset_free(&from_chgset[i], NULL);
+	if (knot_node_rrtype_exists(parent, KNOT_RRTYPE_DNAME)) {
+		// Parent has DNAME RRSet, refuse update
+		return false;
 	}
-	free(from_chgset);
 
-	/* 5) Store the remaining RRSet to the changeset. Do not try to merge
-	 *    to some previous RRSet, there should be none.
-	 */
-	for (int i = 0; i < removed_count; ++i) {
-		if (knot_rrset_rr_count(to_chgset[i]) == 0) {
-			// Empty RRs caused by add + remove combo, skip.
-			knot_rrset_free(&to_chgset[i], NULL);
-			continue;
-		}
-		ret = knot_changeset_add_rrset(changeset, to_chgset[i],
-		                               KNOT_CHANGESET_REMOVE);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to store the RRSet copy to changeset: "
-			         "%s.\n", knot_strerror(ret));
-			for (int j = i; j < removed_count; ++j) {
-				knot_rrset_free(&to_chgset[j], NULL);
-			}
-			free(to_chgset);
-			return ret;
-		}
+	if (rr->type != KNOT_RRTYPE_DNAME || zone_node == NULL) {
+		return true;
 	}
 
-	free(to_chgset);
+	// Check that we have not created node with DNAME children.
+	if (zone_node->children > 0) {
+		// Updated node has children and DNAME was added, refuse update
+		return false;
+	}
 
-	return KNOT_EOK;
+	return true;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_process_rem_all(knot_node_t *node,
-                                     knot_changeset_t *changeset,
-                                     knot_changes_t *changes)
+/*!< \brief Checks whether we can accept this RR. */
+static int check_update(const knot_rrset_t *rrset,
+                                  const knot_pkt_t *query,
+                                  uint16_t *rcode)
 {
-	assert(node != NULL);
-	assert(changeset != NULL);
-	assert(changes != NULL);
-
-	/*! \note
-	 * This basically means to call knot_ddns_process_rem_rrset() for every
-	 * type present in the node.
-	 *
-	 * In case of SOA and NS in apex, the RRSets should not be removed, but
-	 * what about their RRSIGs??
-	 *
-	 * If the zone has to remain properly signed, the UPDATE will have to
-	 * contain at least new SOA and RRSIGs for it (as the auto-incremented
-	 * SOA would not be signed). So it should not matter if we leave the
-	 * RRSIGs there or not. But in case of the NSs it's not that clear.
-	 *
-	 * For now, we will leave the RRSIGs there. It's easier to implement.
-	 *
-	 * \todo Should document this!!
-	 */
-	int ret = 0;
-	// The copy of rrsets is important here.
-	knot_rrset_t **rrsets = knot_node_get_rrsets(node);
-	int count = knot_node_rrset_count(node);
-
-	if (rrsets == NULL && count != 0) {
-		dbg_ddns("Failed to fetch RRSets from node.\n");
-		return KNOT_ENOMEM;
+	/* Accept both subdomain and dname match. */
+	const knot_dname_t *owner = rrset->owner;
+	const knot_dname_t *qname = knot_pkt_qname(query);
+	int is_sub = knot_dname_is_sub(owner, qname);
+	if (!is_sub && knot_dname_cmp(owner, qname) != 0) {
+		*rcode = KNOT_RCODE_NOTZONE;
+		return KNOT_EOUTOFZONE;
 	}
 
-	int is_apex = knot_node_rrset(node, KNOT_RRTYPE_SOA) != NULL;
+	if (knot_rrtype_is_ddns_forbidden(rrset->type)) {
+		*rcode = KNOT_RCODE_REFUSED;
+		log_zone_error("Refusing to update DNSSEC-related record!\n");
+		return KNOT_EDENIED;
+	}
 
-	dbg_ddns_verb("Removing all RRSets (count: %d).\n", count);
-	for (int i = 0; i < count; ++i) {
-		// Skip DNSSEC records - automatic signing will handle this
-		if (knot_rrtype_is_ddns_forbidden(rrsets[i]->type)) {
-			continue;
+	if (rrset->rclass == knot_pkt_qclass(query)) {
+		if (knot_rrtype_is_metatype(rrset->type)) {
+			*rcode = KNOT_RCODE_FORMERR;
+			return KNOT_EMALF;
 		}
-		// If the node is apex, skip NS and SOA as well
-		if (is_apex &&
-		    (knot_rrset_type(rrsets[i]) == KNOT_RRTYPE_SOA
-		     || knot_rrset_type(rrsets[i]) == KNOT_RRTYPE_NS)) {
-			continue;
+	} else if (rrset->rclass == KNOT_CLASS_ANY) {
+		if (!rrset_empty(rrset)
+		    || (knot_rrtype_is_metatype(rrset->type)
+		        && rrset->type != KNOT_RRTYPE_ANY)) {
+			*rcode = KNOT_RCODE_FORMERR;
+			return KNOT_EMALF;
 		}
-
-		ret = knot_ddns_process_rem_rrset(rrsets[i], node, changeset,
-		                                  changes);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to remove RRSet: %s\n",
-			         knot_strerror(ret));
-			free(rrsets);
-			return ret;
+	} else if (rrset->rclass == KNOT_CLASS_NONE) {
+		if (knot_rrset_rr_ttl(rrset, 0) != 0
+		    || knot_rrtype_is_metatype(rrset->type)) {
+			*rcode = KNOT_RCODE_FORMERR;
+			return KNOT_EMALF;
 		}
+	} else {
+		*rcode = KNOT_RCODE_FORMERR;
+		return KNOT_EMALF;
 	}
 
-	free(rrsets);
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int knot_ddns_process_rr(const knot_rrset_t *rr,
-                                knot_zone_contents_t *zone,
+/*!< \brief Checks RR and decides what to do with it. */
+static int process_rr(const knot_rrset_t *rr,
+                                const knot_zone_contents_t *zone,
                                 knot_changeset_t *changeset,
-                                knot_changes_t *changes, uint16_t qclass,
-                                knot_rrset_t **rr_copy)
+                                int *apex_ns_rem)
 {
-	assert(rr != NULL);
-	assert(zone != NULL);
-	assert(changeset != NULL);
-	assert(changes != NULL);
-	assert(rr_copy != NULL);
-
-	/* 1) Find node that will be affected. */
-	knot_node_t *node = knot_zone_contents_get_node(zone, rr->owner);
-
-	/* 2) Decide what to do. */
-	int ret = KNOT_EOK;
-	if (knot_rrset_class(rr) == knot_zone_contents_class(zone)) {
-		ret = knot_ddns_process_add(rr, &node, zone, changeset,
-		                            changes, rr_copy);
-	} else if (node == NULL) {
-		// Removing from non-existing node, just ignore the entry
-		return KNOT_EOK;
-	} else if (knot_rrset_class(rr) == KNOT_CLASS_NONE) {
-		ret = knot_ddns_process_rem_rr(rr, node, zone, changeset,
-		                               changes, qclass);
-	} else if (knot_rrset_class(rr) == KNOT_CLASS_ANY) {
-		if (knot_rrset_type(rr) == KNOT_RRTYPE_ANY) {
-			ret = knot_ddns_process_rem_all(node, changeset,
-			                                changes);
-		} else {
-			ret = knot_ddns_process_rem_rrset(rr, node, changeset,
-			                                  changes);
+	const knot_node_t *node = knot_zone_contents_find_node(zone, rr->owner);
+	if (is_addition(rr)) {
+		int ret = process_add(rr, node, changeset, apex_ns_rem);
+		if (ret == KNOT_EOK) {
+			if (!sem_check(rr, node, zone)) {
+				return KNOT_EDENIED;
+			}
 		}
+		return ret;
+	} else if (is_removal(rr)) {
+		return process_remove(rr, node, changeset, apex_ns_rem);
 	} else {
-		return KNOT_ERROR;
+		return KNOT_EMALF;
 	}
+}
 
-	if (ret == KNOT_EOK) {
-		assert(node);
-		// Do a semantic check for changed node.
-		err_handler_t handler;
-		err_handler_init(&handler);
-		bool fatal = false;
-		ret = sem_check_node_plain(zone, node, &handler, true, &fatal);
-		if (ret == KNOT_EOK) {
-			if (fatal) {
-				ret = KNOT_EDENIED;
-			}
+/*!< \brief Maps Knot return code to RCODE. */
+static uint16_t ret_to_rcode(int ret)
+{
+	if (ret == KNOT_EMALF) {
+		return KNOT_RCODE_FORMERR;
+	} else if (ret == KNOT_EDENIED) {
+		return KNOT_RCODE_REFUSED;
+	} else {
+		return KNOT_RCODE_SERVFAIL;
+	}
+}
+
+/* ---------------------------------- API ----------------------------------- */
+
+int knot_ddns_process_prereqs(const knot_pkt_t *query, const knot_zone_contents_t *zone,
+                              uint16_t *rcode)
+{
+	if (query == NULL || rcode == NULL || zone == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	int ret = KNOT_EOK;
+	list_t rrset_list; // List used to store merged RRSets
+	init_list(&rrset_list);
+
+	const knot_pktsection_t *answer = knot_pkt_section(query, KNOT_ANSWER);
+	for (int i = 0; i < answer->count; ++i) {
+		// Check what can be checked, store full RRs into list
+		ret = process_prereq(&answer->rr[i], knot_pkt_qclass(query),
+		                     zone, rcode, &rrset_list);
+		if (ret != KNOT_EOK) {
+			rrset_list_clear(&rrset_list);
+			return ret;
 		}
 	}
 
+	// Check stored RRSets
+	ret = check_stored_rrsets(&rrset_list, zone, rcode);
+	rrset_list_clear(&rrset_list);
 	return ret;
 }
 
-/*----------------------------------------------------------------------------*/
-/*
- * NOTES:
- * - 'zone' must be a copy of the current zone.
- * - changeset must be allocated
- * - changes must be allocated
- *
- * All this is done in the first parts of xfrin_apply_changesets() - extract
- * to separate function, if possible.
- *
- * If anything fails, rollback must be done. The xfrin_rollback_update() may
- * be good for this.
- */
-int knot_ddns_process_update(knot_zone_contents_t *zone,
+int knot_ddns_process_update(const knot_zone_contents_t *zone,
                              const knot_pkt_t *query,
                              knot_changeset_t *changeset,
-                             knot_changes_t *changes,
                              uint16_t *rcode, uint32_t new_serial)
 {
-	if (zone == NULL || query == NULL || changeset == NULL || rcode == NULL
-	    || changes == NULL) {
+	if (zone == NULL || query == NULL || changeset == NULL || rcode == NULL) {
 		return KNOT_EINVAL;
 	}
 
-	int ret = KNOT_EOK;
-
 	/* Copy base SOA RR. */
-	const knot_rrset_t *soa = knot_node_rrset(knot_zone_contents_apex(zone),
-						  KNOT_RRTYPE_SOA);
-	knot_rrset_t *soa_begin = NULL;
-	knot_rrset_t *soa_end = NULL;
-	ret = knot_rrset_copy(soa, &soa_begin, NULL);
-	if (ret == KNOT_EOK) {
-		knot_changeset_add_soa(changeset, soa_begin,
-		                       KNOT_CHANGESET_REMOVE);
-	} else {
-		*rcode = KNOT_RCODE_SERVFAIL;
-		return ret;
-	}
-
-	/* Current SERIAL */
-	int64_t sn = knot_rdata_soa_serial(soa_begin);
-	int64_t sn_new;
+	knot_rrset_t *soa_begin = knot_node_create_rrset(zone->apex,
+	                                                 KNOT_RRTYPE_SOA);
+	knot_changeset_add_soa(changeset, soa_begin, KNOT_CHANGESET_REMOVE);
 
-	/* Set the new serial according to policy. */
-	if (sn > -1) {
-		sn_new = new_serial;
-		assert(sn_new != KNOT_EINVAL);
-	} else {
-		*rcode = KNOT_RCODE_SERVFAIL;
-		return ret;
-	}
+	int64_t sn_old = knot_zone_serial(zone);
 
 	/* Process all RRs the Authority (Update) section. */
 
-	const knot_rrset_t *rr = NULL;
-	knot_rrset_t *rr_copy = NULL;
-
 	dbg_ddns("Processing UPDATE section.\n");
+	int apex_ns_rem = 0;
 	const knot_pktsection_t *authority = knot_pkt_section(query, KNOT_AUTHORITY);
-	for (int i = 0; i < authority->count; ++i) {
-
-		rr = authority->rr[i];
+	for (uint16_t i = 0; i < authority->count; ++i) {
+		const knot_rrset_t *rr = &authority->rr[i];
 
 		/* Check if the entry is correct. */
-		ret = knot_ddns_check_update(rr, query, rcode);
+		int ret = check_update(rr, query, rcode);
 		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to check update RRSet:%s\n",
-			                knot_strerror(ret));
 			return ret;
 		}
 
-		/* Check if the record is SOA. If yes, check the SERIAL.
-		 * If this record should cause the SOA to be replaced in the
-		 * zone, use it as the ending SOA.
-		 *
-		 * Also handle cases where there are multiple SOAs to be added
-		 * in the same UPDATE. The one with the largest SERIAL should
-		 * be used.
-		 *
-		 * TODO: If there are more SOAs in the UPDATE one after another,
-		 *       the ddns_add_update() function will merge them into a
-		 *       RRSet. This should be handled somehow.
-		 *
-		 * If the serial is not larger than the current zone serial,
-		 * ignore the record and continue. This will ensure that the
-		 * RR processing function receives only SOA RRs that should be
-		 * added to the zone (replacing the old one).
-		 */
-		if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA
-		    && (knot_rrset_class(rr) == KNOT_CLASS_NONE
-		        || knot_rrset_class(rr) == KNOT_CLASS_ANY
-		        || knot_serial_compare(knot_rdata_soa_serial(rr),
-		                               sn) <= 0)) {
-			// This ignores also SOA removals
-			dbg_ddns_verb("Ignoring SOA...\n");
+		if (skip_soa(rr, sn_old)) {
 			continue;
 		}
 
-		dbg_ddns_verb("Processing RR %p...\n", rr);
-		ret = knot_ddns_process_rr(rr, zone, changeset, changes,
-		                           knot_pkt_qclass(query),
-		                           &rr_copy);
-
+		ret = process_rr(rr, zone, changeset, &apex_ns_rem);
 		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to process update RR:%s\n",
-			         knot_strerror(ret));
-			if (ret == KNOT_EMALF) {
-				*rcode = KNOT_RCODE_FORMERR;
-			} else if (ret == KNOT_EDENIED) {
-				*rcode = KNOT_RCODE_REFUSED;
-			} else {
-				*rcode = KNOT_RCODE_SERVFAIL;
-			}
+			*rcode = ret_to_rcode(ret);
 			return ret;
 		}
-
-		// we need the RR copy, that's why this code is here
-		if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) {
-			int64_t sn_rr = knot_rdata_soa_serial(rr);
-			dbg_ddns_verb("Replacing SOA. Old serial: %"PRId64", "
-			              "new serial: %"PRId64"\n", sn_new, sn_rr);
-			assert(knot_serial_compare(sn_rr, sn) > 0);
-			assert(rr_copy != NULL);
-			sn_new = sn_rr;
-			soa_end = rr_copy;
-		}
 	}
 
-	/* Ending SOA (not in the UPDATE) */
-	if (soa_end == NULL) {
-		/* If the changeset is empty, do not process anything further
-		 * and indicate this to the caller, so that the changeset is not
-		 * saved and zone is not switched.
-		 */
+	if (changeset->soa_to == NULL) {
+		// No SOA in the update, create according to current policy
 		if (knot_changeset_is_empty(changeset)) {
-			return 1;
-		}
-
-		/* If not set, create new SOA. */
-		ret = knot_rrset_copy(soa, &soa_end, NULL);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to copy ending SOA: %s\n",
-			         knot_strerror(ret));
-			*rcode = KNOT_RCODE_SERVFAIL;
-			return ret;
+			// No change, no new SOA
+			return KNOT_EOK;
 		}
-		knot_rdata_soa_serial_set(soa_end, sn_new);
 
-		/* And replace it in the zone. */
-		ret = xfrin_replace_rrset_in_node(
-		                        knot_zone_contents_get_apex(zone),
-		                        soa_end, changes, zone);
-		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to copy replace SOA in zone: %s\n",
-			         knot_strerror(ret));
+		knot_rrset_t *soa_cpy = knot_rrset_copy(soa_begin, NULL);
+		if (soa_cpy == NULL) {
 			*rcode = KNOT_RCODE_SERVFAIL;
-			return ret;
+			return KNOT_ENOMEM;
 		}
+		knot_rrs_soa_serial_set(&soa_cpy->rrs, new_serial);
+		knot_changeset_add_soa(changeset, soa_cpy, KNOT_CHANGESET_ADD);
 	}
 
-	ret = knot_ddns_final_soa_to_chgset(soa_end, changeset);
-
-	return ret;
+	return KNOT_EOK;
 }
diff --git a/src/knot/updates/ddns.h b/src/knot/updates/ddns.h
index f6993b65bf3a3a1ec47462bd7a6205083a99aaf8..796b0d56f04a85375dc9208efca987bdac7d313e 100644
--- a/src/knot/updates/ddns.h
+++ b/src/knot/updates/ddns.h
@@ -2,6 +2,7 @@
  * \file ddns.h
  *
  * \author Lubos Slovak <lubos.slovak@nic.cz>
+ * \author Jan Kadlec <jan.kadlec@nic.cz>
  *
  * \brief Dynamic updates processing.
  *
@@ -30,49 +31,37 @@
 #include "knot/updates/changesets.h"
 #include "knot/zone/zone.h"
 #include "libknot/packet/pkt.h"
-#include "libknot/rrset.h"
 #include "libknot/dname.h"
-#include "libknot/consts.h"
-#include "common/lists.h"
-
-typedef struct knot_ddns_prereq_t {
-	knot_rrset_t **exist;
-	size_t exist_count;
-	size_t exist_allocd;
-
-	knot_rrset_t **exist_full;
-	size_t exist_full_count;
-	size_t exist_full_allocd;
-
-	knot_rrset_t **not_exist;
-	size_t not_exist_count;
-	size_t not_exist_allocd;
-
-	knot_dname_t **in_use;
-	size_t in_use_count;
-	size_t in_use_allocd;
-
-	knot_dname_t **not_in_use;
-	size_t not_in_use_count;
-	size_t not_in_use_allocd;
-} knot_ddns_prereq_t;
-
-int knot_ddns_check_zone(const knot_zone_contents_t *zone,
-                         const knot_pkt_t *query, uint16_t *rcode);
 
+/*!
+ * \brief Checks update prerequisite section.
+ *
+ * \param query  DNS message containing the update.
+ * \param zone   Zone to be checked.
+ * \param rcode  Returned DNS RCODE.
+ *
+ * \return KNOT_E*
+ */
 int knot_ddns_process_prereqs(const knot_pkt_t *query,
-                              knot_ddns_prereq_t **prereqs, uint16_t *rcode);
-
-int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
-                            knot_ddns_prereq_t **prereqs, uint16_t *rcode);
-
-int knot_ddns_process_update(knot_zone_contents_t *zone,
-                              const knot_pkt_t *query,
-                              knot_changeset_t *changeset,
-                              knot_changes_t *changes,
-                              uint16_t *rcode, uint32_t new_serial);
+                              const knot_zone_contents_t *zone,
+                              uint16_t *rcode);
 
-void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq);
+/*!
+ * \brief Processes DNS update and creates a changeset out of it. Zone is left
+ *        intact.
+ *
+ * \param zone        Zone to be updated.
+ * \param query       DNS message containing the update.
+ * \param changeset   Output changeset.
+ * \param rcode       Output DNS RCODE.
+ * \param new_serial  New serial to use for updated zone.
+ *
+ * \return KNOT_E*
+ */
+int knot_ddns_process_update(const knot_zone_contents_t *zone,
+                             const knot_pkt_t *query,
+                             knot_changeset_t *changeset,
+                             uint16_t *rcode, uint32_t new_serial);
 
 #endif /* _KNOT_DDNS_H_ */
 
diff --git a/src/knot/updates/xfr-in.c b/src/knot/updates/xfr-in.c
index b43fef2cbf7c47a1e5b2d178605ea09e9acac192..90f4e83c86377e1d26f627e7e016fffdbcb7ee6c 100644
--- a/src/knot/updates/xfr-in.c
+++ b/src/knot/updates/xfr-in.c
@@ -42,6 +42,23 @@
 
 #define KNOT_NS_TSIG_FREQ 100
 
+/*!
+ * \brief Post update cleanup: frees data that are in the tree that will not
+ *        be used (old tree if success, new tree if failure).
+ *          Freed data:
+ *           - actual data inside knot_rrs_t. (the rest is part of the node)
+ *           - arrays allocated for additional nodes. (optional, nodes are kept)
+ */
+static void rrs_list_clear(list_t *l, mm_ctx_t *mm)
+{
+	ptrnode_t *n;
+	node_t *nxt;
+	WALK_LIST_DELSAFE(n, nxt, *l) {
+		mm_free(mm, (void *)n->d);
+		mm_free(mm, n);
+	};
+}
+
 static int knot_ns_tsig_required(int packet_nr)
 {
 	/*! \bug This can overflow to negative numbers. Proper solution is to
@@ -63,10 +80,9 @@ int xfrin_transfer_needed(const knot_zone_contents_t *zone,
 	/*
 	 * Retrieve the local Serial
 	 */
-	const knot_rrset_t *soa_rrset =
-		knot_node_rrset(knot_zone_contents_apex(zone),
-				KNOT_RRTYPE_SOA);
-	if (soa_rrset == NULL) {
+	const knot_rrs_t *soa_rrs =
+		knot_node_rrs(knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA);
+	if (soa_rrs == NULL) {
 		char *name = knot_dname_to_str(knot_node_owner(
 				knot_zone_contents_apex(zone)));
 		dbg_xfrin("SOA RRSet missing in the zone %s!\n", name);
@@ -74,31 +90,21 @@ int xfrin_transfer_needed(const knot_zone_contents_t *zone,
 		return KNOT_ERROR;
 	}
 
-	int64_t local_serial = knot_rdata_soa_serial(soa_rrset);
-	if (local_serial < 0) {
-dbg_xfrin_exec(
-		char *name = knot_dname_to_str(knot_rrset_owner(soa_rrset));
-		dbg_xfrin("Malformed data in SOA of zone %s\n", name);
-		free(name);
-);
-		return KNOT_EMALF;  // maybe some other error
-	}
-
+	uint32_t local_serial = knot_rrs_soa_serial(soa_rrs);
 	/*
 	 * Retrieve the remote Serial
 	 */
 	// the SOA should be the first (and only) RRSet in the response
-
 	const knot_pktsection_t *answer = knot_pkt_section(soa_response, KNOT_ANSWER);
-	if (answer->count < 1 || knot_rrset_type(answer->rr[0]) != KNOT_RRTYPE_SOA) {
+	if (answer->count < 1) {
 		return KNOT_EMALF;
 	}
-
-	int64_t remote_serial = knot_rdata_soa_serial(answer->rr[0]);
-	if (remote_serial < 0) {
-		return KNOT_EMALF;	// maybe some other error
+	knot_rrset_t soa_rr = answer->rr[0];
+	if (soa_rr.type != KNOT_RRTYPE_SOA) {
+		return KNOT_EMALF;
 	}
 
+	uint32_t remote_serial = knot_rrs_soa_serial(&soa_rr.rrs);
 	return (knot_serial_compare(local_serial, remote_serial) < 0);
 }
 
@@ -131,9 +137,10 @@ int xfrin_create_ixfr_query(const zone_t *zone, knot_pkt_t *pkt)
 
 	/* Add SOA RR to authority section for IXFR. */
 	knot_node_t *apex = zone->contents->apex;
-	const knot_rrset_t *soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
+	knot_rrset_t soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
 	knot_pkt_begin(pkt, KNOT_AUTHORITY);
-	return knot_pkt_put(pkt, COMPR_HINT_QNAME, soa, 0);
+	ret = knot_pkt_put(pkt, COMPR_HINT_QNAME, &soa, 0);
+	return ret;
 }
 
 /*----------------------------------------------------------------------------*/
@@ -223,18 +230,14 @@ static int xfrin_check_tsig(knot_pkt_t *packet, knot_ns_xfr_t *xfr,
 
 /*----------------------------------------------------------------------------*/
 
-static int xfrin_take_rr(const knot_pktsection_t *answer, knot_rrset_t **rr, uint16_t *cur)
+static void xfrin_take_rr(const knot_pktsection_t *answer, const knot_rrset_t **rr, uint16_t *cur)
 {
-	int ret = KNOT_EOK;
 	if (*cur < answer->count) {
-		ret = knot_rrset_copy(answer->rr[*cur], rr, NULL);
+		*rr = &answer->rr[*cur];
 		*cur += 1;
 	} else {
 		*rr = NULL;
-		ret = KNOT_EOK;
 	}
-
-	return ret;
 }
 
 /*----------------------------------------------------------------------------*/
@@ -246,19 +249,17 @@ int xfrin_process_axfr_packet(knot_pkt_t *pkt, knot_ns_xfr_t *xfr, knot_zone_con
 	}
 
 	uint16_t rr_id = 0;
-	knot_rrset_t *rr = NULL;
+	const knot_rrset_t *rr = NULL;
 	const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER);
 
-	int ret = xfrin_take_rr(answer, &rr, &rr_id);
+	xfrin_take_rr(answer, &rr, &rr_id);
 	if (*zone == NULL) {
 		// Transfer start, init zone
 		if (rr->type != KNOT_RRTYPE_SOA) {
-			knot_rrset_free(&rr, NULL);
 			return KNOT_EMALF;
 		}
 		*zone = knot_zone_contents_new(rr->owner);
 		if (*zone == NULL) {
-			knot_rrset_free(&rr, NULL);
 			return KNOT_ENOMEM;
 		}
 		xfr->packet_nr = 0;
@@ -270,32 +271,27 @@ int xfrin_process_axfr_packet(knot_pkt_t *pkt, knot_ns_xfr_t *xfr, knot_zone_con
 	zcreator_t zc = {.z = *zone,
 	                 .master = false, .ret = KNOT_EOK };
 
-
-	while (ret == KNOT_EOK && rr) {
+	while (rr) {
 		if (rr->type == KNOT_RRTYPE_SOA &&
-		    knot_node_rrset(zc.z->apex, KNOT_RRTYPE_SOA)) {
+		    knot_node_rrtype_exists(zc.z->apex, KNOT_RRTYPE_SOA)) {
 			// Last SOA, last message, check TSIG.
-			ret = xfrin_check_tsig(pkt, xfr, 1);
-			knot_rrset_free(&rr, NULL);
+			int ret = xfrin_check_tsig(pkt, xfr, 1);
 			if (ret != KNOT_EOK) {
 				return ret;
 			}
 			return 1; // Signal that transfer finished.
 		} else {
-			ret = zcreator_step(&zc, rr);
+			int ret = zcreator_step(&zc, rr);
 			if (ret != KNOT_EOK) {
-				knot_rrset_free(&rr, NULL);
+				// 'rr' is either inserted, or free'd
 				return ret;
 			}
-			ret = xfrin_take_rr(answer, &rr, &rr_id);
+			xfrin_take_rr(answer, &rr, &rr_id);
 		}
 	}
 
-	assert(rr == NULL);
 	// Check possible TSIG at the end of DNS message.
-	ret = xfrin_check_tsig(pkt, xfr,
-	                       knot_ns_tsig_required(xfr->packet_nr));
-	return ret; // ret == KNOT_EOK means processing continues.
+	return xfrin_check_tsig(pkt, xfr, knot_ns_tsig_required(xfr->packet_nr));
 }
 
 /*----------------------------------------------------------------------------*/
@@ -309,45 +305,43 @@ int xfrin_process_ixfr_packet(knot_pkt_t *pkt, knot_ns_xfr_t *xfr)
 	}
 
 	uint16_t rr_id = 0;
-	knot_rrset_t *rr = NULL;
+	const knot_rrset_t *rr = NULL;
 	const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER);
-	int ret = xfrin_take_rr(answer, &rr, &rr_id);
-	if (ret != KNOT_EOK) {
+	xfrin_take_rr(answer, &rr, &rr_id);
+	if (rr == NULL) {
 		return KNOT_EXFRREFUSED; /* Empty, try again with AXFR */
 	}
 
 	// state of the transfer
 	// -1 .. a SOA is expected to create a new changeset
 	int state = 0;
-
-	/*! \todo Replace with RRSet duplicate checking. */
-//	xfrin_insert_rrset_dnames_to_table(rr, xfr->lookup_tree);
-
+	int ret = KNOT_EOK;
 	if (*chs == NULL) {
 		dbg_xfrin_verb("Changesets empty, creating new.\n");
-
 		ret = knot_changesets_init(chs);
 		if (ret != KNOT_EOK) {
-			knot_rrset_free(&rr, NULL);
 			return ret;
 		}
 
 		// the first RR must be a SOA
-		if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) {
+		if (rr->type != KNOT_RRTYPE_SOA) {
 			dbg_xfrin("First RR is not a SOA RR!\n");
-			knot_rrset_free(&rr, NULL);
 			ret = KNOT_EMALF;
 			goto cleanup;
 		}
 
 		// just store the first SOA for later use
-		(*chs)->first_soa = rr;
+		(*chs)->first_soa = knot_rrset_copy(rr, NULL);
+		if ((*chs)->first_soa == NULL) {
+			ret = KNOT_ENOMEM;
+			goto cleanup;
+		}
 		state = -1;
 
 		dbg_xfrin_verb("First SOA of IXFR saved, state set to -1.\n");
 
 		// take next RR
-		ret = xfrin_take_rr(answer, &rr, &rr_id);
+		xfrin_take_rr(answer, &rr, &rr_id);
 
 		/*
 		 * If there is no other records in the response than the SOA, it
@@ -368,16 +362,13 @@ int xfrin_process_ixfr_packet(knot_pkt_t *pkt, knot_ns_xfr_t *xfr)
 		if (rr == NULL) {
 			dbg_xfrin("Response containing only SOA,\n");
 			return XFRIN_RES_SOA_ONLY;
-		} else if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) {
-			knot_rrset_free(&rr, NULL);
+		} else if (rr->type != KNOT_RRTYPE_SOA) {
 			dbg_xfrin("Fallback to AXFR.\n");
-			ret = XFRIN_RES_FALLBACK;
-			return ret;
+			return XFRIN_RES_FALLBACK;
 		}
 	} else {
 		if ((*chs)->first_soa == NULL) {
 			dbg_xfrin("Changesets don't contain SOA first!\n");
-			knot_rrset_free(&rr, NULL);
 			ret = KNOT_EINVAL;
 			goto cleanup;
 		}
@@ -425,7 +416,6 @@ int xfrin_process_ixfr_packet(knot_pkt_t *pkt, knot_ns_xfr_t *xfr)
 		dbg_xfrin_detail("State is not -1, deciding...\n");
 		// there should be at least one started changeset right now
 		if (EMPTY_LIST((*chs)->sets)) {
-			knot_rrset_free(&rr, NULL);
 			ret = KNOT_EMALF;
 			goto cleanup;
 		}
@@ -444,20 +434,12 @@ int xfrin_process_ixfr_packet(knot_pkt_t *pkt, knot_ns_xfr_t *xfr)
 
 	/*! \todo This may be implemented with much less IFs! */
 
-	while (ret == KNOT_EOK && rr != NULL) {
-dbg_xfrin_exec_verb(
-		dbg_xfrin_detail("Next loop, state: %d\n", state);
-		char *name = knot_dname_to_str(knot_rrset_owner(rr));
-		dbg_xfrin_detail("Actual RR: %s, type %u.\n", name,
-				 knot_rrset_type(rr));
-		free(name);
-);
+	while (rr != NULL) {
 		if (!knot_dname_is_sub(rr->owner, xfr->zone->name) &&
 		    !knot_dname_is_equal(rr->owner, xfr->zone->name)) {
 			// out-of-zone domain
-			knot_rrset_free(&rr, NULL);
 			// take next RR
-			ret = xfrin_take_rr(answer, &rr, &rr_id);
+			xfrin_take_rr(answer, &rr, &rr_id);
 			continue;
 		}
 
@@ -467,17 +449,16 @@ dbg_xfrin_exec_verb(
 			// this may be either a start of a changeset or the
 			// last SOA (in case the transfer was empty, but that
 			// is quite weird in fact
-			if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) {
+			if (rr->type != KNOT_RRTYPE_SOA) {
 				dbg_xfrin("First RR is not a SOA RR!\n");
 				dbg_xfrin_verb("RR type: %u\n",
-					       knot_rrset_type(rr));
-				knot_rrset_free(&rr, NULL);
+					       rr->type);
 				ret = KNOT_EMALF;
 				goto cleanup;
 			}
 
-			if (knot_rdata_soa_serial(rr)
-			    == knot_rdata_soa_serial((*chs)->first_soa)) {
+			if (knot_rrs_soa_serial(&rr->rrs)
+			    == knot_rrs_soa_serial(&(*chs)->first_soa->rrs)) {
 
 				/*! \note [TSIG] Check TSIG, we're at the end of
 				 *               transfer.
@@ -485,8 +466,6 @@ dbg_xfrin_exec_verb(
 				ret = xfrin_check_tsig(pkt, xfr, 1);
 
 				// last SOA, discard and end
-				knot_rrset_free(&rr, NULL);
-
 				/*! \note [TSIG] If TSIG validates, consider
 				 *        transfer complete. */
 				if (ret == KNOT_EOK) {
@@ -501,16 +480,20 @@ dbg_xfrin_exec_verb(
 					ret = KNOT_ESPACE;
 
 				if (ret != KNOT_EOK) {
-					knot_rrset_free(&rr, NULL);
 					goto cleanup;
 				}
 
 				chset = knot_changesets_create_changeset(*chs);
 				if (chset == NULL) {
-					knot_rrset_free(&rr, NULL);
 					goto cleanup;
 				}
-				knot_changeset_add_soa(chset, rr, KNOT_CHANGESET_REMOVE);
+				knot_rrset_t *soa = knot_rrset_copy(rr, NULL);
+				if (soa == NULL) {
+					ret = KNOT_ENOMEM;
+					goto cleanup;
+				}
+
+				knot_changeset_add_soa(chset, soa, KNOT_CHANGESET_REMOVE);
 
 				// change state to REMOVE
 				state = KNOT_CHANGESET_REMOVE;
@@ -519,20 +502,28 @@ dbg_xfrin_exec_verb(
 		case KNOT_CHANGESET_REMOVE:
 			// if the next RR is SOA, store it and change state to
 			// ADD
-			if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) {
+			if (rr->type == KNOT_RRTYPE_SOA) {
 				// we should not be here if soa_from is not set
 				assert(chset->soa_from != NULL);
-
-				knot_changeset_add_soa(chset, rr, KNOT_CHANGESET_ADD);
+				knot_rrset_t *soa = knot_rrset_copy(rr, NULL);
+				if (soa == NULL) {
+					ret = KNOT_ENOMEM;
+					goto cleanup;
+				}
+				knot_changeset_add_soa(chset, soa, KNOT_CHANGESET_ADD);
 
 				state = KNOT_CHANGESET_ADD;
 			} else {
 				// just add the RR to the REMOVE part and
 				// continue
-				ret = knot_changeset_add_rr(chset, rr,
-				                            KNOT_CHANGESET_REMOVE);
+				knot_rrset_t *cpy = knot_rrset_copy(rr, NULL);
+				if (cpy == NULL) {
+					ret = KNOT_ENOMEM;
+					goto cleanup;
+				}
+				ret = knot_changeset_add_rrset(chset, cpy,
+				                               KNOT_CHANGESET_REMOVE);
 				if (ret != KNOT_EOK) {
-					knot_rrset_free(&rr, NULL);
 					goto cleanup;
 				}
 			}
@@ -540,20 +531,23 @@ dbg_xfrin_exec_verb(
 		case KNOT_CHANGESET_ADD:
 			// if the next RR is SOA change to state -1 and do not
 			// parse next RR
-			if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) {
+			if (rr->type == KNOT_RRTYPE_SOA) {
 				log_zone_info("%s Serial %u -> %u.\n",
 					      xfr->msg,
-					      knot_rdata_soa_serial(chset->soa_from),
-					      knot_rdata_soa_serial(chset->soa_to));
+					      knot_rrs_soa_serial(&chset->soa_from->rrs),
+					      knot_rrs_soa_serial(&chset->soa_to->rrs));
 				state = -1;
 				continue;
 			} else {
-
 				// just add the RR to the ADD part and continue
-				ret = knot_changeset_add_rr(chset, rr,
-				                            KNOT_CHANGESET_ADD);
+				knot_rrset_t *cpy = knot_rrset_copy(rr, NULL);
+				if (cpy == NULL) {
+					ret = KNOT_ENOMEM;
+					goto cleanup;
+				}
+				ret = knot_changeset_add_rrset(chset, cpy,
+				                               KNOT_CHANGESET_ADD);
 				if (ret != KNOT_EOK) {
-					knot_rrset_free(&rr, NULL);
 					goto cleanup;
 				}
 			}
@@ -561,7 +555,7 @@ dbg_xfrin_exec_verb(
 		}
 
 		// take next RR
-		ret = xfrin_take_rr(answer, &rr, &rr_id);
+		xfrin_take_rr(answer, &rr, &rr_id);
 	}
 
 	/*! \note Check TSIG, we're at the end of packet. It may not be
@@ -595,11 +589,29 @@ cleanup:
 /* Applying changesets to zone                                                */
 /*----------------------------------------------------------------------------*/
 
-void xfrin_zone_contents_free(knot_zone_contents_t **contents)
+void xfrin_cleanup_successful_update(knot_changesets_t *chgs)
 {
-	/*! \todo This should be all in some API!! */
+	if (chgs == NULL) {
+		return;
+	}
+
+	knot_changeset_t *change = NULL;
+	WALK_LIST(change, chgs->sets) {
+		// Delete old RR data
+		rrs_list_clear(&change->old_data, NULL);
+		init_list(&change->old_data);
+		// Keep new RR data
+		ptrlist_free(&change->new_data, NULL);
+		init_list(&change->new_data);
+	};
+}
+
+/*----------------------------------------------------------------------------*/
 
-	// free the zone tree with nodes
+static void xfrin_zone_contents_free(knot_zone_contents_t **contents)
+{
+	// free the zone tree, but only the structure
+	// (nodes are already destroyed)
 	dbg_zone("Destroying zone tree.\n");
 	knot_zone_tree_deep_free(&(*contents)->nodes);
 	dbg_zone("Destroying NSEC3 zone tree.\n");
@@ -613,577 +625,191 @@ void xfrin_zone_contents_free(knot_zone_contents_t **contents)
 
 /*----------------------------------------------------------------------------*/
 
-int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
-                         knot_changes_t *changes, int save_new)
+static void xfrin_cleanup_failed_update(knot_zone_contents_t **new_contents)
 {
-	dbg_xfrin_detail("Copying old RRSet: %p\n", old);
-	// create new RRSet by copying the old one
-	int ret = knot_rrset_copy(old, copy, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_xfrin("Failed to create RRSet copy.\n");
-		return KNOT_ENOMEM;
-	}
-
-	// add the RRSet to the list of new RRSets
-	if (save_new) {
-		ret = knot_changes_add_rrset(changes, *copy, KNOT_CHANGES_NEW);
-		if (ret != KNOT_EOK) {
-			knot_rrset_free(copy, NULL);
-			return ret;
-		}
+	if (new_contents == NULL) {
+		return;
 	}
 
-	ret = knot_changes_add_rrset(changes, old, KNOT_CHANGES_OLD);
-	if (ret != KNOT_EOK) {
-		return ret;
+	if (*new_contents != NULL) {
+		// destroy the shallow copy of zone
+		xfrin_zone_contents_free(new_contents);
 	}
 
-	return KNOT_EOK;
 }
 
 /*----------------------------------------------------------------------------*/
 
-int xfrin_copy_rrset(knot_node_t *node, uint16_t type,
-                     knot_rrset_t **rrset, knot_changes_t *changes,
-                     int save_new)
+void xfrin_rollback_update(knot_changesets_t *chgs,
+                           knot_zone_contents_t **new_contents)
 {
-dbg_xfrin_exec_detail(
-	char *name = knot_dname_to_str(knot_node_owner(node));
-	dbg_xfrin_detail("Removing RRSet of type %u from node %s (%p)\n",
-			 type, name, node);
-	free(name);
-);
-	knot_rrset_t *old = knot_node_remove_rrset(node, type);
-
-	dbg_xfrin_detail("Removed RRSet: %p\n", old);
-	dbg_xfrin_detail("Other RRSet of the same type in the node: %p\n",
-			 knot_node_rrset(node, type));
-
-	if (old == NULL) {
-		dbg_xfrin_verb("RRSet not found for RR to be removed.\n");
-		return 1;
-	}
-
-	int ret = xfrin_copy_old_rrset(old, rrset, changes, save_new);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-
-	dbg_xfrin_detail("Copied old rrset %p to new %p.\n", old, *rrset);
-
-	// replace the RRSet in the node copy by the new one
-	ret = knot_node_add_rrset_replace(node, *rrset);
-	if (ret != KNOT_EOK) {
-		knot_rrset_free(rrset, NULL);
-		dbg_xfrin("Failed to add RRSet copy to node\n");
-		return KNOT_ERROR;
-	}
-
-	return KNOT_EOK;
+	if (chgs != NULL) {
+		knot_changeset_t *change = NULL;
+		WALK_LIST(change, chgs->sets) {
+			// Delete new RR data
+			rrs_list_clear(&change->new_data, NULL);
+			init_list(&change->new_data);
+			// Keep old RR data
+			ptrlist_free(&change->old_data, NULL);
+			init_list(&change->old_data);
+		};
+	}
+	xfrin_cleanup_failed_update(new_contents);
 }
 
 /*----------------------------------------------------------------------------*/
 
-static int xfrin_apply_remove_normal(knot_changes_t *changes,
-                                     const knot_rrset_t *remove,
-                                     knot_node_t *node,
-                                     knot_rrset_t **rrset)
+static int xfrin_replace_rrs_with_copy(knot_node_t *node,
+                                       uint16_t type)
 {
-	assert(changes != NULL);
-	assert(remove != NULL);
-	assert(node != NULL);
-	assert(rrset != NULL);
-
-	int ret;
-
-	// now we have the copy of the node, so lets get the right RRSet
-	// check if we do not already have it
-	if (*rrset
-	    && knot_dname_cmp(knot_rrset_owner(*rrset),
-				  knot_node_owner(node)) == 0
-	    && knot_rrset_type(*rrset) == knot_rrset_type(remove)) {
-		/*! \todo Does some other case even occur? */
-		dbg_xfrin_verb("Using RRSet from previous loop.\n");
-	} else {
-		/*!
-		 * \todo This may happen also with already
-		 *       copied RRSet. In that case it would be
-		 *       an unnecesary overhead but will
-		 *       probably not cause problems. TEST!!
-		 */
-		ret = xfrin_copy_rrset(node,
-			knot_rrset_type(remove), rrset, changes, 1);
-		if (ret != KNOT_EOK) {
-			return ret;
+	// Find data to copy.
+	struct rr_data *data = NULL;
+	for (uint16_t i = 0; i < node->rrset_count; ++i) {
+		if (node->rrs[i].type == type) {
+			data = &node->rrs[i];
+			break;
 		}
 	}
+	assert(data);
 
-	if (*rrset == NULL) {
-		dbg_xfrin_verb("RRSet not found for RR to be removed.\n");
-		return 1;
-	}
-
-dbg_xfrin_exec_detail(
-	char *name = knot_dname_to_str(knot_rrset_owner(*rrset));
-	dbg_xfrin_detail("Updating RRSet with owner %s, type %u\n", name,
-			 knot_rrset_type(*rrset));
-	free(name);
-);
-
-	// remove the specified RRs from the RRSet (de facto difference of sets)
-	knot_rrset_t *rr_remove = NULL;
-	ret = knot_rrset_remove_rr_using_rrset(*rrset, remove, &rr_remove, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_xfrin("xfr: remove_normal: Could not remove RR (%s).\n",
-			  knot_strerror(ret));
-		return ret;
-	}
-	/*!< \todo either one of these checks should be enough. */
-	if (knot_rrset_rr_count(rr_remove) == 0) {
-		/* No RDATA, no need to deep free. */
-		knot_rrset_free(&rr_remove, NULL);
-		dbg_xfrin_verb("Failed to remove RDATA from RRSet\n");
-		// In this case, the RDATA was not found in the RRSet
-		return 1;
-	}
-
-	if (knot_rrset_rr_count(rr_remove) > 0) {
-		ret = knot_changes_add_rrset(changes, rr_remove, KNOT_CHANGES_OLD);
-		if (ret != KNOT_EOK) {
-			knot_rrset_free(&rr_remove, NULL);
-			return ret;
-		}
-	} else {
-		/* Discard empty RRSet. */
-		knot_rrset_free(&rr_remove, NULL);
+	// Create new data.
+	knot_rrs_t *rrs = &data->rrs;
+	void *copy = malloc(knot_rrs_size(rrs));
+	if (copy == NULL) {
+		return KNOT_ENOMEM;
 	}
 
-	// if the RRSet is empty, remove from node and add to old RRSets
-	if (knot_rrset_rr_count(*rrset) == 0) {
-		knot_rrset_t *tmp = knot_node_remove_rrset(node,
-						     knot_rrset_type(*rrset));
-		dbg_xfrin_detail("Removed whole RRSet (%p). Node rr count=%d\n",
-				 tmp, knot_node_rrset_count(node));
+	memcpy(copy, rrs->data, knot_rrs_size(rrs));
 
-		// add the removed RRSet to list of old RRSets
-
-		assert(tmp == *rrset);
-		ret = knot_changes_add_rrset(changes, *rrset, KNOT_CHANGES_OLD);
-		if (ret != KNOT_EOK) {
-			dbg_xfrin("Failed to add empty RRSet to the "
-				  "list of old RRSets.");
-			// delete the RRSet right away
-			knot_rrset_free(rrset, NULL);
-			return ret;
-		}
-	}
+	/*
+	 * Clear additional array from node in new tree. It's callers
+	 * responsibility to store it for cleanup.
+	 */
+	data->additional = NULL;
+	// Store new data into node RRS.
+	rrs->data = copy;
 
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static knot_node_t *xfrin_add_new_node(knot_zone_contents_t *contents,
-                                       knot_rrset_t *rrset, int is_nsec3)
+static void clear_new_rrs(knot_node_t *node, uint16_t type)
 {
-	knot_node_t *node = knot_node_new(knot_rrset_get_owner(rrset),
-					  NULL, 0);
-	if (node == NULL) {
-		dbg_xfrin("Failed to create a new node.\n");
-		return NULL;
+	knot_rrs_t *new_rrs = knot_node_get_rrs(node, type);
+	if (new_rrs) {
+		knot_rrs_clear(new_rrs, NULL);
 	}
-
-	int ret = 0;
-
-	// insert the node into zone structures and create parents if
-	// necessary
-	if (is_nsec3) {
-		ret = knot_zone_contents_add_nsec3_node(contents, node, 1, 0);
-	} else {
-		ret = knot_zone_contents_add_node(contents, node, 1, 0);
-	}
-	if (ret != KNOT_EOK) {
-		dbg_xfrin("Failed to add new node to zone contents.\n");
-		knot_node_free(&node);
-		return NULL;
-	}
-
-	return node;
 }
 
-/*----------------------------------------------------------------------------*/
-
-int xfrin_replace_rrset_in_node(knot_node_t *node,
-                                       knot_rrset_t *rrset_new,
-                                       knot_changes_t *changes,
-                                       knot_zone_contents_t *contents)
+static bool can_remove(const knot_node_t *node, const knot_rrset_t *rr)
 {
-	if (node == NULL || rrset_new == NULL || changes == NULL
-	    || contents == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	uint16_t type = knot_rrset_type(rrset_new);
-	// remove RRSet of the proper type from the node
-	dbg_xfrin_verb("Removing RRSet of type: %u.\n", type);
-	knot_rrset_t *rrset_old = knot_node_remove_rrset(node, type);
-	assert(rrset_old != NULL);
-
-	// save also the RDATA, because RDATA are not deleted with the RRSet
-	// save the new RRSet to the new RRSet, so that it is deleted if the
-	// apply fails
-	int ret = knot_changes_add_rrset(changes, rrset_old, KNOT_CHANGES_OLD);
-	if (ret != KNOT_EOK) {
-		return ret;
+	if (node == NULL) {
+		// Node does not exist, cannot remove anything.
+		return false;
 	}
-
-	// insert the new RRSet to the node
-	dbg_xfrin_verb("Adding new RRSet.\n");
-	ret = knot_zone_contents_add_rrset(contents, rrset_new, &node,
-	                                   KNOT_RRSET_DUPL_SKIP);
-
-	if (ret < 0) {
-		dbg_xfrin("Failed to add RRSet to node.\n");
-		return KNOT_ERROR;
+	const knot_rrs_t *node_rrs = knot_node_rrs(node, rr->type);
+	if (node_rrs == NULL) {
+		// Node does not have this type at all.
+		return false;
 	}
-	assert(ret == 0);
 
-	ret = knot_changes_add_rrset(changes, rrset_new, KNOT_CHANGES_NEW);
-	if (ret != KNOT_EOK) {
-		return ret;
+	const bool compare_ttls = false;
+	for (uint16_t i = 0; i < rr->rrs.rr_count; ++i) {
+		knot_rr_t *rr_cmp = knot_rrs_rr(&rr->rrs, i);
+		if (knot_rrs_member(node_rrs, rr_cmp, compare_ttls)) {
+			// At least one RR matches.
+			return true;
+		}
 	}
 
-	return KNOT_EOK;
+	// Node does have the type, but no RRs match.
+	return false;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int xfrin_apply_add_normal(knot_changes_t *changes,
-                                  knot_rrset_t *add,
-                                  knot_node_t *node,
-                                  knot_rrset_t **rrset,
-                                  knot_zone_contents_t *contents)
+static int add_old_data(knot_changeset_t *chset, knot_rr_t *old_data,
+                        knot_node_t **old_additional)
 {
-	assert(changes != NULL);
-	assert(add != NULL);
-	assert(node != NULL);
-	assert(rrset != NULL);
-	assert(contents != NULL);
-
-	int ret;
-
-	int copied = 0;
-	/*! \note Reusing RRSet from previous function caused it not to be
-	 *        removed from the node.
-	 *        Maybe modification of the code would allow reusing the RRSet
-	 *        as in apply_add_rrsigs() - the RRSet should not be copied
-	 *        in such case.
-	 */
-	if (*rrset
-	    && knot_dname_cmp(knot_rrset_owner(*rrset),
-				  knot_node_owner(node)) == 0
-	    && knot_rrset_type(*rrset) == knot_rrset_type(add)) {
-		dbg_xfrin_verb("Using RRSet from previous iteration.\n");
-	} else {
-		dbg_xfrin_detail("Removing rrset!\n");
-		*rrset = knot_node_remove_rrset(node, knot_rrset_type(add));
-
-		knot_rrset_t *old = *rrset;
-
-		if (*rrset != NULL) {
-			ret = xfrin_copy_old_rrset(old, rrset, changes, 1);
-			if (ret != KNOT_EOK) {
-				return ret;
-			}
-			copied = 1;
-		}
-	}
-
-	if (*rrset == NULL) {
-dbg_xfrin_exec_detail(
-		char *name = knot_dname_to_str(add->owner);
-		dbg_xfrin_detail("RRSet to be added not found in zone.\n");
-		dbg_xfrin_detail("owner: %s type: %u\n", name, add->type);
-		free(name);
-);
-		// add the RRSet from the changeset to the node
-		/*!
-		 * \note The new zone must be adjusted nevertheless, so it
-		 *       doesn't matter whether there are some extra dnames to
-		 *       be added to the table or not.
-		 */
-		ret = knot_zone_contents_add_rrset(contents, add, &node,
-						   KNOT_RRSET_DUPL_SKIP);
-
-		if (ret < 0) {
-			dbg_xfrin("Failed to add RRSet to node.\n");
-			return ret;
-		}
-
-		assert(ret == 0);
-
-		return 1; // return 1 to indicate the add RRSet was used
-	}
-
-dbg_xfrin_exec_detail(
-	char *name = knot_dname_to_str(knot_rrset_owner(*rrset));
-	dbg_xfrin_detail("Found RRSet with owner %s, type %u\n", name,
-			 knot_rrset_type(*rrset));
-	free(name);
-);
-
-	// merge the changeset RRSet to the copy
-	/* What if the update fails?
-	 * The changesets will be destroyed - that will destroy 'add',
-	 * and the copied RRSet will be destroyed because it is in the new
-	 * rrsets list.
-	 *
-	 * If the update is successfull, the old RRSet will be destroyed,
-	 * but the one from the changeset will be not!!
-	 *
-	 * TODO: add the 'add' rrset to list of old RRSets?
-	 */
-
-	 /* Check if the added RR has the same TTL as the first RR from the
-	  * zone's RRSet. If not, log a warning.
-	  * We assume that the added RRSet has only one RR, but that should be
-	  * the case here.
-	  */
-	if (knot_rrset_type(add) != KNOT_RRTYPE_RRSIG
-	    && !knot_rrset_ttl_equal(add, *rrset)) {
-		char type_str[16] = { '\0' };
-		knot_rrtype_to_string(knot_rrset_type(add), type_str,
-		                      sizeof(type_str));
-		char *name = knot_dname_to_str(knot_rrset_owner(add));
-		char *zname = knot_dname_to_str(knot_node_owner(contents->apex));
-		log_zone_warning("Changes application to zone %s: TTL mismatch"
-		                 " in %s, type %s\n", zname, name, type_str);
-		free(name);
-		free(zname);
-	}
-
-	int merged, deleted_rrs;
-	ret = knot_rrset_merge_sort(*rrset, add, &merged, &deleted_rrs,
-	                            NULL);
-	if (ret < 0) {
-		dbg_xfrin("Failed to merge changeset RRSet.\n");
-		return ret;
+	if (ptrlist_add(&chset->old_data, old_data, NULL) == NULL) {
+		return KNOT_ENOMEM;
 	}
-	if (copied) {
-		ret = knot_node_add_rrset_no_merge(node, *rrset);
 
-		if (ret < 0) {
-			dbg_xfrin("Failed to add merged RRSet to the node.\n");
-			return ret;
-		}
+	if (!old_additional) {
+		return KNOT_EOK;
 	}
 
-	// return 2 so that the add RRSet is removed from
-	// the changeset (and thus not deleted)
-	// and put to list of new RRSets (is this ok?)
-	// and deleted
-	return 2;
-}
-
-/*----------------------------------------------------------------------------*/
-
-void xfrin_cleanup_successful_update(knot_changes_t *changes)
-{
-	if (changes == NULL) {
-		return;
-	}
-	// Free old RRSets
-	knot_rr_ln_t *rr_node = NULL;
-	WALK_LIST(rr_node, changes->old_rrsets) {
-		knot_rrset_t *rrset = rr_node->rr;
-		knot_rrset_free(&rrset, NULL);
+	if (ptrlist_add(&chset->old_data, old_additional, NULL) == NULL) {
+		return KNOT_ENOMEM;
 	}
-}
-
-/*----------------------------------------------------------------------------*/
-/* New changeset applying                                                     */
-/*----------------------------------------------------------------------------*/
-
-static int xfrin_switch_nodes_in_node(knot_node_t **node, void *data)
-{
-	UNUSED(data);
-
-	assert(node && *node);
-	assert(knot_node_new_node(*node) == NULL);
-
-	knot_node_update_refs(*node);
 
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int xfrin_switch_nodes(knot_zone_contents_t *contents_copy)
+static int add_new_data(knot_changeset_t *chset, knot_rr_t *new_data)
 {
-	assert(contents_copy != NULL);
-
-	// Traverse the trees and for each node check every reference
-	// stored in that node. The node itself should be new.
-	int ret = knot_zone_tree_apply(contents_copy->nodes,
-	                               xfrin_switch_nodes_in_node, NULL);
-	if (ret == KNOT_EOK) {
-		ret = knot_zone_tree_apply(contents_copy->nsec3_nodes,
-		                           xfrin_switch_nodes_in_node, NULL);
+	if (ptrlist_add(&chset->new_data, new_data, NULL) == NULL) {
+		return KNOT_ENOMEM;
 	}
 
-	return ret;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static void xfrin_zone_contents_free2(knot_zone_contents_t **contents)
-{
-	/*! \todo This should be all in some API!! */
-
-	// free the zone tree, but only the structure
-	// (nodes are already destroyed)
-	dbg_zone("Destroying zone tree.\n");
-	knot_zone_tree_deep_free(&(*contents)->nodes);
-	dbg_zone("Destroying NSEC3 zone tree.\n");
-	knot_zone_tree_deep_free(&(*contents)->nsec3_nodes);
-
-	knot_nsec3_params_free(&(*contents)->nsec3_params);
-
-	free(*contents);
-	*contents = NULL;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static int xfrin_cleanup_old_nodes(knot_node_t **node, void *data)
-{
-	UNUSED(data);
-	assert(node && *node);
-
-	knot_node_set_new_node(*node, NULL);
-
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static void xfrin_cleanup_failed_update(knot_zone_contents_t *old_contents,
-                                        knot_zone_contents_t **new_contents)
+static int remove_rr(knot_node_t *node, const knot_rrset_t *rr,
+                     knot_changeset_t *chset)
 {
-	if (old_contents == NULL && new_contents == NULL) {
-		return;
+	knot_rrset_t removed_rrset = knot_node_rrset(node, rr->type);
+	knot_rr_t *old_data = removed_rrset.rrs.data;
+	knot_node_t **old_additional = removed_rrset.additional;
+	int ret = xfrin_replace_rrs_with_copy(node, rr->type);
+	if (ret != KNOT_EOK) {
+		return ret;
 	}
 
-	if (*new_contents != NULL) {
-		// destroy the shallow copy of zone
-		xfrin_zone_contents_free2(new_contents);
+	// Store old data for cleanup.
+	ret = add_old_data(chset, old_data, old_additional);
+	if (ret != KNOT_EOK) {
+		clear_new_rrs(node, rr->type);
+		return ret;
 	}
 
-	if (old_contents != NULL) {
-		// cleanup old zone tree - reset pointers to new node to NULL
-		knot_zone_tree_apply(old_contents->nodes, xfrin_cleanup_old_nodes,
-				     NULL);
-
-		knot_zone_tree_apply(old_contents->nsec3_nodes, xfrin_cleanup_old_nodes,
-				     NULL);
-	}
-}
-
-/*----------------------------------------------------------------------------*/
-
-void xfrin_rollback_update(knot_zone_contents_t *old_contents,
-                           knot_zone_contents_t **new_contents,
-                           knot_changes_t *changes)
-{
-	if (changes == NULL) {
-		return;
+	knot_rrs_t *changed_rrs = knot_node_get_rrs(node, rr->type);
+	// Subtract changeset RRS from node RRS.
+	ret = knot_rrs_subtract(changed_rrs, &rr->rrs, NULL);
+	if (ret != KNOT_EOK) {
+		clear_new_rrs(node, rr->type);
+		return ret;
 	}
 
-	dbg_xfrin("Rolling back changeset application.\n");
-	knot_rr_ln_t *rr_node = NULL;
-	WALK_LIST(rr_node, changes->new_rrsets) {
-		knot_rrset_t *rrset = rr_node->rr;
-		knot_rrset_free(&rrset, NULL);
+	if (changed_rrs->rr_count > 0) {
+		// Subtraction left some data in RRSet, store it for rollback.
+		ret = add_new_data(chset, changed_rrs->data);
+		if (ret != KNOT_EOK) {
+			knot_rrs_clear(changed_rrs, NULL);
+			return ret;
+		}
+	} else {
+		// RRSet is empty now, remove it from node, all data freed.
+		knot_node_remove_rrset(node, rr->type);
 	}
 
-	xfrin_cleanup_failed_update(old_contents, new_contents);
+	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
 static int xfrin_apply_remove(knot_zone_contents_t *contents,
-                              knot_changeset_t *chset,
-                              knot_changes_t *changes)
+                              knot_changeset_t *chset)
 {
-	/*
-	 * Iterate over removed RRSets, and remove them from the new nodes
-	 * in 'contents'. By default, the RRSet should be copied so that
-	 * RDATA may be removed from it.
-	 */
-	int ret = 0;
-	knot_node_t *last_node = NULL;
-	knot_rrset_t *rrset = NULL;
-	int is_nsec3 = 0;
-
 	knot_rr_ln_t *rr_node = NULL;
 	WALK_LIST(rr_node, chset->remove) {
-		knot_rrset_t *rr = rr_node->rr;
-		assert(rr); // No malformed changesets should get here
-dbg_xfrin_exec_verb(
-		char *name = knot_dname_to_str(
-			knot_rrset_owner(rr));
-		dbg_xfrin_verb("Removing RRSet: %s, type %u\n", name,
-			       knot_rrset_type(rr));
-		free(name);
-);
-
-		is_nsec3 = 0;
-
-		// check if the RRSet belongs to the NSEC3 tree
-		if ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
-		    || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
-			&& knot_rdata_rrsig_type_covered(rr, 0)
-			    == KNOT_RRTYPE_NSEC3))
-		{
-			dbg_xfrin_verb("Removed RRSet belongs to NSEC3 tree.\n");
-			is_nsec3 = 1;
-		}
+		const knot_rrset_t *rr = rr_node->rr;
 
-		// check if the old node is not the one we should use
-		dbg_xfrin_verb("Node:%p Owner: %p Node owner: %p\n",
-			       last_node, knot_rrset_owner(rr),
-			       knot_node_owner(last_node));
-		if (!last_node || knot_rrset_owner(rr)
-			     != knot_node_owner(last_node)) {
-			if (is_nsec3) {
-				last_node = knot_zone_contents_get_nsec3_node(
-					    contents,
-					    knot_rrset_owner(rr));
-			} else {
-				last_node = knot_zone_contents_get_node(contents,
-					    knot_rrset_owner(rr));
-			}
-			if (last_node == NULL) {
-				dbg_xfrin_verb("Node not found for RR to be "
-					       "removed!\n");
-				continue;
-			}
+		// Find node for this owner
+		knot_node_t *node = zone_contents_find_node_for_rr(contents,
+		                                                   rr);
+		if (!can_remove(node, rr)) {
+			// Nothing to remove from, skip.
+			continue;
 		}
 
-		assert(last_node != NULL);
-
-		// The CLASS should not be ANY, we do not accept such chgsets
-		dbg_xfrin_verb("RRSet class to be removed=%u\n",
-			       knot_rrset_class(rr));
-		// this should work also for UPDATE
-		ret = xfrin_apply_remove_normal(changes, rr, last_node,
-		                                &rrset);
-
-		dbg_xfrin_detail("xfrin_apply_remove() ret = %d\n", ret);
-
-		if (ret > 0) {
-			continue;
-		} else if (ret != KNOT_EOK) {
+		int ret = remove_rr(node, rr, chset);
+		if (ret != KNOT_EOK) {
 			return ret;
 		}
 	}
@@ -1191,111 +817,79 @@ dbg_xfrin_exec_verb(
 	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-static int xfrin_apply_add(knot_zone_contents_t *contents,
-                           knot_changeset_t *chset,
-                           knot_changes_t *changes)
+static int add_rr(knot_node_t *node, const knot_rrset_t *rr,
+                  knot_changeset_t *chset)
 {
-	int ret = KNOT_EOK;
-	knot_node_t *last_node = NULL;
-	knot_rrset_t *rrset = NULL;
-	int is_nsec3 = 0;
+	knot_rrset_t changed_rrset = knot_node_rrset(node, rr->type);
+	if (!knot_rrset_empty(&changed_rrset)) {
+		// Modifying existing RRSet.
+		knot_rr_t *old_data = changed_rrset.rrs.data;
+		knot_node_t **old_additional = changed_rrset.additional;
+		int ret = xfrin_replace_rrs_with_copy(node, rr->type);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
 
-	knot_rr_ln_t *rr_node = NULL;
-	node_t *tmp_node;
-	WALK_LIST_DELSAFE(rr_node, tmp_node, chset->add) {
-		knot_rrset_t *rr = rr_node->rr;
-		assert(rr); // No malformed changesets should get here
-dbg_xfrin_exec_verb(
-		char *name = knot_dname_to_str(
-			knot_rrset_owner(rr));
-		dbg_xfrin_verb("Adding RRSet: %s, type: %u\n", name,
-			       knot_rrset_type(rr));
-		free(name);
-);
-		is_nsec3 = 0;
-
-		// check if the RRSet belongs to the NSEC3 tree
-		if ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
-		    || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
-			&& knot_rdata_rrsig_type_covered(rr, 0)
-			    == KNOT_RRTYPE_NSEC3))
-		{
-			dbg_xfrin_detail("This is NSEC3-related RRSet.\n");
-			is_nsec3 = 1;
+		// Store old RRS and additional for cleanup.
+		ret = add_old_data(chset, old_data, old_additional);
+		if (ret != KNOT_EOK) {
+			clear_new_rrs(node, rr->type);
+			return ret;
 		}
 
-		// check if the old node is not the one we should use
-		if (!last_node || knot_rrset_owner(rr)
-			     != knot_node_owner(last_node)) {
-			dbg_xfrin_detail("Searching for node...\n");
-			if (is_nsec3) {
-				last_node = knot_zone_contents_get_nsec3_node(
-				            contents,
-				            knot_rrset_owner(rr));
-			} else {
-				last_node = knot_zone_contents_get_node(contents,
-				            knot_rrset_owner(rr));
-			}
-			if (last_node == NULL) {
-				// create new node, connect it properly to the
-				// zone nodes
-				dbg_xfrin_detail("Node not found. Creating new."
-						 "\n");
-				last_node = xfrin_add_new_node(contents,
-							  rr,
-							  is_nsec3);
-				if (last_node == NULL) {
-					dbg_xfrin("Failed to create new node "
-						  "in zone.\n");
-					return KNOT_ERROR;
-				}
-			}
+		// Extract copy, merge into it
+		knot_rrs_t *changed_rrs = knot_node_get_rrs(node, rr->type);
+		ret = knot_rrs_merge(changed_rrs, &rr->rrs, NULL);
+		if (ret != KNOT_EOK) {
+			clear_new_rrs(node, rr->type);
+			return ret;
+		}
+	} else {
+		// Inserting new RRSet, data will be copied.
+		bool ttl_err = false;
+		int ret = knot_node_add_rrset(node, rr, &ttl_err);
+		if (ret != KNOT_EOK) {
+			return ret;
 		}
 
-		ret = xfrin_apply_add_normal(changes, rr, last_node,
-		                             &rrset, contents);
-		assert(ret <= 3);
+		if (ttl_err) {
+			char type_str[16] = { '\0' };
+			knot_rrtype_to_string(rr->type, type_str, sizeof(type_str));
+			char *name = knot_dname_to_str(rr->owner);
+			char *zname = knot_dname_to_str(chset->soa_from->owner);
+			log_zone_warning("Changes application to zone %s: TTL mismatch"
+			                 " in %s, type %s\n", zname, name, type_str);
+			free(name);
+			free(zname);
+		}
+	}
 
-		// Not correct anymore, add_normal() returns KNOT_EOK if the
-		// changeset RR should be removed
-		//assert(ret != KNOT_EOK);
+	// Get changed RRS and store for possible rollback.
+	knot_rrs_t *rrs = knot_node_get_rrs(node, rr->type);
+	int ret = add_new_data(chset, rrs->data);
+	if (ret != KNOT_EOK) {
+		knot_rrs_clear(rrs, NULL);
+		return ret;
+	}
 
-		dbg_xfrin_detail("xfrin_apply_..() returned %d, rrset: %p\n",
-				 ret, rrset);
+	return KNOT_EOK;
+}
 
-		if (ret > 0) {
-			if (ret == 1) {
-				// the ADD RRSet was used, i.e. it should be
-				// removed from the changeset and saved in the
-				// list of new RRSets
-				ret = knot_changes_add_rrset(changes, rr,
-				                             KNOT_CHANGES_NEW);
-				if (ret != KNOT_EOK) {
-					dbg_xfrin("Failed to add old RRSet to "
-						  "list.\n");
-					return ret;
-				}
+static int xfrin_apply_add(knot_zone_contents_t *contents,
+                           knot_changeset_t *chset)
+{
+	knot_rr_ln_t *rr_node = NULL;
+	WALK_LIST(rr_node, chset->add) {
+		knot_rrset_t *rr = rr_node->rr;
 
-				rem_node((node_t *)rr_node);
-			} else if (ret == 2) {
-				// the copy of the RRSet was used, but it was
-				// already stored in the new RRSets list
-				// just delete the add RRSet, but without RDATA
-				// DNAMES as these were merged to the copied RRSet
-				knot_rrset_free(&rr, NULL);
-				rem_node((node_t *)rr_node);
-			} else if (ret == 3) {
-				// the RRSet was used and both RRSet and RDATA
-				// were properly stored. Just clear the place
-				// in the changeset
-				rem_node((node_t *)rr_node);
-			} else {
-				assert(0);
-			}
+		// Get or create node with this owner
+		knot_node_t *node = zone_contents_get_node_for_rr(contents, rr);
+		if (node == NULL) {
+			return KNOT_ENOMEM;
+		}
 
-		} else if (ret != KNOT_EOK) {
+		int ret = add_rr(node, rr, chset);
+		if (ret != KNOT_EOK) {
 			return ret;
 		}
 	}
@@ -1306,30 +900,23 @@ dbg_xfrin_exec_verb(
 /*----------------------------------------------------------------------------*/
 
 static int xfrin_apply_replace_soa(knot_zone_contents_t *contents,
-                                   knot_changes_t *changes,
                                    knot_changeset_t *chset)
 {
-	dbg_xfrin("Replacing SOA record.\n");
-	knot_node_t *node = knot_zone_contents_get_apex(contents);
-	assert(node != NULL);
-
-	assert(node != NULL);
-
-	int ret = xfrin_replace_rrset_in_node(node, chset->soa_to, changes,
-					      contents);
-	if (ret == KNOT_EOK) {
-		// remove the SOA from the changeset, so it will not be deleted
-		// after successful apply
-		chset->soa_to = NULL;
+	assert(chset->soa_from);
+	int ret = remove_rr(contents->apex, chset->soa_from, chset);
+	if (ret != KNOT_EOK) {
+		return ret;
 	}
 
-	return ret;
+	assert(!knot_node_rrtype_exists(contents->apex, KNOT_RRTYPE_SOA));
+
+	return add_rr(contents->apex, chset->soa_to, chset);
 }
 
 /*----------------------------------------------------------------------------*/
 
-static int xfrin_apply_changeset(knot_zone_contents_t *contents,
-                                 knot_changes_t *changes,
+static int xfrin_apply_changeset(list_t *old_rrs, list_t *new_rrs,
+                                 knot_zone_contents_t *contents,
                                  knot_changeset_t *chset)
 {
 	/*
@@ -1342,25 +929,24 @@ static int xfrin_apply_changeset(knot_zone_contents_t *contents,
 		  chset->serial_from, chset->serial_to);
 
 	// check if serial matches
-	const knot_rrset_t *soa = knot_node_rrset(contents->apex,
-	                                          KNOT_RRTYPE_SOA);
-	if (soa == NULL || knot_rdata_soa_serial(soa)
+	const knot_rrs_t *soa = knot_node_rrs(contents->apex, KNOT_RRTYPE_SOA);
+	if (soa == NULL || knot_rrs_soa_serial(soa)
 			   != chset->serial_from) {
 		dbg_xfrin("SOA serials do not match!!\n");
-		return KNOT_ERROR;
+		return KNOT_EINVAL;
 	}
 
-	int ret = xfrin_apply_remove(contents, chset, changes);
+	int ret = xfrin_apply_remove(contents, chset);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
 
-	ret = xfrin_apply_add(contents, chset, changes);
+	ret = xfrin_apply_add(contents, chset);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
 
-	return xfrin_apply_replace_soa(contents, changes, chset);
+	return xfrin_apply_replace_soa(contents, chset);
 }
 
 /*----------------------------------------------------------------------------*/
@@ -1488,7 +1074,7 @@ int xfrin_prepare_zone_copy(knot_zone_contents_t *old_contents,
 	 * updated.
 	 *
 	 * This will create new zone contents structures (normal nodes' tree,
-	 * NSEC3 tree, hash table, domain name table), and copy all nodes.
+	 * NSEC3 tree), and copy all nodes.
 	 * The data in the nodes (RRSets) remain the same though.
 	 */
 	knot_zone_contents_t *contents_copy = NULL;
@@ -1503,19 +1089,6 @@ int xfrin_prepare_zone_copy(knot_zone_contents_t *old_contents,
 
 	assert(knot_zone_contents_apex(contents_copy) != NULL);
 
-	/*
-	 * Fix references to new nodes. Some references in new nodes may point
-	 * to old nodes. Hash table contains only old nodes.
-	 */
-	dbg_xfrin("Switching ptrs pointing to old nodes to the new nodes.\n");
-	ret = xfrin_switch_nodes(contents_copy);
-	if (ret != KNOT_EOK) {
-		dbg_xfrin("Failed to switch pointers in nodes.\n");
-		knot_zone_contents_free(&contents_copy);
-		return ret;
-	}
-	assert(knot_zone_contents_apex(contents_copy) != NULL);
-
 	*new_contents = contents_copy;
 
 	return KNOT_EOK;
@@ -1571,16 +1144,17 @@ int xfrin_finalize_updated_zone(knot_zone_contents_t *contents_copy,
 /*----------------------------------------------------------------------------*/
 
 int xfrin_apply_changesets_directly(knot_zone_contents_t *contents,
-                                    knot_changes_t *changes,
                                     knot_changesets_t *chsets)
 {
-	if (contents == NULL || changes == NULL || chsets == NULL) {
+	if (contents == NULL || chsets == NULL) {
 		return KNOT_EINVAL;
 	}
 
 	knot_changeset_t *set = NULL;
 	WALK_LIST(set, chsets->sets) {
-		int ret = xfrin_apply_changeset(contents, changes, set);
+		int ret = xfrin_apply_changeset(&set->old_data,
+		                                &set->new_data,
+		                                contents, set);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
@@ -1592,12 +1166,12 @@ int xfrin_apply_changesets_directly(knot_zone_contents_t *contents,
 /*----------------------------------------------------------------------------*/
 
 /* Post-DDNS application, no need to shallow copy. */
-int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
+int xfrin_apply_changesets_dnssec_ddns(zone_t *zone,
                                        knot_zone_contents_t *z_new,
                                        knot_changesets_t *sec_chsets,
                                        knot_changesets_t *chsets)
 {
-	if (z_old == NULL || z_new == NULL ||
+	if (zone == NULL || z_new == NULL ||
 	    sec_chsets == NULL || chsets == NULL) {
 		return KNOT_EINVAL;
 	}
@@ -1606,10 +1180,9 @@ int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
 	knot_zone_contents_set_gen_old(z_new);
 
 	/* Apply changes. */
-	int ret = xfrin_apply_changesets_directly(z_new, chsets->changes,
-	                                          sec_chsets);
+	int ret = xfrin_apply_changesets_directly(z_new, sec_chsets);
 	if (ret != KNOT_EOK) {
-		xfrin_rollback_update(z_old, &z_new, chsets->changes);
+		xfrin_rollback_update(sec_chsets, &z_new);
 		dbg_xfrin("Failed to apply changesets to zone: "
 		          "%s\n", knot_strerror(ret));
 		return ret;
@@ -1620,7 +1193,7 @@ int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
 	if (ret != KNOT_EOK) {
 		dbg_xfrin("Failed to finalize updated zone: %s\n",
 		          knot_strerror(ret));
-		xfrin_rollback_update(z_old, &z_new, chsets->changes);
+		xfrin_rollback_update(sec_chsets, &z_new);
 		return ret;
 	}
 
@@ -1663,10 +1236,11 @@ int xfrin_apply_changesets(zone_t *zone,
 		       old_contents->apex, contents_copy->apex);
 	knot_changeset_t *set = NULL;
 	WALK_LIST(set, chsets->sets) {
-		ret = xfrin_apply_changeset(contents_copy, chsets->changes, set);
+		ret = xfrin_apply_changeset(&set->old_data,
+		                            &set->new_data,
+		                            contents_copy, set);
 		if (ret != KNOT_EOK) {
-			xfrin_rollback_update(old_contents,
-					       &contents_copy, chsets->changes);
+			xfrin_rollback_update(chsets, &contents_copy);
 			dbg_xfrin("Failed to apply changesets to zone: "
 				  "%s\n", knot_strerror(ret));
 			return ret;
@@ -1683,7 +1257,7 @@ int xfrin_apply_changesets(zone_t *zone,
 	if (ret != KNOT_EOK) {
 		dbg_xfrin("Failed to finalize updated zone: %s\n",
 			  knot_strerror(ret));
-		xfrin_rollback_update(old_contents, &contents_copy, chsets->changes);
+		xfrin_rollback_update(chsets, &contents_copy);
 		return ret;
 	}
 
diff --git a/src/knot/updates/xfr-in.h b/src/knot/updates/xfr-in.h
index 3a7f20654d2c9aac32c601e12c09a02abc809cd6..e6e772129e2db8501861038b33ef64ed3ecd5886 100644
--- a/src/knot/updates/xfr-in.h
+++ b/src/knot/updates/xfr-in.h
@@ -155,7 +155,6 @@ int xfrin_apply_changesets(zone_t *zone,
 /*!
  * \brief Applies DNSSEC changesets after DDNS.
  *
- * \param z_old           Old contents for possible rollbacks.
  * \param z_new           Post DDNS/reload zone.
  * \param sec_chsets      Changes with RRSIGs/NSEC(3)s.
  * \param chsets          DDNS/reload changes, for rollback.
@@ -165,7 +164,7 @@ int xfrin_apply_changesets(zone_t *zone,
  * by the UPDATE-processing function. It uses new and old zones from this
  * operation.
  */
-int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
+int xfrin_apply_changesets_dnssec_ddns(zone_t *zone,
                                        knot_zone_contents_t *z_new,
                                        knot_changesets_t *sec_chsets,
                                        knot_changesets_t *chsets);
@@ -174,9 +173,6 @@ int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
  * \brief Applies changesets directly to the zone, without copying it.
  *
  * \param contents Zone contents to apply the changesets to. Will be modified.
- * \param changes  Structure to store changes made during application. It
- *                 doesn't have to be empty, present changes will not be
- *                 modified.
  * \param chsets   Changesets to be applied to the zone.
  *
  * \retval KNOT_EOK if successful.
@@ -184,7 +180,6 @@ int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
  * \return Other error code if the application went wrong.
  */
 int xfrin_apply_changesets_directly(knot_zone_contents_t *contents,
-                                    knot_changes_t *changes,
                                     knot_changesets_t *chsets);
 
 int xfrin_prepare_zone_copy(knot_zone_contents_t *old_contents,
@@ -203,25 +198,19 @@ int xfrin_switch_zone(zone_t *zone,
                       knot_zone_contents_t *new_contents,
                       int transfer_type);
 
-void xfrin_cleanup_successful_update(knot_changes_t *changes);
-
-void xfrin_rollback_update(knot_zone_contents_t *old_contents,
-                           knot_zone_contents_t **new_contents,
-                           knot_changes_t *changes);
+void xfrin_rollback_update(knot_changesets_t *chgs,
+                           knot_zone_contents_t **new_contents);
 
 int xfrin_copy_rrset(knot_node_t *node, uint16_t type,
-                     knot_rrset_t **rrset, knot_changes_t *changes,
-                     int save_new);
+                     knot_rrset_t **rrset);
 
-int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
-                         knot_changes_t *changes, int save_new);
+int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy);
 
 int xfrin_replace_rrset_in_node(knot_node_t *node,
                                 knot_rrset_t *rrset_new,
-                                knot_changes_t *changes,
                                 knot_zone_contents_t *contents);
 
-void xfrin_zone_contents_free(knot_zone_contents_t **contents);
+void xfrin_cleanup_successful_update(knot_changesets_t *chgs);
 
 #endif /* _KNOTXFR_IN_H_ */
 
diff --git a/src/knot/zone/estimator.c b/src/knot/zone/estimator.c
index dd793ddf75c3c212f01e8525953727b4391945d2..239ac8aeaa67539d82bca403c3f6327ac6ed07fa 100644
--- a/src/knot/zone/estimator.c
+++ b/src/knot/zone/estimator.c
@@ -110,7 +110,7 @@ static int insert_dname_into_table(hattrie_t *table, knot_dname_t *d,
 static void rrset_memsize(zone_estim_t *est, const zs_scanner_t *scanner)
 {
 	// Handle RRSet's owner
-	knot_dname_t *owner = knot_dname_copy(scanner->r_owner);
+	knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL);
 	if (owner == NULL) {
 		return;
 	}
@@ -123,7 +123,7 @@ static void rrset_memsize(zone_estim_t *est, const zs_scanner_t *scanner)
 		est->dname_size += dname_memsize(owner);
 		// Trie's nodes handled at the end of computation
 	}
-	knot_dname_free(&owner);
+	knot_dname_free(&owner, NULL);
 	assert(n);
 
 	// We will always add RDATA
diff --git a/src/knot/zone/node.c b/src/knot/zone/node.c
index aae2ed8e3bd97060a4416640630e98b158763171..8a131da05a5e79c1a059b02e87b09287b4134911 100644
--- a/src/knot/zone/node.c
+++ b/src/knot/zone/node.c
@@ -23,9 +23,11 @@
 #include "libknot/common.h"
 #include "knot/zone/node.h"
 #include "libknot/rrset.h"
+#include "libknot/rr.h"
 #include "libknot/rdata.h"
 #include "common/descriptor.h"
 #include "common/debug.h"
+#include "common/mempattern.h"
 
 /*----------------------------------------------------------------------------*/
 /* Non-API functions                                                          */
@@ -67,6 +69,24 @@ static inline void knot_node_flags_clear(knot_node_t *node, uint8_t flag)
 	node->flags &= ~flag;
 }
 
+static void rr_data_clear(struct rr_data *data, mm_ctx_t *mm)
+{
+	knot_rrs_clear(&data->rrs, mm);
+	free(data->additional);
+}
+
+static int rr_data_from(const knot_rrset_t *rrset, struct rr_data *data, mm_ctx_t *mm)
+{
+	int ret = knot_rrs_copy(&data->rrs, &rrset->rrs, mm);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	data->type = rrset->type;
+	data->additional = NULL;
+
+	return KNOT_EOK;
+}
+
 /*----------------------------------------------------------------------------*/
 /* API functions                                                              */
 /*----------------------------------------------------------------------------*/
@@ -86,11 +106,11 @@ knot_node_t *knot_node_new(const knot_dname_t *owner, knot_node_t *parent,
 	 *        do the copying (or not if he decides to do so).
 	 */
 	if (owner) {
-		ret->owner = knot_dname_copy(owner);
+		ret->owner = knot_dname_copy(owner, NULL);
 	}
 
 	knot_node_set_parent(ret, parent);
-	ret->rrset_tree = NULL;
+	ret->rrs = NULL;
 	ret->flags = flags;
 
 	assert(ret->children == 0);
@@ -98,95 +118,71 @@ knot_node_t *knot_node_new(const knot_dname_t *owner, knot_node_t *parent,
 	return ret;
 }
 
-int knot_node_add_rrset_no_merge(knot_node_t *node, knot_rrset_t *rrset)
+static int knot_node_add_rrset_no_merge(knot_node_t *node, const knot_rrset_t *rrset)
 {
 	if (node == NULL) {
 		return KNOT_EINVAL;
 	}
 
-	size_t nlen = (node->rrset_count + 1) * sizeof(knot_rrset_t*);
-	void *p = realloc(node->rrset_tree, nlen);
+	const size_t nlen = (node->rrset_count + 1) * sizeof(struct rr_data);
+	void *p = realloc(node->rrs, nlen);
 	if (p == NULL) {
 		return KNOT_ENOMEM;
 	}
-	node->rrset_tree = p;
-	node->rrset_tree[node->rrset_count] = rrset;
+	node->rrs = p;
+	int ret = rr_data_from(rrset, node->rrs + node->rrset_count, NULL);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
 	++node->rrset_count;
 
 	return KNOT_EOK;
 }
 
-int knot_node_add_rrset_replace(knot_node_t *node, knot_rrset_t *rrset)
-{
-	if (node == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	for (uint16_t i = 0; i < node->rrset_count; ++i) {
-		if (node->rrset_tree[i]->type == rrset->type) {
-		node->rrset_tree[i] = rrset;
-		}
-	}
-
-	return knot_node_add_rrset_no_merge(node, rrset);
-}
-
-int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset,
-                        bool *ttl_err)
+int knot_node_add_rrset(knot_node_t *node, const knot_rrset_t *rrset,  bool *ttl_err)
 {
 	if (node == NULL || rrset == NULL) {
 		return KNOT_EINVAL;
 	}
 
 	for (uint16_t i = 0; i < node->rrset_count; ++i) {
-		if (node->rrset_tree[i]->type == rrset->type) {
-			int merged, deleted_rrs;
+		if (node->rrs[i].type == rrset->type) {
+			struct rr_data *node_data = &node->rrs[i];
 
 			/* Check if the added RR has the same TTL as the first
 			 * RR in the RRSet.
 			 */
-			if (ttl_err && knot_rrset_type(rrset) != KNOT_RRTYPE_RRSIG
-			    && !knot_rrset_ttl_equal(rrset,
-			                             node->rrset_tree[i])) {
+			knot_rr_t *first = knot_rrs_rr(&node_data->rrs, 0);
+			uint32_t inserted_ttl = knot_rrset_rr_ttl(rrset, 0);
+			if (ttl_err && rrset->type != KNOT_RRTYPE_RRSIG &&
+			    inserted_ttl != knot_rr_ttl(first)) {
 				*ttl_err = true;
 			}
 
-			int ret = knot_rrset_merge_sort(node->rrset_tree[i],
-			                                rrset, &merged,
-			                                &deleted_rrs, NULL);
-			if (ret != KNOT_EOK) {
-				return ret;
-			} else if (merged || deleted_rrs) {
-				return 1;
-			} else {
-				return 0;
-			}
+			return knot_rrs_merge(&node_data->rrs, &rrset->rrs, NULL);
 		}
 	}
 
+	// New RRSet (with one RR)
 	return knot_node_add_rrset_no_merge(node, rrset);
 }
 
 /*----------------------------------------------------------------------------*/
 
-const knot_rrset_t *knot_node_rrset(const knot_node_t *node,
-                                        uint16_t type)
+const knot_rrs_t *knot_node_rrs(const knot_node_t *node, uint16_t type)
 {
-	return knot_node_get_rrset(node, type);
+	return (const knot_rrs_t *)knot_node_get_rrs(node, type);
 }
 
-/*----------------------------------------------------------------------------*/
-
-knot_rrset_t *knot_node_get_rrset(const knot_node_t *node, uint16_t type)
+knot_rrs_t *knot_node_get_rrs(const knot_node_t *node, uint16_t type)
 {
 	if (node == NULL) {
 		return NULL;
 	}
 
-	knot_rrset_t **rrs = node->rrset_tree;
 	for (uint16_t i = 0; i < node->rrset_count; ++i) {
-		if (rrs[i]->type == type) {
-			return rrs[i];
+		if (node->rrs[i].type == type) {
+			return &node->rrs[i].rrs;
 		}
 	}
 
@@ -195,36 +191,39 @@ knot_rrset_t *knot_node_get_rrset(const knot_node_t *node, uint16_t type)
 
 /*----------------------------------------------------------------------------*/
 
-knot_rrset_t *knot_node_remove_rrset(knot_node_t *node, uint16_t type)
+knot_rrset_t *knot_node_create_rrset(const knot_node_t *node, uint16_t type)
 {
 	if (node == NULL) {
 		return NULL;
 	}
 
-	uint16_t i = 0;
-	knot_rrset_t *ret = NULL;
-	knot_rrset_t **rrs = node->rrset_tree;
-	for (; i < node->rrset_count && ret == NULL; ++i) {
-		if (rrs[i]->type == type) {
-			ret = rrs[i];
-			memmove(rrs + i, rrs + i + 1, (node->rrset_count - i - 1) * sizeof(knot_rrset_t *));
-			--node->rrset_count;
+	for (uint16_t i = 0; i < node->rrset_count; ++i) {
+		if (node->rrs[i].type == type) {
+			knot_rrset_t rrset = knot_node_rrset_at(node, i);
+			return knot_rrset_copy(&rrset, NULL);
 		}
 	}
 
-	return ret;
+	return NULL;
 }
 
 /*----------------------------------------------------------------------------*/
 
-void knot_node_remove_all_rrsets(knot_node_t *node)
+void knot_node_remove_rrset(knot_node_t *node, uint16_t type)
 {
 	if (node == NULL) {
 		return;
 	}
 
-	// remove RRSets but do not delete them
-	node->rrset_count = 0;
+	for (int i = 0; i < node->rrset_count; ++i) {
+		if (node->rrs[i].type == type) {
+			memmove(node->rrs + i, node->rrs + i + 1, (node->rrset_count - i - 1) * sizeof(struct rr_data));
+			--node->rrset_count;
+			return;
+		}
+	}
+
+	return;
 }
 
 /*----------------------------------------------------------------------------*/
@@ -240,48 +239,6 @@ short knot_node_rrset_count(const knot_node_t *node)
 
 /*----------------------------------------------------------------------------*/
 
-knot_rrset_t **knot_node_get_rrsets(const knot_node_t *node)
-{
-	if (node == NULL || node->rrset_count == 0) {
-		return NULL;
-	}
-
-	size_t rrlen = node->rrset_count * sizeof(knot_rrset_t*);
-	knot_rrset_t **cpy = malloc(rrlen);
-	if (cpy != NULL) {
-		memcpy(cpy, node->rrset_tree, rrlen);
-	}
-
-	return cpy;
-}
-
-/*----------------------------------------------------------------------------*/
-
-const knot_rrset_t **knot_node_rrsets(const knot_node_t *node)
-{
-	if (node == NULL) {
-		return NULL;
-	}
-
-	return (const knot_rrset_t **)knot_node_get_rrsets(node);
-}
-
-knot_rrset_t **knot_node_get_rrsets_no_copy(const knot_node_t *node)
-{
-	if (node == NULL) {
-		return NULL;
-	}
-
-	return node->rrset_tree;
-}
-
-const knot_rrset_t **knot_node_rrsets_no_copy(const knot_node_t *node)
-{
-	return (const knot_rrset_t **)knot_node_get_rrsets_no_copy(node);
-}
-
-/*----------------------------------------------------------------------------*/
-
 const knot_node_t *knot_node_parent(const knot_node_t *node)
 {
 	if (node == NULL) {
@@ -455,63 +412,6 @@ const knot_node_t *knot_node_wildcard_child(const knot_node_t *node)
 
 /*----------------------------------------------------------------------------*/
 
-const knot_node_t *knot_node_new_node(const knot_node_t *node)
-{
-	if (node == NULL) {
-		return NULL;
-	}
-
-	return node->new_node;
-}
-
-/*----------------------------------------------------------------------------*/
-
-knot_node_t *knot_node_get_new_node(const knot_node_t *node)
-{
-	if (node == NULL) {
-		return NULL;
-	}
-
-	return node->new_node;
-}
-
-/*----------------------------------------------------------------------------*/
-
-void knot_node_set_new_node(knot_node_t *node,
-                              knot_node_t *new_node)
-{
-	if (node == NULL) {
-		return;
-	}
-
-	node->new_node = new_node;
-}
-
-/*----------------------------------------------------------------------------*/
-
-void knot_node_update_ref(knot_node_t **ref)
-{
-	if (*ref != NULL && (*ref)->new_node != NULL) {
-		*ref = (*ref)->new_node;
-	}
-}
-
-/*----------------------------------------------------------------------------*/
-
-void knot_node_update_refs(knot_node_t *node)
-{
-	// reference to previous node
-	knot_node_update_ref(&node->prev);
-	// reference to parent
-	knot_node_update_ref(&node->parent);
-	// reference to wildcard child
-	knot_node_update_ref(&node->wildcard_child);
-	// reference to NSEC3 node
-	knot_node_update_ref(&node->nsec3_node);
-}
-
-/*----------------------------------------------------------------------------*/
-
 void knot_node_set_deleg_point(knot_node_t *node)
 {
 	if (node == NULL) {
@@ -641,7 +541,6 @@ int knot_node_is_apex(const knot_node_t *node)
 	return knot_node_flags_get(node, KNOT_NODE_FLAGS_APEX);
 }
 
-
 /*----------------------------------------------------------------------------*/
 
 void knot_node_free_rrsets(knot_node_t *node)
@@ -650,9 +549,8 @@ void knot_node_free_rrsets(knot_node_t *node)
 		return;
 	}
 
-	knot_rrset_t **rrs = node->rrset_tree;
 	for (uint16_t i = 0; i < node->rrset_count; ++i) {
-		knot_rrset_free(&(rrs[i]), NULL);
+		rr_data_clear(&node->rrs[i], NULL);
 	}
 }
 
@@ -666,14 +564,14 @@ void knot_node_free(knot_node_t **node)
 
 	dbg_node_detail("Freeing node: %p\n", *node);
 
-	if ((*node)->rrset_tree != NULL) {
+	if ((*node)->rrs != NULL) {
 		dbg_node_detail("Freeing RRSets.\n");
-		free((*node)->rrset_tree);
-		(*node)->rrset_tree = NULL;
+		free((*node)->rrs);
+		(*node)->rrs = NULL;
 		(*node)->rrset_count = 0;
 	}
 
-	knot_dname_free(&(*node)->owner);
+	knot_dname_free(&(*node)->owner, NULL);
 
 	free(*node);
 	*node = NULL;
@@ -694,21 +592,24 @@ int knot_node_shallow_copy(const knot_node_t *from, knot_node_t **to)
 	if (*to == NULL) {
 		return KNOT_ENOMEM;
 	}
+	memset(*to, 0, sizeof(knot_node_t));
 
-	// do not use the API function to set parent, so that children count
-	// is not changed
-	memcpy(*to, from, sizeof(knot_node_t));
-	(*to)->owner = knot_dname_copy(from->owner);
+	// Copy owner
+	(*to)->owner = knot_dname_copy(from->owner, NULL);
+	if ((*to)->owner == NULL) {
+		free(*to);
+		return KNOT_ENOMEM;
+	}
 
 	// copy RRSets
-	size_t rrlen = sizeof(knot_rrset_t*) * from->rrset_count;
-	(*to)->rrset_tree = malloc(rrlen);
-	if ((*to)->rrset_tree == NULL) {
-		free(*to);
-		*to = NULL;
+	(*to)->rrset_count = from->rrset_count;
+	size_t rrlen = sizeof(struct rr_data) * from->rrset_count;
+	(*to)->rrs = malloc(rrlen);
+	if ((*to)->rrs == NULL) {
+		knot_node_free(to);
 		return KNOT_ENOMEM;
 	}
-	memcpy((*to)->rrset_tree, from->rrset_tree, rrlen);
+	memcpy((*to)->rrs, from->rrs, rrlen);
 
 	return KNOT_EOK;
 }
@@ -719,15 +620,15 @@ bool knot_node_rrtype_is_signed(const knot_node_t *node, uint16_t type)
 		return false;
 	}
 
-	const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG);
+	const knot_rrs_t *rrsigs = knot_node_rrs(node, KNOT_RRTYPE_RRSIG);
 	if (rrsigs == NULL) {
 		return false;
 	}
 
-	uint16_t rrsigs_rdata_count = knot_rrset_rr_count(rrsigs);
+	uint16_t rrsigs_rdata_count = rrsigs->rr_count;
 	for (uint16_t i = 0; i < rrsigs_rdata_count; ++i) {
 		const uint16_t type_covered =
-			knot_rdata_rrsig_type_covered(rrsigs, i);
+			knot_rrs_rrsig_type_covered(rrsigs, i);
 		if (type_covered == type) {
 			return true;
 		}
@@ -735,3 +636,9 @@ bool knot_node_rrtype_is_signed(const knot_node_t *node, uint16_t type)
 
 	return false;
 }
+
+bool knot_node_rrtype_exists(const knot_node_t *node, uint16_t type)
+{
+	return knot_node_rrs(node, type) != NULL;
+}
+
diff --git a/src/knot/zone/node.h b/src/knot/zone/node.h
index c7b00420cf9a6d582c8475d85df3a2d818a41f60..42fd2908697e5945565bd84d02a2e4fbaa86bccb 100644
--- a/src/knot/zone/node.h
+++ b/src/knot/zone/node.h
@@ -28,13 +28,13 @@
 #ifndef _KNOT_NODE_H_
 #define _KNOT_NODE_H_
 
+#include "common/descriptor.h"
 #include "libknot/dname.h"
 #include "libknot/rrset.h"
+#include "libknot/rr.h"
 
-/*! \brief RRSet count in node if there is only NSEC (and possibly its RRSIG).*/
-#define KNOT_NODE_RRSET_COUNT_ONLY_NSEC 2
+struct rr_data;
 
-/*----------------------------------------------------------------------------*/
 /*!
  * \brief Structure representing one node in a domain name tree, i.e. one domain
  *        name in a zone.
@@ -45,8 +45,8 @@ struct knot_node {
 	knot_dname_t *owner; /*!< Domain name being the owner of this node. */
 	struct knot_node *parent; /*!< Parent node in the name hierarchy. */
 
-	/*! \brief Type-ordered list of RRSets belonging to this node. */
-	knot_rrset_t **rrset_tree;
+	/*! \brief Type-ordered array of RRSets belonging to this node. */
+	struct rr_data *rrs;
 
 	/*! \brief Wildcard node being the direct descendant of this node. */
 	struct knot_node *wildcard_child;
@@ -68,8 +68,6 @@ struct knot_node {
 	 */
 	struct knot_node *nsec3_node;
 
-	struct knot_node *new_node;
-
 	unsigned int children;
 
 	uint16_t rrset_count; /*!< Number of RRSets stored in the node. */
@@ -87,6 +85,13 @@ struct knot_node {
 
 typedef struct knot_node knot_node_t;
 
+/*!< \brief Structure storing RR data. */
+struct rr_data {
+	uint16_t type; /*!< \brief RR type of data. */
+	knot_rrs_t rrs; /*!< \brief Data of given type. */
+	knot_node_t **additional; /*!< \brief Additional nodes with glues. */
+};
+
 /*----------------------------------------------------------------------------*/
 /*! \brief Flags used to mark nodes with some property. */
 typedef enum {
@@ -125,29 +130,14 @@ knot_node_t *knot_node_new(const knot_dname_t *owner, knot_node_t *parent,
  *
  * \param node Node to add the RRSet to.
  * \param rrset RRSet to add.
- * \param rrset Stores destination RRSet in case of merge.
  *
  * \retval KNOT_EOK on success.
  * \retval KNOT_ERROR if the RRSet could not be inserted.
  */
-int knot_node_add_rrset(knot_node_t *node, knot_rrset_t *rrset,
-                        bool *ttl_err);
+int knot_node_add_rrset(knot_node_t *node, const knot_rrset_t *rrset, bool *ttl_err);
 
-int knot_node_add_rrset_replace(knot_node_t *node, knot_rrset_t *rrset);
-
-int knot_node_add_rrset_no_merge(knot_node_t *node, knot_rrset_t *rrset);
-
-/*!
- * \brief Returns the RRSet of the given type from the node.
- *
- * \param node Node to get the RRSet from.
- * \param type Type of the RRSet to retrieve.
- *
- * \return RRSet from node \a node having type \a type, or NULL if no such
- *         RRSet exists in this node.
- */
-const knot_rrset_t *knot_node_rrset(const knot_node_t *node,
-                                        uint16_t type);
+const knot_rrs_t *knot_node_rrs(const knot_node_t *node, uint16_t type);
+knot_rrs_t *knot_node_get_rrs(const knot_node_t *node, uint16_t type);
 
 /*!
  * \brief Returns the RRSet of the given type from the node (non-const version).
@@ -158,11 +148,9 @@ const knot_rrset_t *knot_node_rrset(const knot_node_t *node,
  * \return RRSet from node \a node having type \a type, or NULL if no such
  *         RRSet exists in this node.
  */
-knot_rrset_t *knot_node_get_rrset(const knot_node_t *node, uint16_t type);
+knot_rrset_t *knot_node_create_rrset(const knot_node_t *node, uint16_t type);
 
-knot_rrset_t *knot_node_remove_rrset(knot_node_t *node, uint16_t type);
-
-void knot_node_remove_all_rrsets(knot_node_t *node);
+void knot_node_remove_rrset(knot_node_t *node, uint16_t type);
 
 /*!
  * \brief Returns number of RRSets in the node.
@@ -173,31 +161,6 @@ void knot_node_remove_all_rrsets(knot_node_t *node);
  */
 short knot_node_rrset_count(const knot_node_t *node);
 
-/*!
- * \brief Returns all RRSets from the node.
- *
- * \param node Node to get the RRSets from.
- *
- * \return Newly allocated array of RRSets or NULL if an error occured.
- */
-knot_rrset_t **knot_node_get_rrsets(const knot_node_t *node);
-
-/*!
- * \brief Returns all RRSets from the node.
- *
- * \note This function is identical to knot_node_get_rrsets(), only it returns
- *       non-modifiable data.
- *
- * \param node Node to get the RRSets from.
- *
- * \return Newly allocated array of RRSets or NULL if an error occured.
- */
-const knot_rrset_t **knot_node_rrsets(const knot_node_t *node);
-const knot_rrset_t **knot_node_rrsets_no_copy(const knot_node_t *node);
-knot_rrset_t **knot_node_get_rrsets_no_copy(const knot_node_t *node);
-
-int knot_node_count_rrsets(const knot_node_t *node);
-
 /*!
  * \brief Returns the parent of the node.
  *
@@ -321,20 +284,6 @@ void knot_node_set_wildcard_child(knot_node_t *node,
 
 knot_node_t *knot_node_get_wildcard_child(const knot_node_t *node);
 
-//const knot_node_t *knot_node_current(const knot_node_t *node);
-
-//knot_node_t *knot_node_get_current(knot_node_t *node);
-
-const knot_node_t *knot_node_new_node(const knot_node_t *node);
-
-knot_node_t *knot_node_get_new_node(const knot_node_t *node);
-
-void knot_node_set_new_node(knot_node_t *node, knot_node_t *new_node);
-
-void knot_node_update_ref(knot_node_t **ref);
-
-void knot_node_update_refs(knot_node_t *node);
-
 /*!
  * \brief Mark the node as a delegation point.
  *
@@ -417,6 +366,7 @@ int knot_node_shallow_copy(const knot_node_t *from, knot_node_t **to);
 
 /*!
  * \brief Checks whether node contains an RRSIG for given type.
+ *
  * \param node  Node to check in.
  * \param node  Type to check for.
  *
@@ -424,6 +374,69 @@ int knot_node_shallow_copy(const knot_node_t *from, knot_node_t **to);
  */
 bool knot_node_rrtype_is_signed(const knot_node_t *node, uint16_t type);
 
+/*!
+ * \brief Checks whether node contains RRSet for given type.
+ *
+ * \param node  Node to check in.
+ * \param node  Type to check for.
+ *
+ * \return True/False.
+ */
+bool knot_node_rrtype_exists(const knot_node_t *node, uint16_t type);
+
+/*!
+ * \brief Returns RRSet structure initialized with data from node.
+ *
+ * \param node   Node containing RRSet.
+ * \param type   RRSet type we want to get.
+ *
+ * \return RRSet structure with wanted type, or empty RRSet.
+ */
+static inline knot_rrset_t knot_node_rrset(const knot_node_t *node, uint16_t type)
+{
+	knot_rrset_t rrset;
+	for (uint16_t i = 0; node && i < node->rrset_count; ++i) {
+		if (node->rrs[i].type == type) {
+			struct rr_data *rr_data = &node->rrs[i];
+			rrset.owner = node->owner;
+			rrset.type = type;
+			rrset.rclass = KNOT_CLASS_IN;
+			rrset.rrs = rr_data->rrs;
+			rrset.additional = rr_data->additional;
+			return rrset;
+		}
+	}
+	knot_rrset_init_empty(&rrset);
+	return rrset;
+}
+
+/*!
+ * \brief Returns RRSet structure initialized with data from node at position
+ *        equal to \a pos.
+ *
+ * \param node  Node containing RRSet.
+ * \param pos   RRSet position we want to get.
+ *
+ * \return RRSet structure with data from wanted position, or empty RRSet.
+ */
+static inline knot_rrset_t knot_node_rrset_at(const knot_node_t *node, size_t pos)
+{
+	knot_rrset_t rrset;
+	if (node == NULL || pos >= node->rrset_count) {
+		knot_rrset_init_empty(&rrset);
+		return rrset;
+	}
+
+	struct rr_data *rr_data = &node->rrs[pos];
+	rrset.owner = node->owner;
+	rrset.type = rr_data->type;
+	rrset.rclass = KNOT_CLASS_IN;
+	rrset.rrs = rr_data->rrs;
+	rrset.additional = rr_data->additional;
+
+	return rrset;
+}
+
 #endif /* _KNOT_NODE_H_ */
 
 /*! @} */
diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c
index e00e2aa0697b28e0056ec1047917a7a6dfd74194..5135adb978fe1c49125e8062abcc067a8612e7dd 100644
--- a/src/knot/zone/semantic-check.c
+++ b/src/knot/zone/semantic-check.c
@@ -18,13 +18,14 @@
 #include <stdint.h>
 #include <sys/types.h>
 #include <sys/socket.h>
-#include <netinet/in.h>	
+#include <netinet/in.h>
 #include <arpa/inet.h>
 
 #include "knot/knot.h"
 #include "knot/other/debug.h"
 #include "libknot/libknot.h"
 #include "libknot/dnssec/key.h"
+#include "libknot/dnssec/rrset-sign.h"
 #include "common/base32hex.h"
 #include "common/crc.h"
 #include "common/descriptor.h"
@@ -110,7 +111,6 @@ static char *error_messages[(-ZC_ERR_UNKNOWN) + 1] = {
 	/* ^
 	   | Important errors (to be logged on first occurence and counted) */
 
-
 	/* Below are errors of lesser importance, to be counted unless
 	   specified otherwise */
 
@@ -165,7 +165,7 @@ static void log_error_from_node(err_handler_t *handler,
 		log_zone_warning("Unknown error.\n");
 		return;
 	}
-	
+
 	if (node == NULL) {
 		log_zone_warning("Total number of warnings is: %d for error: %s\n",
 		                 handler->errors[-error], error_messages[-error]);
@@ -195,8 +195,8 @@ int err_handler_handle_error(err_handler_t *handler, const knot_node_t *node,
 		return KNOT_EINVAL;
 	}
 
-	/* 
-	 * A missing SOA can only occur once, so there needn't be 
+	/*
+	 * A missing SOA can only occur once, so there needn't be
 	 * an option for it.
 	 */
 
@@ -286,7 +286,7 @@ static int check_dnskey_rdata(const knot_rrset_t *rrset, size_t rdata_pos)
  */
 static int check_rrsig_rdata(err_handler_t *handler,
                              const knot_node_t *node,
-                             const knot_rrset_t *rrsig,
+                             const knot_rrs_t *rrsig,
                              size_t rr_pos,
                              const knot_rrset_t *rrset,
                              const knot_rrset_t *dnskey_rrset)
@@ -294,14 +294,13 @@ static int check_rrsig_rdata(err_handler_t *handler,
 	/* Prepare additional info string. */
 	char info_str[50] = { '\0' };
 	char type_str[16] = { '\0' };
-	knot_rrtype_to_string(knot_rrset_type(rrset), type_str, sizeof(type_str));
+	knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str));
 	int ret = snprintf(info_str, sizeof(info_str), "Record type: %s.", type_str);
 	if (ret < 0 || ret >= sizeof(info_str)) {
 		return KNOT_ENOMEM;
 	}
 
-	if (knot_rdata_rrsig_type_covered(rrsig, 0) !=
-	    knot_rrset_type(rrset)) {
+	if (knot_rrs_rrsig_type_covered(rrsig, 0) != rrset->type) {
 		/* zoneparser would not let this happen
 		 * but to be on the safe side
 		 */
@@ -311,13 +310,13 @@ static int check_rrsig_rdata(err_handler_t *handler,
 	}
 
 	/* label number at the 2nd index should be same as owner's */
-	uint8_t labels_rdata = knot_rdata_rrsig_labels(rrsig, rr_pos);
+	uint8_t labels_rdata = knot_rrs_rrsig_labels(rrsig, rr_pos);
 
-	int tmp = knot_dname_labels(knot_rrset_owner(rrset), NULL) - labels_rdata;
+	int tmp = knot_dname_labels(rrset->owner, NULL) - labels_rdata;
 
 	if (tmp != 0) {
 		/* if name has wildcard, label must not be included */
-		if (!knot_dname_is_wildcard(knot_rrset_owner(rrset))) {
+		if (!knot_dname_is_wildcard(rrset->owner)) {
 			err_handler_handle_error(handler, node,
 			                         ZC_ERR_RRSIG_RDATA_LABELS,
 			                         info_str);
@@ -331,8 +330,7 @@ static int check_rrsig_rdata(err_handler_t *handler,
 	}
 
 	/* check original TTL */
-	uint32_t original_ttl =
-		knot_rdata_rrsig_original_ttl(rrsig, rr_pos);
+	uint32_t original_ttl = knot_rrs_rrsig_original_ttl(rrsig, rr_pos);
 
 	uint16_t rr_count = knot_rrset_rr_count(rrset);
 	for (uint16_t i = 0; i < rr_count; ++i) {
@@ -344,26 +342,25 @@ static int check_rrsig_rdata(err_handler_t *handler,
 	}
 
 	/* Check for expired signature. */
-	if (knot_rdata_rrsig_sig_expiration(rrsig, rr_pos) < time(NULL)) {
+	if (knot_rrs_rrsig_sig_expiration(rrsig, rr_pos) < time(NULL)) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_RDATA_EXPIRATION,
 		                         info_str);
 	}
 
 	/* Check if DNSKEY exists. */
-	if (!dnskey_rrset) {
+	if (knot_rrset_empty(dnskey_rrset)) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_NO_DNSKEY, info_str);
 	}
 
 	/* signer's name is same as in the zone apex */
 	const knot_dname_t *signer_name =
-		knot_rdata_rrsig_signer_name(rrsig, rr_pos);
+		knot_rrs_rrsig_signer_name(rrsig, rr_pos);
 
 	/* dnskey is in the apex node */
-	if (dnskey_rrset &&
-	    knot_dname_cmp(signer_name, knot_rrset_owner(dnskey_rrset)) != 0
-	) {
+	if (!knot_rrset_empty(dnskey_rrset) &&
+	    !knot_dname_is_equal(signer_name, dnskey_rrset->owner)) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_RDATA_DNSKEY_OWNER,
 		                         info_str);
@@ -372,18 +369,18 @@ static int check_rrsig_rdata(err_handler_t *handler,
 	/* Compare algorithm, key tag and signer's name with DNSKEY rrset
 	 * one of the records has to match. Signer name has been checked
 	 * before */
-	
+
 	int match = 0;
-	uint8_t rrsig_alg = knot_rdata_rrsig_algorithm(rrsig, rr_pos);
-	uint16_t key_tag_rrsig = knot_rdata_rrsig_key_tag(rrsig, rr_pos);
+	uint8_t rrsig_alg = knot_rrs_rrsig_algorithm(rrsig, rr_pos);
+	uint16_t key_tag_rrsig = knot_rrs_rrsig_key_tag(rrsig, rr_pos);
 	for (uint16_t i = 0; i < knot_rrset_rr_count(dnskey_rrset) &&
 	     !match; ++i) {
 		uint8_t dnskey_alg =
-			knot_rdata_dnskey_alg(dnskey_rrset, i);
+			knot_rrs_dnskey_alg(&dnskey_rrset->rrs, i);
 		if (rrsig_alg != dnskey_alg) {
 			continue;
 		}
-		
+
 		/* Calculate keytag. */
 		uint16_t dnskey_key_tag =
 			knot_keytag(knot_rrset_rr_rdata(dnskey_rrset, i),
@@ -391,7 +388,7 @@ static int check_rrsig_rdata(err_handler_t *handler,
 		if (key_tag_rrsig != dnskey_key_tag) {
 			continue;
 		}
-		
+
 		/* Final step - check DNSKEY validity. */
 		if (check_dnskey_rdata(dnskey_rrset, i) == KNOT_EOK) {
 			match = 1;
@@ -401,7 +398,7 @@ static int check_rrsig_rdata(err_handler_t *handler,
 			                         "DNSKEY RDATA not matching.");
 		}
 	}
-	
+
 	if (!match) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_NO_DNSKEY, info_str);
@@ -432,24 +429,24 @@ static int check_rrsig_in_rrset(err_handler_t *handler,
 	/* Prepare additional info string. */
 	char info_str[50] = { '\0' };
 	char type_str[16] = { '\0' };
-	knot_rrtype_to_string(knot_rrset_type(rrset), type_str, sizeof(type_str));
+	knot_rrtype_to_string(rrset->type, type_str, sizeof(type_str));
 	int ret = snprintf(info_str, sizeof(info_str), "Record type: %s.",
 	                   type_str);
 	if (ret < 0 || ret >= sizeof(info_str)) {
 		return KNOT_ENOMEM;
 	}
-	knot_rrset_t *rrsigs = NULL;
-	ret = knot_rrset_synth_rrsig(rrset->owner,
-	                             rrset->type,
-	                             knot_node_rrset(node, KNOT_RRTYPE_RRSIG),
-	                             &rrsigs, NULL);
+	knot_rrs_t rrsigs;
+	knot_rrs_init(&rrsigs);
+	ret = knot_synth_rrsig(rrset->type,
+	                           knot_node_rrs(node, KNOT_RRTYPE_RRSIG),
+	                           &rrsigs, NULL);
 	if (ret != KNOT_EOK) {
 		if (ret != KNOT_ENOENT) {
 			return ret;
 		}
 	}
 
-	if (rrsigs == NULL) {
+	if (ret == KNOT_ENOENT) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_NO_RRSIG,
 		                         info_str);
@@ -464,36 +461,15 @@ static int check_rrsig_in_rrset(err_handler_t *handler,
 		/* Safe to continue, nothing is malformed. */
 	}
 
-	/* Different owner, class, ttl */
-	if (knot_dname_cmp(knot_rrset_owner(rrset),
-				 knot_rrset_owner(rrsigs)) != 0) {
-		err_handler_handle_error(handler, node,
-		                         ZC_ERR_RRSIG_OWNER,
-		                         info_str);
-	}
-
-	if (knot_rrset_class(rrset) != knot_rrset_class(rrsigs)) {
-		err_handler_handle_error(handler, node,
-		                         ZC_ERR_RRSIG_CLASS,
-		                         info_str);
-	}
-
-	if (!knot_rrset_ttl_equal(rrset, rrsigs)) {
+	const knot_rr_t *sig_rr = knot_rrs_rr(&rrsigs, 0);
+	if (knot_rrset_rr_ttl(rrset, 0) != knot_rr_ttl(sig_rr)) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_RRSIG_TTL,
 		                         info_str);
 	}
 
-	if (knot_rrset_rr_count(rrsigs) == 0) {
-		err_handler_handle_error(handler, node,
-		                         ZC_ERR_RRSIG_RDATA_SIGNED_WRONG,
-		                         info_str);
-		knot_rrset_free(&rrsigs, NULL);
-		return KNOT_EOK;
-	}
-	
-	for (uint16_t i = 0; i < knot_rrset_rr_count(rrsigs); ++i) {
-		int ret = check_rrsig_rdata(handler, node, rrsigs, i, rrset,
+	for (uint16_t i = 0; i < (&rrsigs)->rr_count; ++i) {
+		int ret = check_rrsig_rdata(handler, node, &rrsigs, i, rrset,
 		                            dnskey_rrset);
 		if (ret != KNOT_EOK) {
 			dbg_semcheck("Could not check RRSIG properly (%s).\n",
@@ -502,7 +478,7 @@ static int check_rrsig_in_rrset(err_handler_t *handler,
 		}
 	}
 
-	knot_rrset_free(&rrsigs, NULL);
+	knot_rrs_clear(&rrsigs, NULL);
 	return ret;
 }
 
@@ -533,23 +509,22 @@ static int get_bit(uint8_t *bits, size_t index)
  * \retval KNOT_OK on success.
  * \retval KNOT_NOMEM on memory error.
  */
-static int rdata_nsec_to_type_array(const knot_rrset_t *rrset, size_t pos,
-				    uint16_t **array, size_t *count)
+static int rdata_nsec_to_type_array(const knot_rrs_t *rrs, uint16_t type,
+                                    size_t pos, uint16_t **array, size_t *count)
 {
 	assert(*array == NULL);
-	assert(rrset->type == KNOT_RRTYPE_NSEC || rrset->type == KNOT_RRTYPE_NSEC3);
-	
+
 	uint8_t *data = NULL;
 	uint16_t rr_bitmap_size = 0;
-	if (rrset->type == KNOT_RRTYPE_NSEC) {
-		knot_rdata_nsec_bitmap(rrset, &data, &rr_bitmap_size);
+	if (type == KNOT_RRTYPE_NSEC) {
+		knot_rrs_nsec_bitmap(rrs, &data, &rr_bitmap_size);
 	} else {
-		knot_rdata_nsec3_bitmap(rrset, pos, &data, &rr_bitmap_size);
+		knot_rrs_nsec3_bitmap(rrs, pos, &data, &rr_bitmap_size);
 	}
 	if (data == NULL) {
 		return KNOT_EMALF;
 	}
-	
+
 	*count = 0;
 	int increment = 0;
 	for (int i = 0; i < rr_bitmap_size; i += increment) {
@@ -615,7 +590,7 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 	if (nsec3_node == NULL) {
 		/* I know it's probably not what RFCs say, but it will have to
 		 * do for now. */
-		if (knot_node_rrset(node, KNOT_RRTYPE_DS) != NULL) {
+		if (knot_node_rrtype_exists(node, KNOT_RRTYPE_DS)) {
 			err_handler_handle_error(handler, node,
 					ZC_ERR_NSEC3_UNSECURED_DELEGATION,
 			                         NULL);
@@ -642,15 +617,14 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 
 			assert(nsec3_previous);
 
-			const knot_rrset_t *previous_rrset =
-				knot_node_rrset(nsec3_previous,
-						KNOT_RRTYPE_NSEC3);
+			const knot_rrs_t *previous_rrs =
+				knot_node_rrs(nsec3_previous, KNOT_RRTYPE_NSEC3);
 
-			assert(previous_rrset);
+			assert(previous_rrs);
 
 			/* check for Opt-Out flag */
 			uint8_t flags =
-				knot_rdata_nsec3_flags(previous_rrset, 0);
+				knot_rrs_nsec3_flags(previous_rrs, 0);
 			uint8_t opt_out_mask = 1;
 
 			if (!(flags & opt_out_mask)) {
@@ -663,32 +637,29 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 		}
 	}
 
-	const knot_rrset_t *nsec3_rrset =
-		knot_node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3);
-
-	if (nsec3_rrset == NULL) {
+	const knot_rrs_t *nsec3_rrs = knot_node_rrs(nsec3_node,
+	                                            KNOT_RRTYPE_NSEC3);
+	if (nsec3_rrs == NULL) {
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_NSEC3_RDATA_CHAIN, NULL);
 		return KNOT_EOK;
 	}
 
-	const knot_rrset_t *soa_rrset =
-		knot_node_rrset(knot_zone_contents_apex(zone),
-	                        KNOT_RRTYPE_SOA);
-	assert(soa_rrset);
-	uint32_t minimum_ttl = knot_rdata_soa_minimum(soa_rrset);
-
-	if (knot_rrset_rr_ttl(nsec3_rrset, 0) != minimum_ttl) {
-			err_handler_handle_error(handler, node,
-						 ZC_ERR_NSEC3_RDATA_TTL, NULL);
+	const knot_rr_t *nsec3_rr = knot_rrs_rr(nsec3_rrs, 0);
+	const knot_rrs_t *soa_rrs = knot_node_rrs(zone->apex, KNOT_RRTYPE_SOA);
+	assert(soa_rrs);
+	uint32_t minimum_ttl = knot_rrs_soa_minimum(soa_rrs);
+	if (knot_rr_ttl(nsec3_rr) != minimum_ttl) {
+		err_handler_handle_error(handler, node,
+		                         ZC_ERR_NSEC3_RDATA_TTL, NULL);
 	}
 
 	/* Result is a dname, it can't be larger */
 	const knot_node_t *apex = knot_zone_contents_apex(zone);
 	uint8_t *next_dname_str = NULL;
 	uint8_t next_dname_size = 0;
-	knot_rdata_nsec3_next_hashed(nsec3_rrset, 0, &next_dname_str,
-	                             &next_dname_size);
+	knot_rrs_nsec3_next_hashed(nsec3_rrs, 0, &next_dname_str,
+	                           &next_dname_size);
 	knot_dname_t *next_dname = knot_nsec3_hash_to_dname(next_dname_str,
 	                                                    next_dname_size,
 	                                                    apex->owner);
@@ -701,19 +672,18 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 					 ZC_ERR_NSEC3_RDATA_CHAIN, NULL);
 	}
 
-	knot_dname_free(&next_dname);
-	
+	knot_dname_free(&next_dname, NULL);
+
 	size_t arr_size;
 	uint16_t *array = NULL;
 	/* TODO only works for one NSEC3 RR. */
-	int ret = rdata_nsec_to_type_array(nsec3_rrset, 0, &array, &arr_size);
+	int ret = rdata_nsec_to_type_array(nsec3_rrs,
+	                                   KNOT_RRTYPE_NSEC3, 0,
+	                                   &array, &arr_size);
 	if (ret != KNOT_EOK) {
-		dbg_semcheck("semchecks: check_nsec3_node: Could not "
-		             "convert NSEC to type array. Reason: %s\n",
-		             knot_strerror(ret));
 		return ret;
 	}
-	
+
 	uint16_t type = 0;
 	for (int j = 0; j < arr_size; j++) {
 		/* test for each type's presence */
@@ -721,22 +691,17 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 		if (type == KNOT_RRTYPE_RRSIG) {
 			continue;
 		}
-		
-		if (knot_node_rrset(node, type) == NULL) {
+
+		if (!knot_node_rrtype_exists(node, type)) {
 			err_handler_handle_error(handler, node,
 			                         ZC_ERR_NSEC3_RDATA_BITMAP,
 			                         NULL);
 		}
 	}
-	
+
 	/* Check that the node only contains NSEC3 and RRSIG. */
-	const knot_rrset_t **rrsets = knot_node_rrsets_no_copy(nsec3_node);
-	if (rrsets == NULL) {
-		return KNOT_ENOMEM;
-	}
-	
 	for (int i = 0; i < knot_node_rrset_count(nsec3_node); i++) {
-		uint16_t type = knot_rrset_type(rrsets[i]);
+		uint16_t type = nsec3_node->rrs[i].type;
 		if (!(type == KNOT_RRTYPE_NSEC3 ||
 		    type == KNOT_RRTYPE_RRSIG)) {
 			err_handler_handle_error(handler, nsec3_node,
@@ -753,12 +718,12 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone,
 static int sem_check_node_mandatory(const knot_node_t *node,
                                     err_handler_t *handler, bool *fatal_error)
 {
-	const knot_rrset_t *cname_rrset =
-			knot_node_rrset(node, KNOT_RRTYPE_CNAME);
-	if (cname_rrset) {
+	const knot_rrs_t *cname_rrs = knot_node_rrs(node, KNOT_RRTYPE_CNAME);
+	if (cname_rrs) {
 		if (knot_node_rrset_count(node) != 1) {
 			/* With DNSSEC node can contain RRSIGs or NSEC */
-			if (!(knot_node_rrset(node, KNOT_RRTYPE_NSEC) || knot_node_rrset(node, KNOT_RRTYPE_RRSIG)) ||
+			if (!(knot_node_rrtype_exists(node, KNOT_RRTYPE_NSEC) ||
+			      knot_node_rrtype_exists(node, KNOT_RRTYPE_RRSIG)) ||
 			    knot_node_rrset_count(node) > 3) {
 				*fatal_error = true;
 				err_handler_handle_error(handler, node,
@@ -766,23 +731,22 @@ static int sem_check_node_mandatory(const knot_node_t *node,
 			}
 		}
 
-		if (knot_rrset_rr_count(cname_rrset) != 1) {
+		if (cname_rrs->rr_count != 1) {
 			*fatal_error = true;
 			err_handler_handle_error(handler, node,
 			                         ZC_ERR_CNAME_MULTIPLE, NULL);
 		}
 	}
 
-	const knot_rrset_t *dname_rrset =
-		knot_node_rrset(node, KNOT_RRTYPE_DNAME);
-	if (dname_rrset) {
-		if (cname_rrset) {
+	const knot_rrs_t *dname_rrs = knot_node_rrs(node, KNOT_RRTYPE_DNAME);
+	if (dname_rrs) {
+		if (cname_rrs) {
 			*fatal_error = true;
 			err_handler_handle_error(handler, node,
 			                         ZC_ERR_CNAME_EXTRA_RECORDS,
 			                         NULL);
 		}
-	
+
 		if (node->children != 0) {
 			*fatal_error = true;
 			err_handler_handle_error(handler, node,
@@ -790,14 +754,14 @@ static int sem_check_node_mandatory(const knot_node_t *node,
 			                         "Error triggered by parent node.");
 		}
 	}
-	
-	if (node->parent && knot_node_rrset(node->parent, KNOT_RRTYPE_DNAME)) {
+
+	if (node->parent && knot_node_rrtype_exists(node->parent, KNOT_RRTYPE_DNAME)) {
 		*fatal_error = true;
 		err_handler_handle_error(handler, node,
 		                         ZC_ERR_DNAME_CHILDREN,
 		                         "Error triggered by child node.");
 	}
-	
+
 	return KNOT_EOK;
 }
 
@@ -805,28 +769,24 @@ static int sem_check_node_optional(const knot_zone_contents_t *zone,
                                    const knot_node_t *node,
                                    err_handler_t *handler)
 {
-	if (knot_node_is_deleg_point(node) || knot_zone_contents_apex(zone) ==
-	                node) {
-		const knot_rrset_t *ns_rrset =
-				knot_node_rrset(node, KNOT_RRTYPE_NS);
-		if (ns_rrset == NULL) {
-			err_handler_handle_error(handler, node,
-			                         ZC_ERR_MISSING_NS_DEL_POINT,
-			                         NULL);
-			return KNOT_EOK;
-		}
-
-		/* TODO How about multiple RRs? */
-		knot_dname_t *ns_dname =
-			knot_dname_copy(knot_rdata_ns_name(ns_rrset,
-		                             0));
-		if (ns_dname == NULL) {
-			return KNOT_ENOMEM;
-		}
+	if (!(knot_node_is_deleg_point(node) || knot_zone_contents_apex(zone) ==
+	                node)) {
+		return KNOT_EOK;
+	}
+	const knot_rrs_t *ns_rrs = knot_node_rrs(node, KNOT_RRTYPE_NS);
+	if (ns_rrs == NULL) {
+		err_handler_handle_error(handler, node,
+		                         ZC_ERR_MISSING_NS_DEL_POINT,
+		                         NULL);
+		return KNOT_EOK;
+	}
 
+	for (int i = 0; i < ns_rrs->rr_count; ++i) {
+		const knot_dname_t *ns_dname =
+			knot_rrs_ns_name(ns_rrs, i);
 		const knot_node_t *glue_node =
-				knot_zone_contents_find_node(zone, ns_dname);
-		
+			knot_zone_contents_find_node(zone, ns_dname);
+
 		if (knot_dname_is_sub(ns_dname,
 			      knot_node_owner(knot_zone_contents_apex(zone)))) {
 			if (glue_node == NULL) {
@@ -836,37 +796,27 @@ static int sem_check_node_optional(const knot_zone_contents_t *zone,
 				knot_dname_to_wire(wildcard + 2,
 				                   knot_wire_next_label(ns_dname, NULL),
 				                   sizeof(wildcard));
-				const knot_node_t *wildcard_node = 
+				const knot_node_t *wildcard_node =
 					knot_zone_contents_find_node(zone,
 				                                     wildcard);
 				if (wildcard_node == NULL) {
 					err_handler_handle_error(handler, node,
 							 ZC_ERR_GLUE_NODE,
 							NULL );
-				} else {
-					/* Look for A or AAAA. */
-					if ((knot_node_rrset(wildcard_node,
-					    KNOT_RRTYPE_A) == NULL) &&
-					    (knot_node_rrset(wildcard_node,
-					    KNOT_RRTYPE_AAAA) == NULL)) {
-						err_handler_handle_error(handler, node,
-								 ZC_ERR_GLUE_RECORD,
-								 NULL);
-					}
-				}	
-			} else {
-				if ((knot_node_rrset(glue_node,
-					       KNOT_RRTYPE_A) == NULL) &&
-				    (knot_node_rrset(glue_node,
-					       KNOT_RRTYPE_AAAA) == NULL)) {
-					err_handler_handle_error(handler, node,
-							 ZC_ERR_GLUE_RECORD,
-							 NULL);
+					// Cannot continue
+					return KNOT_EOK;
 				}
+				glue_node = wildcard_node;
+			}
+			if (!knot_node_rrtype_exists(glue_node, KNOT_RRTYPE_A) &&
+			    !knot_node_rrtype_exists(glue_node, KNOT_RRTYPE_AAAA)) {
+				err_handler_handle_error(handler, node,
+				                         ZC_ERR_GLUE_RECORD,
+				                         NULL);
 			}
 		}
-		knot_dname_free(&ns_dname);
 	}
+
 	return KNOT_EOK;
 }
 
@@ -918,83 +868,71 @@ int sem_check_node_plain(const knot_zone_contents_t *zone,
  * \return Appropriate error code if error was found.
  */
 static int semantic_checks_dnssec(knot_zone_contents_t *zone,
-				  knot_node_t *node,
-				  knot_node_t **last_node,
-				  err_handler_t *handler,
-				  char nsec3)
+                                  knot_node_t *node,
+                                  knot_node_t **last_node,
+                                  err_handler_t *handler,
+                                  char nsec3)
 {
 	assert(handler);
 	assert(node);
-	char auth = !knot_node_is_non_auth(node);
-	char deleg = knot_node_is_deleg_point(node);
-	uint rrset_count = knot_node_rrset_count(node);
-	const knot_rrset_t **rrsets = knot_node_rrsets_no_copy(node);
-	const knot_rrset_t *dnskey_rrset =
-		knot_node_rrset(knot_zone_contents_apex(zone),
-				  KNOT_RRTYPE_DNSKEY);
+	bool auth = !knot_node_is_non_auth(node);
+	bool deleg = knot_node_is_deleg_point(node);
+	short rrset_count = knot_node_rrset_count(node);
+	knot_rrset_t dnskey_rrset = knot_node_rrset(zone->apex, KNOT_RRTYPE_DNSKEY);
 
-	int ret = 0;
+	int ret = KNOT_EOK;
 
 	for (int i = 0; i < rrset_count; i++) {
-		const knot_rrset_t *rrset = rrsets[i];
-		if (auth && !deleg && rrset->type != KNOT_RRTYPE_RRSIG &&
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
+		if (auth && !deleg && rrset.type != KNOT_RRTYPE_RRSIG &&
 		    (ret = check_rrsig_in_rrset(handler, node,
-		                                rrset, dnskey_rrset)) != 0) {
+		                                &rrset, &dnskey_rrset)) != 0) {
 			err_handler_handle_error(handler, node, ret, NULL);
 		}
 
 		if (!nsec3 && auth) {
 			/* check for NSEC record */
-			const knot_rrset_t *nsec_rrset =
-					knot_node_rrset(node,
-							  KNOT_RRTYPE_NSEC);
-
-			if (nsec_rrset == NULL) {
+			const knot_rrs_t *nsec_rrs =
+				knot_node_rrs(node, KNOT_RRTYPE_NSEC);
+			if (nsec_rrs == NULL) {
 				err_handler_handle_error(handler, node,
-							 ZC_ERR_NO_NSEC, NULL);
-			} else {
-				/* check NSEC/NSEC3 bitmap */
-				size_t count;
-				uint16_t *array = NULL;
-				
-				int ret = rdata_nsec_to_type_array(nsec_rrset,
-				                                   0,
-				                                   &array,
-				                                   &count);
-				if (ret != KNOT_EOK) {
-					dbg_semcheck("semchecks: "
-					             "Could not create type "
-					             "array. Reason: %s.\n",
-					             knot_strerror(ret));
-					return ret;
-				}
-
-				uint16_t type = 0;
-				for (int j = 0; j < count; j++) {
-					/* test for each type's presence */
-					type = array[j];
-					if (type == KNOT_RRTYPE_RRSIG) {
-						continue;
-					}
-					if (knot_node_rrset(node,
-							      type) == NULL) {
-					err_handler_handle_error(
-						handler,
-						node,
-						ZC_ERR_NSEC_RDATA_BITMAP, NULL);
-					}
-				}
-				free(array);
+				                         ZC_ERR_NO_NSEC, NULL);
+				return KNOT_EOK;
 			}
 
-			/* Test that only one record is in the
-				 * NSEC RRSet */
+			/* check NSEC/NSEC3 bitmap */
+			size_t count;
+			uint16_t *array = NULL;
+			int ret = rdata_nsec_to_type_array(nsec_rrs,
+			                                   KNOT_RRTYPE_NSEC,
+			                                   0,
+			                                   &array,
+			                                   &count);
+			if (ret != KNOT_EOK) {
+				return ret;
+			}
 
-			if (knot_rrset_rr_count(nsec_rrset) != 1) {
+			uint16_t type = 0;
+			for (int j = 0; j < count; j++) {
+				/* test for each type's presence */
+				type = array[j];
+				if (type == KNOT_RRTYPE_RRSIG) {
+					continue;
+				}
+				if (!knot_node_rrtype_exists(node, type)) {
+					err_handler_handle_error(handler,
+					                         node,
+					                         ZC_ERR_NSEC_RDATA_BITMAP,
+					                         NULL);
+				}
+			}
+			free(array);
+			/* Test that only one record is in the NSEC RRSet */
+			if (nsec_rrs->rr_count != 1) {
 				err_handler_handle_error(handler,
-						 node,
-						 ZC_ERR_NSEC_RDATA_MULTIPLE,
-				                NULL);
+				                         node,
+				                         ZC_ERR_NSEC_RDATA_MULTIPLE,
+				                         NULL);
 			}
 
 			/*
@@ -1004,31 +942,26 @@ static int semantic_checks_dnssec(knot_zone_contents_t *zone,
 			 * so checking should only be matter of testing
 			 * the next link in each node.
 			 */
+			const knot_dname_t *next_domain =
+				knot_rrs_nsec_next(nsec_rrs);
+			// Convert name to lowercase for trie lookup
+			knot_dname_t *lowercase = knot_dname_copy(next_domain, NULL);
+			if (lowercase == NULL) {
+				return KNOT_ENOMEM;
+			}
+			knot_dname_to_lower(lowercase);
 
-			if (nsec_rrset != NULL) {
-				const knot_dname_t *next_domain =
-					knot_rdata_nsec_next(nsec_rrset);
-				assert(next_domain);
-				// Convert name to lowercase for trie lookup
-				knot_dname_t *lowercase = knot_dname_copy(next_domain);
-				if (lowercase == NULL) {
-					return KNOT_ENOMEM;
-				}
-				knot_dname_to_lower(lowercase);
-
-				if (knot_zone_contents_find_node(zone, lowercase) == NULL) {
-					err_handler_handle_error(handler, node,
-						ZC_ERR_NSEC_RDATA_CHAIN, NULL);
-				}
+			if (knot_zone_contents_find_node(zone, lowercase) == NULL) {
+				err_handler_handle_error(handler, node,
+				                         ZC_ERR_NSEC_RDATA_CHAIN,
+				                         NULL);
+			}
 
-				if (knot_dname_cmp(lowercase,
-				    knot_node_owner(knot_zone_contents_apex(zone)))
-					== 0) {
-					/* saving the last node */
-					*last_node = node;
-				}
-				knot_dname_free(&lowercase);
+			if (knot_dname_is_equal(lowercase, zone->apex->owner)) {
+				/* saving the last node */
+				*last_node = node;
 			}
+			knot_dname_free(&lowercase, NULL);
 		} else if (nsec3 && (auth || deleg)) { /* nsec3 */
 			int ret = check_nsec3_node_in_zone(zone, node,
 			                                   handler);
@@ -1064,7 +997,7 @@ static int do_checks_in_tree(knot_node_t *node, void *data)
 	knot_node_t **last_node = (knot_node_t **)args->arg5;
 
 	err_handler_t *handler = (err_handler_t *)args->arg6;
-	
+
 	char do_checks = *((char *)(args->arg3));
 
 	if (do_checks) {
@@ -1113,10 +1046,10 @@ int zone_do_sem_checks(knot_zone_contents_t *zone, int do_checks,
 	if (fatal_error) {
 		return KNOT_ERROR;
 	}
-	
+
 	log_cyclic_errors_in_zone(handler, zone, last_node, first_nsec3_node,
 	                          last_nsec3_node, do_checks);
-	
+
 	return KNOT_EOK;
 }
 
@@ -1127,15 +1060,14 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
                                const knot_node_t *last_nsec3_node,
                                char do_checks)
 {
-	if (do_checks == 3) {
+	if (do_checks == SEM_CHECK_NSEC3) {
 		/* Each NSEC3 node should only contain one RRSET. */
 		if (last_nsec3_node == NULL || first_nsec3_node == NULL) {
 			return;
 		}
-		const knot_rrset_t *nsec3_rrset =
-			knot_node_rrset(last_nsec3_node,
-		                              KNOT_RRTYPE_NSEC3);
-		if (nsec3_rrset == NULL) {
+		const knot_rrs_t *nsec3_rrs =
+			knot_node_rrs(last_nsec3_node, KNOT_RRTYPE_NSEC3);
+		if (nsec3_rrs == NULL) {
 			err_handler_handle_error(handler, last_nsec3_node,
 						 ZC_ERR_NSEC3_RDATA_CHAIN, NULL);
 			return;
@@ -1145,8 +1077,8 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
 		const knot_node_t *apex = knot_zone_contents_apex(zone);
 		uint8_t *next_dname_str = NULL;
 		uint8_t next_dname_size = 0;
-		knot_rdata_nsec3_next_hashed(nsec3_rrset, 0, &next_dname_str,
-		                             &next_dname_size);
+		knot_rrs_nsec3_next_hashed(nsec3_rrs, 0, &next_dname_str,
+		                           &next_dname_size);
 		knot_dname_t *next_dname = knot_nsec3_hash_to_dname(next_dname_str,
 		                                                    next_dname_size,
 		                                                    apex->owner);
@@ -1171,7 +1103,7 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
 		}
 
 		/* Directly discard. */
-		knot_dname_free(&next_dname);
+		knot_dname_free(&next_dname, NULL);
 
 	} else if (do_checks == 2 ) {
 		if (last_node == NULL) {
@@ -1179,18 +1111,16 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
 				ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC, NULL);
 				return;
 		} else {
-			const knot_rrset_t *nsec_rrset =
-				knot_node_rrset(last_node,
-						  KNOT_RRTYPE_NSEC);
+			const knot_rrs_t *nsec_rrs =
+				knot_node_rrs(last_node, KNOT_RRTYPE_NSEC);
 
-			if (nsec_rrset == NULL) {
+			if (nsec_rrs == NULL) {
 				err_handler_handle_error(handler, last_node,
 					 ZC_ERR_NSEC_RDATA_CHAIN_NOT_CYCLIC, NULL);
 				return;
 			}
 
-			const knot_dname_t *next_dname =
-				knot_rdata_nsec_next(nsec_rrset);
+			const knot_dname_t *next_dname = knot_rrs_nsec_next(nsec_rrs);
 			assert(next_dname);
 
 			const knot_dname_t *apex_dname =
diff --git a/src/knot/zone/zone-contents.c b/src/knot/zone/zone-contents.c
index 9bcdfea775b32d7780bb88eaea22614d2df02dad..ff9155fe2958eb6b5e4d750b542285b05384e034 100644
--- a/src/knot/zone/zone-contents.c
+++ b/src/knot/zone/zone-contents.c
@@ -152,33 +152,34 @@ static int knot_zone_contents_nsec3_name(const knot_zone_contents_t *zone,
 }
 
 /*! \brief Link pointers to additional nodes for this RRSet. */
-static int discover_additionals(knot_rrset_t *rr, knot_zone_contents_t *zone)
+static int discover_additionals(struct rr_data *rr_data,
+                                knot_zone_contents_t *zone)
 {
 	const knot_node_t *node = NULL, *encloser = NULL, *prev = NULL;
 	const knot_dname_t *dname = NULL;
-
-	/* Free old additional nodes. */
-	if (rr->additional != NULL) {
-		free(rr->additional);
-	}
+	const knot_rrs_t *rrs = &rr_data->rrs;
 
 	/* Create new additional nodes. */
-	uint16_t rdcount = knot_rrset_rr_count(rr);
-	rr->additional = malloc(rdcount * sizeof(knot_node_t*));
-	if (rr->additional == NULL) {
+	uint16_t rdcount = rrs->rr_count;
+	if (rr_data->additional) {
+		free(rr_data->additional);
+	}
+	rr_data->additional = malloc(rdcount * sizeof(knot_node_t *));
+	if (rr_data->additional == NULL) {
+		ERR_ALLOC_FAILED;
 		return KNOT_ENOMEM;
 	}
 
 	for (uint16_t i = 0; i < rdcount; i++) {
 
 		/* Try to find node for the dname in the RDATA. */
-		dname = knot_rdata_name(rr, i);
+		dname = knot_rrs_name(rrs, i, rr_data->type);
 		knot_zone_contents_find_dname(zone, dname, &node, &encloser, &prev);
 		if (node == NULL && encloser && encloser->wildcard_child) {
 			node = encloser->wildcard_child;
 		}
 
-		rr->additional[i] = (knot_node_t *)node;
+		rr_data->additional[i] = (knot_node_t *)node;
 	}
 
 	return KNOT_EOK;
@@ -213,7 +214,7 @@ static int adjust_pointers(knot_node_t **tnode, void *data)
 		|| knot_node_is_non_auth(knot_node_parent(node)))
 	) {
 		knot_node_set_non_auth(node);
-	} else if (knot_node_rrset(node, KNOT_RRTYPE_NS) != NULL
+	} else if (knot_node_rrtype_exists(node, KNOT_RRTYPE_NS)
 		   && node != args->zone->apex) {
 		knot_node_set_deleg_point(node);
 	} else {
@@ -254,7 +255,7 @@ static int adjust_nsec3_pointers(knot_node_t **tnode, void *data)
 		ret = KNOT_EOK;
 	}
 
-	knot_dname_free(&nsec3_name);
+	knot_dname_free(&nsec3_name, NULL);
 	return ret;
 }
 
@@ -329,12 +330,12 @@ static int adjust_additional(knot_node_t **tnode, void *data)
 	int ret = KNOT_EOK;
 	knot_zone_adjust_arg_t *args = (knot_zone_adjust_arg_t *)data;
 	knot_node_t *node = *tnode;
-	knot_rrset_t **rrset = node->rrset_tree;
 
 	/* Lookup additional records for specific nodes. */
 	for(uint16_t i = 0; i < node->rrset_count; ++i) {
-		if (rrset_additional_needed(rrset[i]->type)) {
-			ret = discover_additionals(rrset[i], args->zone);
+		struct rr_data *rr_data = &node->rrs[i];
+		if (knot_rrtype_additional_needed(rr_data->type)) {
+			ret = discover_additionals(rr_data, args->zone);
 			if (ret != KNOT_EOK) {
 				break;
 			}
@@ -385,27 +386,27 @@ static int knot_zone_contents_find_in_tree(knot_zone_tree_t *tree,
 
 /*----------------------------------------------------------------------------*/
 
-static int knot_zc_nsec3_parameters_match(const knot_rrset_t *rrset,
+static int knot_zc_nsec3_parameters_match(const knot_rrs_t *rrs,
                                           const knot_nsec3_params_t *params,
                                           size_t rdata_pos)
 {
-	assert(rrset != NULL && params != NULL);
+	assert(rrs != NULL && params != NULL);
 
 	dbg_zone_detail("RDATA algo: %u, iterations: %u, salt length: %u, salt:"
 			" %.*s\n",
-			knot_rdata_nsec3_algorithm(rrset, rdata_pos),
-			knot_rdata_nsec3_iterations(rrset, rdata_pos),
-			knot_rdata_nsec3_salt_length(rrset, rdata_pos),
-			knot_rdata_nsec3_salt_length(rrset, rdata_pos),
-			knot_rdata_nsec3_salt(rrset, rdata_pos));
+			knot_rrs_nsec3_algorithm(rrs, rdata_pos),
+			knot_rrs_nsec3_iterations(rrs, rdata_pos),
+			knot_rrs_nsec3_salt_length(rrs, rdata_pos),
+			knot_rrs_nsec3_salt_length(rrs, rdata_pos),
+			knot_rrs_nsec3_salt(rrs, rdata_pos));
 	dbg_zone_detail("NSEC3PARAM algo: %u, iterations: %u, salt length: %u, "
 			"salt: %.*s\n",  params->algorithm, params->iterations,
 			params->salt_length, params->salt_length, params->salt);
 
-	return (knot_rdata_nsec3_algorithm(rrset, rdata_pos) == params->algorithm
-		&& knot_rdata_nsec3_iterations(rrset, rdata_pos) == params->iterations
-		&& knot_rdata_nsec3_salt_length(rrset, rdata_pos) == params->salt_length
-		&& strncmp((const char *)knot_rdata_nsec3_salt(rrset, rdata_pos),
+	return (knot_rrs_nsec3_algorithm(rrs, rdata_pos) == params->algorithm
+		&& knot_rrs_nsec3_iterations(rrs, rdata_pos) == params->iterations
+		&& knot_rrs_nsec3_salt_length(rrs, rdata_pos) == params->salt_length
+		&& strncmp((const char *)knot_rrs_nsec3_salt(rrs, rdata_pos),
 			   (const char *)params->salt, params->salt_length)
 		   == 0);
 }
@@ -484,22 +485,26 @@ void knot_zone_contents_set_gen_new(knot_zone_contents_t *contents)
 	contents->flags |= KNOT_ZONE_FLAGS_GEN_NEW;
 }
 
-/*----------------------------------------------------------------------------*/
-
-uint16_t knot_zone_contents_class(const knot_zone_contents_t *contents)
+static knot_node_t *knot_zone_contents_get_node(const knot_zone_contents_t *zone,
+				    const knot_dname_t *name)
 {
-	if (contents == NULL || contents->apex == NULL
-	    || knot_node_rrset(contents->apex, KNOT_RRTYPE_SOA) == NULL) {
-		return KNOT_EINVAL;
+	if (zone == NULL || name == NULL) {
+		return NULL;
+	}
+
+	knot_node_t *n;
+	int ret = knot_zone_tree_get(zone->nodes, name, &n);
+	if (ret != KNOT_EOK) {
+		dbg_zone("Failed to find name in the zone tree.\n");
+		return NULL;
 	}
 
-	return knot_rrset_class(knot_node_rrset(contents->apex,
-						KNOT_RRTYPE_SOA));
+	return n;
 }
 
 /*----------------------------------------------------------------------------*/
 
-int knot_zone_contents_add_node(knot_zone_contents_t *zone,
+static int knot_zone_contents_add_node(knot_zone_contents_t *zone,
                                   knot_node_t *node, int create_parents,
                                   uint8_t flags)
 {
@@ -561,7 +566,6 @@ dbg_zone_exec_detail(
 
 			/* Insert node to a tree. */
 			dbg_zone_detail("Inserting new node to zone tree.\n");
-			assert(knot_zone_contents_find_node(zone, parent) == NULL);
 			ret = knot_zone_tree_insert(zone->nodes, next_node);
 			if (ret != KNOT_EOK) {
 				knot_node_free(&next_node);
@@ -594,42 +598,68 @@ dbg_zone_exec_detail(
 
 /*----------------------------------------------------------------------------*/
 
-int knot_zone_contents_create_node(knot_zone_contents_t *contents,
-                                   const knot_rrset_t *rr,
-                                   knot_node_t **node)
+static int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *zone,
+                                             knot_node_t *node)
 {
-	if (contents == NULL || rr == NULL || node == NULL) {
+	if (zone == NULL || node == NULL) {
 		return KNOT_EINVAL;
 	}
 
-	*node = knot_node_new(rr->owner, NULL, 0);
-	if (*node == NULL) {
-		return KNOT_ENOMEM;
+	int ret = 0;
+	if ((ret = knot_zone_contents_check_node(zone, node)) != 0) {
+		dbg_zone("Failed node check: %s\n", knot_strerror(ret));
+		return ret;
 	}
 
-	/* Add to the proper tree. */
-	int ret = KNOT_EOK;
-	if (knot_rrset_is_nsec3rel(rr)) {
-		ret = knot_zone_contents_add_nsec3_node(contents, *node, 1, 0);
-	} else {
-		ret = knot_zone_contents_add_node(contents, *node, 1, 0);
+	/* Create NSEC3 tree if not exists. */
+	if (zone->nsec3_nodes == NULL) {
+		zone->nsec3_nodes = knot_zone_tree_create();
+		if (zone->nsec3_nodes == NULL) {
+			return KNOT_ENOMEM;
+		}
 	}
 
+	// how to know if this is successfull??
+	ret = knot_zone_tree_insert(zone->nsec3_nodes, node);
 	if (ret != KNOT_EOK) {
-		dbg_xfrin("Failed to add new node to zone contents.\n");
-		knot_node_free(node);
+		dbg_zone("Failed to insert node into NSEC3 tree: %s.\n",
+			 knot_strerror(ret));
 		return ret;
 	}
 
-	return ret;
+	// no parents to be created, the only parent is the zone apex
+	// set the apex as the parent of the node
+	knot_node_set_parent(node, zone->apex);
+
+	// cannot be wildcard child, so nothing to be done
+
+	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
+static knot_node_t *knot_zone_contents_get_nsec3_node(
+	const knot_zone_contents_t *zone, const knot_dname_t *name)
+{
+	if (zone == NULL || name == NULL) {
+		return NULL;
+	}
+
+	knot_node_t *n;
+	int ret = knot_zone_tree_get(zone->nsec3_nodes, name, &n);
+
+	if (ret != KNOT_EOK) {
+		dbg_zone("Failed to find NSEC3 name in the zone tree."
+				  "\n");
+		return NULL;
+	}
+
+	return n;
+}
 
-static int insert_rr(knot_zone_contents_t *z, knot_rrset_t *rr, knot_node_t **n,
+static int insert_rr(knot_zone_contents_t *z,
+                     const knot_rrset_t *rr, knot_node_t **n,
                      bool nsec3, bool *ttl_err)
 {
-	if (z == NULL || rr == NULL || n == NULL || ttl_err == NULL) {
+	if (z == NULL || knot_rrset_empty(rr) || n == NULL) {
 		return KNOT_EINVAL;
 	}
 
@@ -649,11 +679,10 @@ static int insert_rr(knot_zone_contents_t *z, knot_rrset_t *rr, knot_node_t **n,
 			if (*n == NULL) {
 				return KNOT_ENOMEM;
 			}
-			ret = nsec3 ? knot_zone_contents_add_nsec3_node(z, *n, true, 0) :
+			ret = nsec3 ? knot_zone_contents_add_nsec3_node(z, *n) :
 			              knot_zone_contents_add_node(z, *n, true, 0);
 			if (ret != KNOT_EOK) {
 				knot_node_free(n);
-				return ret;
 			}
 		}
 	}
@@ -661,110 +690,114 @@ static int insert_rr(knot_zone_contents_t *z, knot_rrset_t *rr, knot_node_t **n,
 	return knot_node_add_rrset(*n, rr, ttl_err);
 }
 
-static bool to_nsec3_tree(const knot_rrset_t *rr)
-{
-	return knot_rrset_is_nsec3rel(rr);
-}
-
-int knot_zone_contents_add_rr(knot_zone_contents_t *z, knot_rrset_t *rr,
-                              knot_node_t **n, bool *ttl_err)
+static int recreate_normal_tree(const knot_zone_contents_t *z,
+                                knot_zone_contents_t *out)
 {
-	return insert_rr(z, rr, n, to_nsec3_tree(rr), ttl_err);
-}
-
-int knot_zone_contents_add_rrset(knot_zone_contents_t *zone,
-                                 knot_rrset_t *rrset, knot_node_t **node,
-                                 knot_rrset_dupl_handling_t dupl)
-{
-	if (zone == NULL || rrset == NULL || zone->apex == NULL
-	    || zone->apex->owner == NULL || node == NULL) {
-		return KNOT_EINVAL;
+	out->nodes = hattrie_dup(z->nodes, NULL);
+	if (out->nodes == NULL) {
+		return KNOT_ENOMEM;
 	}
 
-dbg_zone_exec_detail(
-	char *name = knot_dname_to_str(knot_rrset_owner(rrset));
-	dbg_zone_detail("Adding RRSet to zone contents: %s, type %d\n",
-			name, knot_rrset_type(rrset));
-	free(name);
-);
-
-	// check if the RRSet belongs to the zone
-	if (!knot_dname_is_equal(rrset->owner, zone->apex->owner)
-	    && !knot_dname_is_sub(rrset->owner, zone->apex->owner)) {
-		return KNOT_EOUTOFZONE;
+	// Insert APEX first.
+	knot_node_t *apex_cpy;
+	int ret = knot_node_shallow_copy(z->apex, &apex_cpy);
+	if (ret != KNOT_EOK) {
+		return ret;
 	}
 
-	if ((*node) == NULL
-	    && (*node = knot_zone_contents_get_node(zone,
-	                                            rrset->owner)) == NULL) {
-		return KNOT_ENONODE;
+	// Normal additions need apex ... so we need to insert directly.
+	ret = knot_zone_tree_insert(out->nodes, apex_cpy);
+	if (ret != KNOT_EOK) {
+		knot_node_free(&apex_cpy);
+		return ret;
 	}
 
-	assert(*node != NULL);
-
-	// add all domain names from the RRSet to domain name table
-	int rc;
+	out->apex = apex_cpy;
 
-	/*! \todo REMOVE RRSET */
-	if (dupl == KNOT_RRSET_DUPL_MERGE) {
-		rc = knot_node_add_rrset(*node, rrset, NULL);
-	} else {
-		rc = knot_node_add_rrset_no_merge(*node, rrset);
+	hattrie_iter_t *itt = hattrie_iter_begin(z->nodes, true);
+	if (itt == NULL) {
+		return KNOT_ENOMEM;
 	}
-
-	if (rc < 0) {
-		dbg_zone("Failed to add RRSet to node.\n");
-		return rc;
+	while (!hattrie_iter_finished(itt)) {
+		const knot_node_t *to_cpy = (knot_node_t *)*hattrie_iter_val(itt);
+		if (to_cpy == z->apex) {
+			// Inserted already.
+			hattrie_iter_next(itt);
+			continue;
+		}
+		knot_node_t *to_add;
+		int ret = knot_node_shallow_copy(to_cpy, &to_add);
+		if (ret != KNOT_EOK) {
+			hattrie_iter_free(itt);
+			return ret;
+		}
+		ret = knot_zone_contents_add_node(out, to_add, true, 0);
+		if (ret != KNOT_EOK) {
+			knot_node_free(&to_add);
+			hattrie_iter_free(itt);
+			return ret;
+		}
+		hattrie_iter_next(itt);
 	}
 
-	int ret = rc;
+	hattrie_iter_free(itt);
+	hattrie_build_index(out->nodes);
 
-	dbg_zone_detail("RRSet OK (%d).\n", ret);
-	return ret;
+	return KNOT_EOK;
 }
 
-/*----------------------------------------------------------------------------*/
-
-int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *zone,
-                                        knot_node_t *node, int create_parents,
-                                        uint8_t flags)
+static int recreate_nsec3_tree(const knot_zone_contents_t *z,
+                               knot_zone_contents_t *out)
 {
-	UNUSED(create_parents);
-	UNUSED(flags);
-
-	if (zone == NULL || node == NULL) {
-		return KNOT_EINVAL;
+	out->nsec3_nodes = hattrie_dup(z->nsec3_nodes, NULL);
+	if (out->nsec3_nodes == NULL) {
+		return KNOT_ENOMEM;
 	}
 
-	int ret = 0;
-	if ((ret = knot_zone_contents_check_node(zone, node)) != 0) {
-		dbg_zone("Failed node check: %s\n", knot_strerror(ret));
-		return ret;
+	hattrie_iter_t *itt = hattrie_iter_begin(z->nsec3_nodes, false);
+	if (itt == NULL) {
+		return KNOT_ENOMEM;
 	}
-
-	/* Create NSEC3 tree if not exists. */
-	if (zone->nsec3_nodes == NULL) {
-		zone->nsec3_nodes = knot_zone_tree_create();
-		if (zone->nsec3_nodes == NULL) {
-			return KNOT_ENOMEM;
+	while (!hattrie_iter_finished(itt)) {
+		const knot_node_t *to_cpy = (knot_node_t *)*hattrie_iter_val(itt);
+		knot_node_t *to_add;
+		int ret = knot_node_shallow_copy(to_cpy, &to_add);
+		if (ret != KNOT_EOK) {
+			hattrie_iter_free(itt);
+			return ret;
+		}
+		ret = knot_zone_contents_add_nsec3_node(out, to_add);
+		if (ret != KNOT_EOK) {
+			hattrie_iter_free(itt);
+			knot_node_free(&to_add);
+			return ret;
 		}
+		hattrie_iter_next(itt);
 	}
 
-	// how to know if this is successfull??
-	ret = knot_zone_tree_insert(zone->nsec3_nodes, node);
-	if (ret != KNOT_EOK) {
-		dbg_zone("Failed to insert node into NSEC3 tree: %s.\n",
-			 knot_strerror(ret));
-		return ret;
-	}
+	hattrie_iter_free(itt);
+	hattrie_build_index(out->nsec3_nodes);
 
-	// no parents to be created, the only parent is the zone apex
-	// set the apex as the parent of the node
-	knot_node_set_parent(node, zone->apex);
+	return KNOT_EOK;
+}
 
-	// cannot be wildcard child, so nothing to be done
+static bool rrset_is_nsec3rel(const knot_rrset_t *rr)
+{
+	if (rr == NULL) {
+		return false;
+	}
 
-	return KNOT_EOK;
+	/* Is NSEC3 or non-empty RRSIG covering NSEC3. */
+	return ((rr->type == KNOT_RRTYPE_NSEC3)
+	        || (rr->type == KNOT_RRTYPE_RRSIG
+	            && knot_rrs_rrsig_type_covered(&rr->rrs, 0)
+	            == KNOT_RRTYPE_NSEC3));
+}
+
+int knot_zone_contents_add_rr(knot_zone_contents_t *z,
+                              const knot_rrset_t *rr, knot_node_t **n, bool *ttl_err)
+{
+	return insert_rr(z, rr, n, rrset_is_nsec3rel(rr), ttl_err);
 }
 
 /*----------------------------------------------------------------------------*/
@@ -814,46 +847,6 @@ int knot_zone_contents_remove_nsec3_node(knot_zone_contents_t *contents,
 
 /*----------------------------------------------------------------------------*/
 
-knot_node_t *knot_zone_contents_get_node(const knot_zone_contents_t *zone,
-				    const knot_dname_t *name)
-{
-	if (zone == NULL || name == NULL) {
-		return NULL;
-	}
-
-	knot_node_t *n;
-	int ret = knot_zone_tree_get(zone->nodes, name, &n);
-	if (ret != KNOT_EOK) {
-		dbg_zone("Failed to find name in the zone tree.\n");
-		return NULL;
-	}
-
-	return n;
-}
-
-/*----------------------------------------------------------------------------*/
-
-knot_node_t *knot_zone_contents_get_nsec3_node(
-	const knot_zone_contents_t *zone, const knot_dname_t *name)
-{
-	if (zone == NULL || name == NULL) {
-		return NULL;
-	}
-
-	knot_node_t *n;
-	int ret = knot_zone_tree_get(zone->nsec3_nodes, name, &n);
-
-	if (ret != KNOT_EOK) {
-		dbg_zone("Failed to find NSEC3 name in the zone tree."
-				  "\n");
-		return NULL;
-	}
-
-	return n;
-}
-
-/*----------------------------------------------------------------------------*/
-
 const knot_node_t *knot_zone_contents_find_node(
 	const knot_zone_contents_t *zone,const knot_dname_t *name)
 {
@@ -980,34 +973,6 @@ const knot_node_t *knot_zone_contents_find_previous(
 }
 
 /*----------------------------------------------------------------------------*/
-
-knot_node_t *knot_zone_contents_get_previous_nsec3(
-	const knot_zone_contents_t *zone, const knot_dname_t *name)
-{
-	if (zone == NULL || name == NULL) {
-		return NULL;
-	}
-
-	knot_node_t *found = NULL, *prev = NULL;
-
-	int exact_match = knot_zone_contents_find_in_tree(zone->nsec3_nodes,
-							   name, &found, &prev);
-	assert(exact_match >= 0);
-	assert(prev != NULL);
-
-	return prev;
-}
-
-/*----------------------------------------------------------------------------*/
-
-const knot_node_t *knot_zone_contents_find_previous_nsec3(
-	const knot_zone_contents_t *zone, const knot_dname_t *name)
-{
-	return knot_zone_contents_get_previous_nsec3(zone, name);
-}
-
-/*----------------------------------------------------------------------------*/
-
 const knot_node_t *knot_zone_contents_find_nsec3_node(
 	const knot_zone_contents_t *zone, const knot_dname_t *name)
 {
@@ -1034,6 +999,7 @@ int knot_zone_contents_find_nsec3_for_name(const knot_zone_contents_t *zone,
 
 	knot_dname_t *nsec3_name = NULL;
 	int ret = knot_zone_contents_nsec3_name(zone, name, &nsec3_name);
+
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
@@ -1051,7 +1017,7 @@ dbg_zone_exec_verb(
 		zone->nsec3_nodes, nsec3_name, &found, &prev);
 	assert(exact_match >= 0);
 
-	knot_dname_free(&nsec3_name);
+	knot_dname_free(&nsec3_name, NULL);
 
 dbg_zone_exec_detail(
 	if (found) {
@@ -1073,13 +1039,6 @@ dbg_zone_exec_detail(
 );
 	*nsec3_node = found;
 
-	// This check cannot be used now, the function returns proper return
-	// value if the node was not found
-//	if (*nsec3_node == NULL) {
-//		// there is no NSEC3 node even if there should be
-//		return KNOT_ENSEC3CHAIN;
-//	}
-
 	if (prev == NULL) {
 		// either the returned node is the root of the tree, or it is
 		// the leftmost node in the tree; in both cases node was found
@@ -1097,20 +1056,20 @@ dbg_zone_exec_detail(
 	 * from the right chain. Check iterations, hash algorithm and salt
 	 * values and compare them to the ones from NSEC3PARAM.
 	 */
-	const knot_rrset_t *nsec3_rrset = knot_node_rrset(*nsec3_previous,
-							  KNOT_RRTYPE_NSEC3);
-	assert(nsec3_rrset);
+	const knot_rrs_t *nsec3_rrs =
+		knot_node_rrs(*nsec3_previous, KNOT_RRTYPE_NSEC3);
+	assert(nsec3_rrs);
 	const knot_node_t *original_prev = *nsec3_previous;
 
 	int match = 0;
 
-	while (nsec3_rrset && !match) {
+	while (nsec3_rrs && !match) {
 		for (uint16_t i = 0;
-		     i < knot_rrset_rr_count(nsec3_rrset) && !match;
+		     i < nsec3_rrs->rr_count && !match;
 		     i++) {
-			if (knot_zc_nsec3_parameters_match(nsec3_rrset,
-							    &zone->nsec3_params,
-							    i)) {
+			if (knot_zc_nsec3_parameters_match(nsec3_rrs,
+			                                   &zone->nsec3_params,
+			                                   i)) {
 				/* Matching NSEC3PARAM match at position nr.: i. */
 				match = 1;
 			}
@@ -1122,8 +1081,7 @@ dbg_zone_exec_detail(
 
 		/* This RRSET was not a match, try the one from previous node. */
 		*nsec3_previous = knot_node_previous(*nsec3_previous);
-		nsec3_rrset = knot_node_rrset(*nsec3_previous,
-					      KNOT_RRTYPE_NSEC3);
+		nsec3_rrs = knot_node_rrs(*nsec3_previous, KNOT_RRTYPE_NSEC3);
 		dbg_zone_exec_detail(
 		char *name = (*nsec3_previous)
 				? knot_dname_to_str(
@@ -1135,7 +1093,7 @@ dbg_zone_exec_detail(
 			free(name);
 		}
 );
-		if (*nsec3_previous == original_prev || nsec3_rrset == NULL) {
+		if (*nsec3_previous == original_prev || nsec3_rrs == NULL) {
 			// cycle
 			*nsec3_previous = NULL;
 			break;
@@ -1161,17 +1119,6 @@ const knot_node_t *knot_zone_contents_apex(
 
 /*----------------------------------------------------------------------------*/
 
-knot_node_t *knot_zone_contents_get_apex(const knot_zone_contents_t *zone)
-{
-	if (zone == NULL) {
-		return NULL;
-	}
-
-	return zone->apex;
-}
-
-/*----------------------------------------------------------------------------*/
-
 static int knot_zone_contents_adjust_nodes(knot_zone_tree_t *nodes,
                                            knot_zone_adjust_arg_t *adjust_arg,
                                            knot_zone_tree_apply_cb_t callback)
@@ -1321,11 +1268,9 @@ int knot_zone_contents_load_nsec3param(knot_zone_contents_t *zone)
 		return KNOT_EINVAL;
 	}
 
-	const knot_rrset_t *rrset = knot_node_rrset(zone->apex,
-						    KNOT_RRTYPE_NSEC3PARAM);
-
-	if (rrset != NULL) {
-		int r = knot_nsec3_params_from_wire(&zone->nsec3_params, rrset);
+	const knot_rrs_t *rrs = knot_node_rrs(zone->apex, KNOT_RRTYPE_NSEC3PARAM);
+	if (rrs!= NULL) {
+		int r = knot_nsec3_params_from_wire(&zone->nsec3_params, rrs);
 		if (r != KNOT_EOK) {
 			dbg_zone("Failed to load NSEC3PARAM (%s).\n",
 			         knot_strerror(r));
@@ -1403,41 +1348,36 @@ int knot_zone_contents_shallow_copy(const knot_zone_contents_t *from,
 		return KNOT_EINVAL;
 	}
 
-	int ret = KNOT_EOK;
-
-	knot_zone_contents_t *contents = (knot_zone_contents_t *)calloc(
-					     1, sizeof(knot_zone_contents_t));
+	knot_zone_contents_t *contents = calloc(1, sizeof(knot_zone_contents_t));
 	if (contents == NULL) {
 		ERR_ALLOC_FAILED;
 		return KNOT_ENOMEM;
 	}
 
-	//contents->apex = from->apex;
-
-	contents->node_count = from->node_count;
 	contents->flags = from->flags;
-	// set the 'new' flag
 	knot_zone_contents_set_gen_new(contents);
 
-	if ((ret = knot_zone_tree_deep_copy(from->nodes,
-					    &contents->nodes)) != KNOT_EOK
-	    || (ret = knot_zone_tree_deep_copy(from->nsec3_nodes,
-					  &contents->nsec3_nodes)) != KNOT_EOK) {
-		goto cleanup;
+	int ret = recreate_normal_tree(from, contents);
+	if (ret != KNOT_EOK) {
+		knot_zone_tree_free(&contents->nodes);
+		free(contents);
+		return ret;
 	}
 
-	contents->apex = knot_node_get_new_node(from->apex);
-
-	dbg_zone("knot_zone_contents_shallow_copy: finished OK\n");
+	if (from->nsec3_nodes) {
+		ret = recreate_nsec3_tree(from, contents);
+		if (ret != KNOT_EOK) {
+			knot_zone_tree_free(&contents->nodes);
+			knot_zone_tree_free(&contents->nsec3_nodes);
+			free(contents);
+			return ret;
+		}
+	} else {
+		contents->nsec3_nodes = NULL;
+	}
 
 	*to = contents;
 	return KNOT_EOK;
-
-cleanup:
-	knot_zone_tree_free(&contents->nodes);
-	knot_zone_tree_free(&contents->nsec3_nodes);
-	free(contents);
-	return ret;
 }
 
 /*----------------------------------------------------------------------------*/
@@ -1490,12 +1430,65 @@ void knot_zone_contents_deep_free(knot_zone_contents_t **contents)
 uint32_t knot_zone_serial(const knot_zone_contents_t *zone)
 {
 	if (!zone) return 0;
-	const knot_rrset_t *soa = NULL;
-	soa = knot_node_rrset(knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA);
-	return knot_rdata_soa_serial(soa);
+	const knot_rrs_t *soa = NULL;
+	soa = knot_node_rrs(knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA);
+	uint32_t serial = knot_rrs_soa_serial(soa);
+	return serial;
 }
 
 bool knot_zone_contents_is_signed(const knot_zone_contents_t *zone)
 {
 	return knot_node_rrtype_is_signed(zone->apex, KNOT_RRTYPE_SOA);
 }
+
+knot_node_t *zone_contents_get_node_for_rr(knot_zone_contents_t *zone,
+                                           const knot_rrset_t *rrset)
+{
+	if (zone == NULL || rrset == NULL) {
+		return NULL;
+	}
+
+	knot_node_t *node;
+	const bool nsec3 = rrset_is_nsec3rel(rrset);
+	if (!nsec3) {
+		node = knot_zone_contents_get_node(zone, rrset->owner);
+	} else {
+		node = knot_zone_contents_get_nsec3_node(zone, rrset->owner);
+	}
+
+	if (node == NULL) {
+		int ret = KNOT_EOK;
+		node = knot_node_new(rrset->owner, NULL, 0);
+		if (!nsec3) {
+			ret = knot_zone_contents_add_node(zone, node, 1, 0);
+		} else {
+			ret = knot_zone_contents_add_nsec3_node(zone, node);
+		}
+		if (ret != KNOT_EOK) {
+			knot_node_free(&node);
+			return NULL;
+		}
+
+		return node;
+	} else {
+		return node;
+	}
+}
+
+knot_node_t *zone_contents_find_node_for_rr(knot_zone_contents_t *zone,
+                                            const knot_rrset_t *rrset)
+{
+	if (zone == NULL || rrset == NULL) {
+		return NULL;
+	}
+
+	knot_node_t *node;
+	const bool nsec3 = rrset_is_nsec3rel(rrset);
+	if (!nsec3) {
+		node = knot_zone_contents_get_node(zone, rrset->owner);
+	} else {
+		node = knot_zone_contents_get_nsec3_node(zone, rrset->owner);
+	}
+
+	return node;
+}
diff --git a/src/knot/zone/zone-contents.h b/src/knot/zone/zone-contents.h
index 06b5d2c26d7fc09cfdca217c761d7193964eff32..4331a98451d340da117c68dcaf1b96d14133423f 100644
--- a/src/knot/zone/zone-contents.h
+++ b/src/knot/zone/zone-contents.h
@@ -29,6 +29,7 @@
 
 #include "knot/zone/node.h"
 #include "libknot/dnssec/nsec3.h"
+#include "common/lists.h"
 
 #include "knot/zone/zone-tree.h"
 
@@ -92,89 +93,8 @@ int knot_zone_contents_gen_is_new(const knot_zone_contents_t *contents);
 void knot_zone_contents_set_gen_old(knot_zone_contents_t *contents);
 void knot_zone_contents_set_gen_new(knot_zone_contents_t *contents);
 
-uint16_t knot_zone_contents_class(const knot_zone_contents_t *contents);
-
-/*!
- * \brief Adds a node to the given zone.
- *
- * Checks if the node belongs to the zone, i.e. if its owner is a subdomain of
- * the zone's apex. It thus also forbids adding node with the same name as the
- * zone apex.
- *
- * \warning This function may destroy domain names saved in the node, that
- *          are already present in the zone.
- *
- * \param zone Zone to add the node into.
- * \param node Node to add into the zone.
- *
- * \retval KNOT_EOK
- * \retval KNOT_EINVAL
- * \retval KNOT_EOUTOFZONE
- * \retval KNOT_EHASH
- */
-int knot_zone_contents_add_node(knot_zone_contents_t *contents,
-                                  knot_node_t *node, int create_parents,
-                                  uint8_t flags);
-
-/*!
- * \brief Create new node in the zone contents for given RRSet.
- *
- * \param contents Zone to add the node into.
- * \param rr Given RRSet.
- * \param node Returns created node.
- * \return
- */
-int knot_zone_contents_create_node(knot_zone_contents_t *contents,
-                                   const knot_rrset_t *rr,
-                                   knot_node_t **node);
-
-int knot_zone_contents_add_rr(knot_zone_contents_t *z, knot_rrset_t *rr,
-                              knot_node_t **n, bool *ttl_err);
-
-/*!
- * \brief Adds a RRSet to the given zone.
- *
- * Checks if the RRSet belongs to the zone, i.e. if its owner is a subdomain of
- * the zone's apex. The RRSet is inserted only if the node is given, or if
- * a node where the RRSet should belong is found in the zone.
- *
- * \warning The function does not check if the node is already inserted in the
- *          zone, just assumes that it is.
- * \warning This function may destroy domain names saved in the RRSet, that
- *          are already present in the zone.
- *
- * \param zone Zone to add the node into.
- * \param rrset RRSet to add into the zone.
- * \param node Node the RRSet should be inserted into. (Should be a node of the
- *             given zone.) If set to NULL, the function will find proper node
- *             and set it to this parameter.
- *
- * \retval KNOT_EOK
- * \retval KNOT_EINVAL
- * \retval KNOT_EOUTOFZONE
- */
-int knot_zone_contents_add_rrset(knot_zone_contents_t *contents,
-                                 knot_rrset_t *rrset, knot_node_t **node,
-                                 knot_rrset_dupl_handling_t dupl);
-
-/*!
- * \brief Adds a node holding NSEC3 records to the given zone.
- *
- * Checks if the node belongs to the zone, i.e. if its owner is a subdomain of
- * the zone's apex. It does not check if the node really contains any NSEC3
- * records, nor if the name is a hash (as there is actually no way of
- * determining this).
- *
- * \param zone Zone to add the node into.
- * \param node Node to add into the zone.
- *
- * \retval KNOT_EOK
- * \retval KNOT_EINVAL
- * \retval KNOT_EOUTOFZONE
- */
-int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *contents,
-                                        knot_node_t *node, int create_parents,
-                                        uint8_t flags);
+int knot_zone_contents_add_rr(knot_zone_contents_t *z,
+                              const knot_rrset_t *rr, knot_node_t **n, bool *ttl_err);
 
 int knot_zone_contents_remove_node(knot_zone_contents_t *contents,
 	const knot_dname_t *owner);
@@ -182,29 +102,6 @@ int knot_zone_contents_remove_node(knot_zone_contents_t *contents,
 int knot_zone_contents_remove_nsec3_node(knot_zone_contents_t *contents,
 	const knot_dname_t *owner);
 
-/*!
- * \brief Tries to find a node with the specified name in the zone.
- *
- * \param zone Zone where the name should be searched for.
- * \param name Name to find.
- *
- * \return Corresponding node if found, NULL otherwise.
- */
-knot_node_t *knot_zone_contents_get_node(
-	const knot_zone_contents_t *contents, const knot_dname_t *name);
-
-/*!
- * \brief Tries to find a node with the specified name among the NSEC3 nodes
- *        of the zone.
- *
- * \param zone Zone where the name should be searched for.
- * \param name Name to find.
- *
- * \return Corresponding node if found, NULL otherwise.
- */
-knot_node_t *knot_zone_contents_get_nsec3_node(
-	const knot_zone_contents_t *contents, const knot_dname_t *name);
-
 /*!
  * \brief Tries to find a node with the specified name in the zone.
  *
@@ -251,15 +148,6 @@ int knot_zone_contents_find_dname(const knot_zone_contents_t *contents,
 const knot_node_t *knot_zone_contents_find_previous(
 	const knot_zone_contents_t *contents, const knot_dname_t *name);
 
-knot_node_t *knot_zone_contents_get_previous(
-	const knot_zone_contents_t *contents, const knot_dname_t *name);
-
-const knot_node_t *knot_zone_contents_find_previous_nsec3(
-	const knot_zone_contents_t *contents, const knot_dname_t *name);
-
-knot_node_t *knot_zone_contents_get_previous_nsec3(
-	const knot_zone_contents_t *contents, const knot_dname_t *name);
-
 /*!
  * \brief Tries to find a node with the specified name among the NSEC3 nodes
  *        of the zone.
@@ -311,9 +199,6 @@ int knot_zone_contents_find_nsec3_for_name(
 const knot_node_t *knot_zone_contents_apex(
 	const knot_zone_contents_t *contents);
 
-knot_node_t *knot_zone_contents_get_apex(
-	const knot_zone_contents_t *contents);
-
 /*!
  * \brief Sets parent and previous pointers and node flags. (cheap operation)
  *        For both normal and NSEC3 tree
@@ -444,6 +329,12 @@ uint32_t knot_zone_serial(const knot_zone_contents_t *zone);
  */
 bool knot_zone_contents_is_signed(const knot_zone_contents_t *zone);
 
+knot_node_t *zone_contents_get_node_for_rr(knot_zone_contents_t *zone,
+                                           const knot_rrset_t *rrset);
+
+knot_node_t *zone_contents_find_node_for_rr(knot_zone_contents_t *zone,
+                                            const knot_rrset_t *rrset);
+
 #endif
 
 /*! @} */
diff --git a/src/knot/zone/zone-create.c b/src/knot/zone/zone-create.c
index b550b27f63c52b59689c5d5c9a5bbed3bfadb5cf..cc6fd779662d7f66bd07ffb003364cbc2aa5fd3b 100644
--- a/src/knot/zone/zone-create.c
+++ b/src/knot/zone/zone-create.c
@@ -77,16 +77,15 @@ static bool handle_err(zcreator_t *zc,
 	}
 }
 
-int zcreator_step(zcreator_t *zc, knot_rrset_t *rr)
+int zcreator_step(zcreator_t *zc, const knot_rrset_t *rr)
 {
 	if (zc == NULL || rr == NULL || knot_rrset_rr_count(rr) != 1) {
 		return KNOT_EINVAL;
 	}
 
 	if (rr->type == KNOT_RRTYPE_SOA &&
-	    knot_node_rrset(zc->z->apex, KNOT_RRTYPE_SOA)) {
+	    knot_node_rrtype_exists(zc->z->apex, KNOT_RRTYPE_SOA)) {
 		// Ignore extra SOA
-		knot_rrset_free(&rr, NULL);
 		return KNOT_EOK;
 	}
 
@@ -99,7 +98,6 @@ int zcreator_step(zcreator_t *zc, knot_rrset_t *rr)
 			return ret;
 		}
 		// Recoverable error, skip record
-		knot_rrset_free(&rr, NULL);
 		return KNOT_EOK;
 	}
 	assert(node);
@@ -132,10 +130,6 @@ int zcreator_step(zcreator_t *zc, knot_rrset_t *rr)
 		}
 	}
 
-	if (ret > 0) {
-		knot_rrset_free(&rr, NULL);
-	}
-
 	ret = sem_check_node_plain(zc->z, node,
 	                           &err_handler, true,
 	                           &sem_fatal_error);
@@ -154,37 +148,30 @@ static void loader_process(const zs_scanner_t *scanner)
 		return;
 	}
 
-	knot_dname_t *owner = knot_dname_copy(scanner->r_owner);
+	knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL);
 	if (owner == NULL) {
 		zc->ret = KNOT_ENOMEM;
 		return;
 	}
 	knot_dname_to_lower(owner);
 
-	knot_rrset_t *rr = knot_rrset_new(owner,
-	                                  scanner->r_type,
-	                                  scanner->r_class,
-	                                  NULL);
-	if (rr == NULL) {
-		knot_dname_free(&owner);
-		zc->ret = KNOT_ENOMEM;
-		return;
-	}
-
-	int ret = add_rdata_to_rr(rr, scanner);
+	knot_rrset_t rr;
+	knot_rrset_init(&rr, owner, scanner->r_type, scanner->r_class);
+	int ret = add_rdata_to_rr(&rr, scanner);
 	if (ret != KNOT_EOK) {
-		char *rr_name = knot_dname_to_str(rr->owner);
+		char *rr_name = knot_dname_to_str(rr.owner);
 		log_zone_error("%s:%"PRIu64": Can't add RDATA for '%s'.\n",
 		               scanner->file_name, scanner->line_counter, rr_name);
 		free(rr_name);
-		knot_rrset_free(&rr, NULL);
+		knot_dname_free(&owner, NULL);
 		zc->ret = ret;
 		return;
 	}
 
-	ret = zcreator_step(zc, rr);
+	ret = zcreator_step(zc, &rr);
+	knot_dname_free(&owner, NULL);
+	knot_rrs_clear(&rr.rrs, NULL);
 	if (ret != KNOT_EOK) {
-		knot_rrset_free(&rr, NULL);
 		zc->ret = ret;
 		return;
 	}
@@ -201,7 +188,7 @@ static knot_zone_contents_t *create_zone_from_name(const char *origin)
 	}
 	knot_dname_to_lower(owner);
 	knot_zone_contents_t *z = knot_zone_contents_new(owner);
-	knot_dname_free(&owner);
+	knot_dname_free(&owner, NULL);
 	return z;
 }
 
@@ -280,7 +267,7 @@ knot_zone_contents_t *zonefile_load(zloader_t *loader)
 		goto fail;
 	}
 
-	if (knot_node_rrset(loader->creator->z->apex, KNOT_RRTYPE_SOA) == NULL) {
+	if (!knot_node_rrtype_exists(loader->creator->z->apex, KNOT_RRTYPE_SOA)) {
 		log_zone_error("%s: no SOA record in the zone file.\n",
 		               loader->source);
 		goto fail;
@@ -299,17 +286,14 @@ knot_zone_contents_t *zonefile_load(zloader_t *loader)
 
 	if (loader->semantic_checks) {
 		int check_level = SEM_CHECK_UNSIGNED;
-		const knot_rrset_t *soa_rr =
-			knot_node_rrset(zc->z->apex,
-		                        KNOT_RRTYPE_SOA);
-		assert(soa_rr); // In this point, SOA has to exist
-		const knot_rrset_t *nsec3param_rr =
-			knot_node_rrset(zc->z->apex,
-		                        KNOT_RRTYPE_NSEC3PARAM);
-		if (knot_zone_contents_is_signed(zc->z) && nsec3param_rr == NULL) {
+		knot_rrset_t soa_rr = knot_node_rrset(zc->z->apex, KNOT_RRTYPE_SOA);
+		assert(!knot_rrset_empty(&soa_rr)); // In this point, SOA has to exist
+		const bool have_nsec3param =
+			knot_node_rrtype_exists(zc->z->apex, KNOT_RRTYPE_NSEC3PARAM);
+		if (knot_zone_contents_is_signed(zc->z) && have_nsec3param) {
 			/* Set check level to DNSSEC. */
 			check_level = SEM_CHECK_NSEC;
-		} else if (knot_zone_contents_is_signed(zc->z) && nsec3param_rr) {
+		} else if (knot_zone_contents_is_signed(zc->z) && have_nsec3param) {
 			check_level = SEM_CHECK_NSEC3;
 		}
 		err_handler_t err_handler;
@@ -317,7 +301,7 @@ knot_zone_contents_t *zonefile_load(zloader_t *loader)
 		zone_do_sem_checks(zc->z, check_level,
 		                   &err_handler, first_nsec3_node,
 		                   last_nsec3_node);
-		char *zname = knot_dname_to_str(knot_rrset_owner(soa_rr));
+		char *zname = knot_dname_to_str(soa_rr.owner);
 		log_zone_info("Semantic checks completed for zone=%s\n", zname);
 		free(zname);
 	}
diff --git a/src/knot/zone/zone-create.h b/src/knot/zone/zone-create.h
index 105613d842971a2e0248f5a148d8a07aeed471ce..1b97aeab240e520fcd05967d2fecec0a13b84414 100644
--- a/src/knot/zone/zone-create.h
+++ b/src/knot/zone/zone-create.h
@@ -82,7 +82,7 @@ knot_zone_contents_t *zonefile_load(zloader_t *loader);
  */
 void zonefile_close(zloader_t *loader);
 
-int zcreator_step(zcreator_t *zl, knot_rrset_t *rr);
+int zcreator_step(zcreator_t *zl, const knot_rrset_t *rr);
 
 void process_error(const zs_scanner_t *scanner);
 
diff --git a/src/knot/zone/zone-diff.c b/src/knot/zone/zone-diff.c
index be3d5a00736e62bec85318af6690d2027558c324..43cd8bb545e00dfe7098bd7fa3b0726260b49973 100644
--- a/src/knot/zone/zone-diff.c
+++ b/src/knot/zone/zone-diff.c
@@ -46,74 +46,48 @@ static int knot_zone_diff_load_soas(const knot_zone_contents_t *zone1,
 	const knot_node_t *apex1 = knot_zone_contents_apex(zone1);
 	const knot_node_t *apex2 = knot_zone_contents_apex(zone2);
 	if (apex1 == NULL || apex2 == NULL) {
-		dbg_zonediff("zone_diff: "
-		             "both zones must have apex nodes.\n");
 		return KNOT_EINVAL;
 	}
 
-	knot_rrset_t *soa_rrset1 = knot_node_get_rrset(apex1, KNOT_RRTYPE_SOA);
-	knot_rrset_t *soa_rrset2 = knot_node_get_rrset(apex2, KNOT_RRTYPE_SOA);
-	if (soa_rrset1 == NULL || soa_rrset2 == NULL) {
-		dbg_zonediff("zone_diff: "
-		             "both zones must have apex nodes.\n");
+	knot_rrset_t soa_rrset1 = knot_node_rrset(apex1, KNOT_RRTYPE_SOA);
+	knot_rrset_t soa_rrset2 = knot_node_rrset(apex2, KNOT_RRTYPE_SOA);
+	if (knot_rrset_empty(&soa_rrset1) || knot_rrset_empty(&soa_rrset2)) {
 		return KNOT_EINVAL;
 	}
 
-	if (knot_rrset_rr_count(soa_rrset1) == 0 ||
-	    knot_rrset_rr_count(soa_rrset2) == 0) {
-		dbg_zonediff("zone_diff: "
-		             "both zones must have apex nodes with SOA "
-		             "RRs.\n");
+	if (knot_rrset_rr_count(&soa_rrset1) == 0 ||
+	    knot_rrset_rr_count(&soa_rrset2) == 0) {
 		return KNOT_EINVAL;
 	}
 
-	int64_t soa_serial1 =
-		knot_rdata_soa_serial(soa_rrset1);
-	if (soa_serial1 == -1) {
-		dbg_zonediff("zone_diff: load_soas: Got bad SOA.\n");
-	}
-
-	int64_t soa_serial2 =
-		knot_rdata_soa_serial(soa_rrset2);
-	if (soa_serial2 == -1) {
-		dbg_zonediff("zone_diff: load_soas: Got bad SOA.\n");
-	}
+	int64_t soa_serial1 = knot_rrs_soa_serial(&soa_rrset1.rrs);
+	int64_t soa_serial2 = knot_rrs_soa_serial(&soa_rrset2.rrs);
 
 	if (knot_serial_compare(soa_serial1, soa_serial2) == 0) {
-		dbg_zonediff("zone_diff: "
-		             "second zone must have higher serial than the "
-		             "first one. (%"PRId64" vs. %"PRId64")\n",
-		             soa_serial1, soa_serial2);
 		return KNOT_ENODIFF;
 	}
 
 	if (knot_serial_compare(soa_serial1, soa_serial2) > 0) {
-		dbg_zonediff("zone_diff: "
-		             "second zone must have higher serial than the "
-		             "first one. (%"PRId64" vs. %"PRId64")\n",
-		             soa_serial1, soa_serial2);
 		return KNOT_ERANGE;
 	}
 
 	assert(changeset);
 
-	int ret = knot_rrset_copy(soa_rrset1, &changeset->soa_from, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_zonediff("zone_diff: load_soas: Cannot copy RRSet.\n");
-		return ret;
+	changeset->soa_from = knot_rrset_copy(&soa_rrset1, NULL);
+	if (changeset->soa_from == NULL) {
+		return KNOT_ENOMEM;
 	}
-
-	ret = knot_rrset_copy(soa_rrset2, &changeset->soa_to, NULL);
-	if (ret != KNOT_EOK) {
-		dbg_zonediff("zone_diff: load_soas: Cannot copy RRSet.\n");
-		return ret;
+	changeset->soa_to = knot_rrset_copy(&soa_rrset2, NULL);
+	if (changeset->soa_to == NULL) {
+		knot_rrset_free(&changeset->soa_from, NULL);
+		return KNOT_ENOMEM;
 	}
 
 	changeset->serial_from = soa_serial1;
 	changeset->serial_to = soa_serial2;
 
 	dbg_zonediff_verb("zone_diff: load_soas: SOAs diffed. (%"PRId64" -> %"PRId64")\n",
-	            soa_serial1, soa_serial2);
+	                  soa_serial1, soa_serial2);
 
 	return KNOT_EOK;
 }
@@ -134,15 +108,14 @@ static int knot_zone_diff_changeset_add_rrset(knot_changeset_t *changeset,
 		return KNOT_EOK;
 	}
 
-	knot_rrset_t *rrset_copy = NULL;
-	int ret = knot_rrset_copy(rrset, &rrset_copy, NULL);
-	if (ret != KNOT_EOK) {
+	knot_rrset_t *rrset_copy = knot_rrset_copy(rrset, NULL);
+	if (rrset_copy == NULL) {
 		dbg_zonediff("zone_diff: add_rrset: Cannot copy RRSet.\n");
-		return ret;
+		return KNOT_ENOMEM;
 	}
 
-	ret = knot_changeset_add_rrset(changeset, rrset_copy,
-	                               KNOT_CHANGESET_ADD);
+	int ret = knot_changeset_add_rrset(changeset, rrset_copy,
+	                                   KNOT_CHANGESET_ADD);
 	if (ret != KNOT_EOK) {
 		/* We have to free the copy now! */
 		knot_rrset_free(&rrset_copy, NULL);
@@ -173,15 +146,14 @@ static int knot_zone_diff_changeset_remove_rrset(knot_changeset_t *changeset,
 		return KNOT_EOK;
 	}
 
-	knot_rrset_t *rrset_copy = NULL;
-	int ret = knot_rrset_copy(rrset, &rrset_copy, NULL);
-	if (ret != KNOT_EOK) {
+	knot_rrset_t *rrset_copy = knot_rrset_copy(rrset, NULL);
+	if (rrset_copy == NULL) {
 		dbg_zonediff("zone_diff: remove_rrset: Cannot copy RRSet.\n");
-		return ret;
+		return KNOT_ENOMEM;
 	}
 
-	ret = knot_changeset_add_rrset(changeset, rrset_copy,
-	                               KNOT_CHANGESET_REMOVE);
+	int ret = knot_changeset_add_rrset(changeset, rrset_copy,
+	                                   KNOT_CHANGESET_REMOVE);
 	if (ret != KNOT_EOK) {
 		/* We have to free the copy now. */
 		knot_rrset_free(&rrset_copy, NULL);
@@ -196,74 +168,48 @@ static int knot_zone_diff_changeset_remove_rrset(knot_changeset_t *changeset,
 static int knot_zone_diff_add_node(const knot_node_t *node,
                                    knot_changeset_t *changeset)
 {
-	if (node == NULL || changeset == NULL) {
-		dbg_zonediff("zone_diff: add_node: NULL arguments.\n");
-		return KNOT_EINVAL;
-	}
-
 	/* Add all rrsets from node. */
-	const knot_rrset_t **rrsets = knot_node_rrsets(node);
-	if (rrsets == NULL) {
-		/* Empty non-terminals - legal case. */
-		dbg_zonediff_detail("zone_diff: Node has no RRSets.\n");
-		return KNOT_EOK;
-	}
-
 	for (uint i = 0; i < knot_node_rrset_count(node); i++) {
-		assert(rrsets[i]);
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
 		int ret = knot_zone_diff_changeset_add_rrset(changeset,
-		                                         rrsets[i]);
+		                                             &rrset);
 		if (ret != KNOT_EOK) {
 			dbg_zonediff("zone_diff: add_node: Cannot add RRSet (%s).\n",
 			       knot_strerror(ret));
-			free(rrsets);
 			return ret;
 		}
 	}
 
-	free(rrsets);
-
 	return KNOT_EOK;
 }
 
 static int knot_zone_diff_remove_node(knot_changeset_t *changeset,
                                                 const knot_node_t *node)
 {
-	if (changeset == NULL || node == NULL) {
-		dbg_zonediff("zone_diff: remove_node: NULL parameters.\n");
-		return KNOT_EINVAL;
-	}
-
-	dbg_zonediff("zone_diff: remove_node: Removing node: ...\n");
-
-	const knot_rrset_t **rrsets = knot_node_rrsets(node);
-	if (rrsets == NULL) {
-		dbg_zonediff_verb("zone_diff: remove_node: "
-		                  "Nothing to remove.\n");
-		return KNOT_EOK;
-	}
-
-	dbg_zonediff_detail("zone_diff: remove_node: Will be removing %d RRSets.\n",
-	              knot_node_rrset_count(node));
-
 	/* Remove all the RRSets of the node. */
 	for (uint i = 0; i < knot_node_rrset_count(node); i++) {
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
 		int ret = knot_zone_diff_changeset_remove_rrset(changeset,
-		                                            rrsets[i]);
+		                                                &rrset);
 		if (ret != KNOT_EOK) {
 			dbg_zonediff("zone_diff: remove_node: Failed to "
 			             "remove rrset. Error: %s\n",
 			             knot_strerror(ret));
-			free(rrsets);
 			return ret;
 		}
 	}
 
-	free(rrsets);
-
 	return KNOT_EOK;
 }
 
+static bool rr_exists(const knot_rrset_t *in, const knot_rrset_t *ref,
+                      size_t ref_pos)
+{
+	knot_rr_t *to_check = knot_rrs_rr(&ref->rrs, ref_pos);
+	const bool compare_ttls = true;
+	return knot_rrs_member(&in->rrs, to_check, compare_ttls);
+}
+
 static int knot_zone_diff_rdata_return_changes(const knot_rrset_t *rrset1,
                                                const knot_rrset_t *rrset2,
                                                knot_rrset_t **changes)
@@ -282,53 +228,32 @@ static int knot_zone_diff_rdata_return_changes(const knot_rrset_t *rrset1,
 	* changed/removed rdatas. This has awful computation time.
 	*/
 	/* Create fake RRSet, it will be easier to handle. */
-	knot_dname_t *owner_copy = knot_dname_copy(knot_rrset_get_owner(rrset1));
-	*changes = knot_rrset_new(owner_copy,
-	                          knot_rrset_type(rrset1),
-	                          knot_rrset_class(rrset1),
-	                          NULL);
+
+	*changes = knot_rrset_new(rrset1->owner, rrset1->type,
+	                          rrset1->rclass, NULL);
 	if (*changes == NULL) {
-		knot_dname_free(&owner_copy);
 		dbg_zonediff("zone_diff: diff_rdata: "
 		             "Could not create RRSet with changes.\n");
 		return KNOT_ENOMEM;
 	}
 
 	const rdata_descriptor_t *desc =
-		get_rdata_descriptor(knot_rrset_type(rrset1));
+		get_rdata_descriptor(rrset1->type);
 	assert(desc);
 
 	uint16_t rr1_count = knot_rrset_rr_count(rrset1);
 	for (uint16_t i = 0; i < rr1_count; ++i) {
-		size_t rr_pos = 0;
-		int ret = knot_rrset_find_rr_pos(rrset2, rrset1, i, &rr_pos);
-		if (ret == KNOT_ENOENT) {
-			/* No such RR is present in 'rrset2'. */
-			dbg_zonediff("zone_diff: diff_rdata: "
-			       "No match for RR (type=%u owner=%p).\n",
-			       knot_rrset_type(rrset1),
-			       rrset1->owner);
-			/* We'll copy index 'i' into 'changes' RRSet. */
-			ret = knot_rrset_add_rr_from_rrset(*changes, rrset1, i, NULL);
+		if (!rr_exists(rrset2, rrset1, i)) {
+			/*
+			 * No such RR is present in 'rrset2'. We'll copy
+			 * index 'i' into 'changes' RRSet.
+			 */
+			knot_rr_t *add_rr = knot_rrs_rr(&rrset1->rrs, i);
+			int ret = knot_rrs_add_rr(&(*changes)->rrs, add_rr, NULL);
 			if (ret != KNOT_EOK) {
 				knot_rrset_free(changes, NULL);
 				return ret;
 			}
-		} else if (ret == KNOT_EOK) {
-			if (knot_rrset_rr_ttl(rrset1, i) !=
-			    knot_rrset_rr_ttl(rrset2, rr_pos)) {
-				// TTLs differ add a change
-				ret = knot_rrset_add_rr_from_rrset(*changes, rrset1, i, NULL);
-				if (ret != KNOT_EOK) {
-					knot_rrset_free(changes, NULL);
-					return ret;
-				}
-			}
-		} else {
-			dbg_zonediff("zone_diff: diff_rdata: Could not search "
-			             "for RR (%s).\n", knot_strerror(ret));
-			knot_rrset_free(changes, NULL);
-			return ret;
 		}
 	}
 
@@ -449,8 +374,7 @@ static int knot_zone_diff_node(knot_node_t **node_ptr, void *data)
 	dbg_zonediff_detail("zone_diff: diff_node: Node %p is present in "
 	              "both trees.\n", node_owner);
 	/* The nodes are in both trees, we have to diff each RRSet. */
-	const knot_rrset_t **rrsets = knot_node_rrsets(node);
-	if (rrsets == NULL) {
+	if (node->rrset_count == 0) {
 		dbg_zonediff("zone_diff: Node in first tree has no RRSets.\n");
 		/*
 		 * If there are no RRs in the first tree, all of the RRs
@@ -468,110 +392,55 @@ static int knot_zone_diff_node(knot_node_t **node_ptr, void *data)
 
 	for (uint i = 0; i < knot_node_rrset_count(node); i++) {
 		/* Search for the RRSet in the node from the second tree. */
-		const knot_rrset_t *rrset = rrsets[i];
-		assert(rrset);
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
 
-		/* SOAs are handled explicitly. */
-		if (knot_rrset_type(rrset) == KNOT_RRTYPE_SOA) {
+		/* SOAs are handled explicitely. */
+		if (rrset.type == KNOT_RRTYPE_SOA) {
 			continue;
 		}
 
-		const knot_rrset_t *rrset_from_second_node =
-			knot_node_rrset(node_in_second_tree,
-			                knot_rrset_type(rrset));
-		if (rrset_from_second_node == NULL) {
-			dbg_zonediff("zone_diff: diff_node: There is no counterpart "
-			       "for RRSet of type %u in second tree.\n",
-			       knot_rrset_type(rrset));
+		knot_rrset_t rrset_from_second_node = knot_node_rrset(node_in_second_tree, rrset.type);
+		if (knot_rrset_empty(&rrset_from_second_node)) {
 			/* RRSet has been removed. Make a copy and remove. */
-			assert(rrset);
 			int ret = knot_zone_diff_changeset_remove_rrset(
 				param->changeset,
-				rrset);
+				&rrset);
 			if (ret != KNOT_EOK) {
 				dbg_zonediff("zone_diff: diff_node: "
 				             "Failed to remove RRSet.\n");
-				free(rrsets);
 				return ret;
 			}
 		} else {
-			dbg_zonediff("zone_diff: diff_node: There is a counterpart "
-			       "for RRSet of type %u in second tree.\n",
-			       knot_rrset_type(rrset));
 			/* Diff RRSets. */
-			int ret = knot_zone_diff_rrsets(rrset,
-			                                rrset_from_second_node,
+			int ret = knot_zone_diff_rrsets(&rrset,
+			                                &rrset_from_second_node,
 			                                param->changeset);
 			if (ret != KNOT_EOK) {
 				dbg_zonediff("zone_diff: "
 				             "Failed to diff RRSets.\n");
-				free(rrsets);
 				return ret;
 			}
-
-//			dbg_zonediff_verb("zone_diff: diff_node: Changes in "
-//			            "RRSIGs.\n");
-//			/*! \todo There is ad-hoc solution in the function, maybe handle here. */
-//			ret = knot_zone_diff_rrsets(rrset->rrsigs,
-//			                                rrset_from_second_node->rrsigs,
-//			                                param->changeset);
-//			if (ret != KNOT_EOK) {
-//				dbg_zonediff("zone_diff: "
-//				             "Failed to diff RRSIGs.\n");
-//				return ret;
-//			}
 		}
 	}
 
-	free(rrsets);
-
-	/*! \todo move to one function with the code above. */
-	rrsets = knot_node_rrsets(node_in_second_tree);
-	if (rrsets == NULL) {
-		dbg_zonediff("zone_diff: Node in second tree has no RRSets.\n");
-		/*
-		 * This can happen when node in second
-		 * tree is empty non-terminal and as such has no RRs.
-		 * Whole node from the first tree has to be removed.
-		 */
-		// TODO following code creates duplicated RR in diff.
-		// IHMO such case should be handled here
-//		int ret = knot_zone_diff_remove_node(param->changeset,
-//		                                     node);
-//		if (ret != KNOT_EOK) {
-//			dbg_zonediff("zone_diff: diff_node: "
-//			             "Cannot remove node. Reason: %s.\n",
-//			             knot_strerror(ret));
-//		}
-		return KNOT_EOK;
-	}
-
 	for (uint i = 0; i < knot_node_rrset_count(node_in_second_tree); i++) {
 		/* Search for the RRSet in the node from the second tree. */
-		const knot_rrset_t *rrset = rrsets[i];
-		assert(rrset);
+		knot_rrset_t rrset = knot_node_rrset_at(node_in_second_tree, i);
 
-		/* SOAs are handled explicitly. */
-		if (knot_rrset_type(rrset) == KNOT_RRTYPE_SOA) {
+		/* SOAs are handled explicitely. */
+		if (rrset.type == KNOT_RRTYPE_SOA) {
 			continue;
 		}
 
-		const knot_rrset_t *rrset_from_first_node =
-			knot_node_rrset(node,
-			                knot_rrset_type(rrset));
-		if (rrset_from_first_node == NULL) {
-			dbg_zonediff("zone_diff: diff_node: There is no counterpart "
-			       "for RRSet of type %u in first tree.\n",
-			       knot_rrset_type(rrset));
+		knot_rrset_t rrset_from_first_node = knot_node_rrset(node, rrset.type);
+		if (knot_rrset_empty(&rrset_from_first_node)) {
 			/* RRSet has been added. Make a copy and add. */
-			assert(rrset);
 			int ret = knot_zone_diff_changeset_add_rrset(
 				param->changeset,
-				rrset);
+				&rrset);
 			if (ret != KNOT_EOK) {
 				dbg_zonediff("zone_diff: diff_node: "
 				             "Failed to add RRSet.\n");
-				free(rrsets);
 				return ret;
 			}
 		} else {
@@ -580,8 +449,6 @@ static int knot_zone_diff_node(knot_node_t **node_ptr, void *data)
 		}
 	}
 
-	free(rrsets);
-
 	return KNOT_EOK;
 }
 
@@ -657,7 +524,6 @@ static int knot_zone_diff_load_trees(knot_zone_tree_t *nodes1,
 	return result;
 }
 
-
 static int knot_zone_diff_load_content(const knot_zone_contents_t *zone1,
                                        const knot_zone_contents_t *zone2,
                                        knot_changeset_t *changeset)
@@ -674,7 +540,6 @@ static int knot_zone_diff_load_content(const knot_zone_contents_t *zone1,
 	return result;
 }
 
-
 static int knot_zone_contents_diff(const knot_zone_contents_t *zone1,
                             const knot_zone_contents_t *zone2,
                             knot_changeset_t *changeset)
diff --git a/src/knot/zone/zone-dump.c b/src/knot/zone/zone-dump.c
index ee6b916ca30c3447199ed80a04854f14e1b7e801..4443ac2bc4930f0920b56604036ec9513342e41d 100644
--- a/src/knot/zone/zone-dump.c
+++ b/src/knot/zone/zone-dump.c
@@ -40,27 +40,25 @@ typedef struct {
 
 static int apex_node_dump_text(knot_node_t *node, dump_params_t *params)
 {
-	const knot_rrset_t *soa = knot_node_rrset(node, KNOT_RRTYPE_SOA);
-
+	knot_rrset_t soa = knot_node_rrset(node, KNOT_RRTYPE_SOA);
 	knot_dump_style_t soa_style = *params->style;
 
 	// Dump SOA record as a first.
 	if (!params->dump_nsec) {
 		soa_style.show_class = true;
-		if (knot_rrset_txt_dump(soa, params->buf, params->buflen,
+		if (knot_rrset_txt_dump(&soa, params->buf, params->buflen,
 					&soa_style) < 0) {
 			return KNOT_ENOMEM;
 		}
-		params->rr_count += knot_rrset_rr_count(soa);
+		params->rr_count += knot_rrset_rr_count(&soa);
 		fprintf(params->file, "%s", params->buf);
 		params->buf[0] = '\0';
 	}
 
-	const knot_rrset_t **rrsets = knot_node_rrsets_no_copy(node);
-
 	// Dump other records.
 	for (uint16_t i = 0; i < node->rrset_count; i++) {
-		switch (rrsets[i]->type) {
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
+		switch (rrset.type) {
 		case KNOT_RRTYPE_NSEC:
 			continue;
 		case KNOT_RRTYPE_RRSIG:
@@ -71,11 +69,11 @@ static int apex_node_dump_text(knot_node_t *node, dump_params_t *params)
 			break;
 		}
 
-		if (knot_rrset_txt_dump(rrsets[i], params->buf, params->buflen,
+		if (knot_rrset_txt_dump(&rrset, params->buf, params->buflen,
 		                        params->style) < 0) {
 			return KNOT_ENOMEM;
 		}
-		params->rr_count +=  knot_rrset_rr_count(rrsets[i]);
+		params->rr_count +=  knot_rrset_rr_count(&rrset);
 		fprintf(params->file, "%s", params->buf);
 		params->buf[0] = '\0';
 	}
@@ -94,11 +92,10 @@ static int node_dump_text(knot_node_t *node, void *data)
 		return KNOT_EOK;
 	}
 
-	const knot_rrset_t **rrsets = knot_node_rrsets_no_copy(node);
-
 	// Dump non-apex rrsets.
 	for (uint16_t i = 0; i < node->rrset_count; i++) {
-		switch (rrsets[i]->type) {
+		knot_rrset_t rrset = knot_node_rrset_at(node, i);
+		switch (rrset.type) {
 		case KNOT_RRTYPE_RRSIG:
 			if (params->dump_rrsig) {
 				break;
@@ -121,11 +118,11 @@ static int node_dump_text(knot_node_t *node, void *data)
 			break;
 		}
 
-		if (knot_rrset_txt_dump(rrsets[i], params->buf, params->buflen,
+		if (knot_rrset_txt_dump(&rrset, params->buf, params->buflen,
 		                        params->style) < 0) {
 			return KNOT_ENOMEM;
 		}
-		params->rr_count += knot_rrset_rr_count(rrsets[i]);
+		params->rr_count += knot_rrset_rr_count(&rrset);
 		fprintf(params->file, "%s", params->buf);
 		params->buf[0] = '\0';
 	}
diff --git a/src/knot/zone/zone-tree.c b/src/knot/zone/zone-tree.c
index 3628694878e4255732cdcaf2a39f2857c15961c3..af36361e8c10ab567fc5441d6875800681924b3f 100644
--- a/src/knot/zone/zone-tree.c
+++ b/src/knot/zone/zone-tree.c
@@ -23,23 +23,6 @@
 #include "common/debug.h"
 #include "common/hattrie/hat-trie.h"
 
-/*----------------------------------------------------------------------------*/
-/* Non-API functions                                                          */
-/*----------------------------------------------------------------------------*/
-
-static value_t knot_zone_node_copy(value_t v)
-{
-	return v;
-}
-
-static value_t knot_zone_node_deep_copy(value_t v)
-{
-	knot_node_t *n = NULL;
-	knot_node_shallow_copy((knot_node_t *)v, &n);
-	knot_node_set_new_node((knot_node_t *)v, n);
-	return (value_t)n;
-}
-
 /*----------------------------------------------------------------------------*/
 /* API functions                                                              */
 /*----------------------------------------------------------------------------*/
@@ -279,42 +262,6 @@ int knot_zone_tree_apply(knot_zone_tree_t *tree,
 
 /*----------------------------------------------------------------------------*/
 
-int knot_zone_tree_shallow_copy(knot_zone_tree_t *from,
-                                  knot_zone_tree_t **to)
-{
-	if (to == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	if (from != NULL) {
-		*to = hattrie_dup(from, knot_zone_node_copy);
-	} else {
-		*to = NULL;
-	}
-
-	return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-int knot_zone_tree_deep_copy(knot_zone_tree_t *from,
-                             knot_zone_tree_t **to)
-{
-	if (to == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	if (from != NULL) {
-		*to = hattrie_dup(from, knot_zone_node_deep_copy);
-	} else {
-		*to = NULL;
-	}
-
-	return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
 void knot_zone_tree_free(knot_zone_tree_t **tree)
 {
 	if (tree == NULL || *tree == NULL) {
diff --git a/src/knot/zone/zone-tree.h b/src/knot/zone/zone-tree.h
index cbc234ead678ae371f3bf66c438de3b95733a11b..07fcae54ec38674d3a8fe52204ee5e02735cf115 100644
--- a/src/knot/zone/zone-tree.h
+++ b/src/knot/zone/zone-tree.h
@@ -207,25 +207,6 @@ int knot_zone_tree_apply_inorder(knot_zone_tree_t *tree,
 int knot_zone_tree_apply(knot_zone_tree_t *tree,
                          knot_zone_tree_apply_cb_t function, void *data);
 
-/*!
- * \brief Copies the whole zone tree structure (but not the data contained
- *        within).
- *
- * \warning This function does not check if the target zone tree is empty,
- *          it just replaces the root pointer.
- *
- * \param from Original zone tree.
- * \param to Zone tree to copy the original one into.
- *
- * \retval KNOT_EOK
- * \retval KNOT_ENOMEM
- */
-int knot_zone_tree_shallow_copy(knot_zone_tree_t *from,
-                                  knot_zone_tree_t **to);
-
-int knot_zone_tree_deep_copy(knot_zone_tree_t *from,
-                             knot_zone_tree_t **to);
-
 /*!
  * \brief Destroys the zone tree, not touching the saved data.
  *
diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c
index 3d7d19f70600797481adbb5227a5041abab771d8..09f6ffa3363a77178e1635dc4e337b37da361192 100644
--- a/src/knot/zone/zone.c
+++ b/src/knot/zone/zone.c
@@ -223,7 +223,7 @@ void zone_free(zone_t **zone_ptr)
 
 	zone_t *zone = *zone_ptr;
 
-	knot_dname_free(&zone->name);
+	knot_dname_free(&zone->name, NULL);
 
 	/* Cancel and free timers. */
 	zone_timer_free(zone->xfr_in.timer);
diff --git a/src/libknot/dname.c b/src/libknot/dname.c
index 861bbb71f574b2a27b2587c7a11ab9d6db545f86..8688501d01f2624591d99e355e998ff9384b2be2 100644
--- a/src/libknot/dname.c
+++ b/src/libknot/dname.c
@@ -101,7 +101,8 @@ int knot_dname_wire_check(const uint8_t *name, const uint8_t *endp,
 
 /*----------------------------------------------------------------------------*/
 
-knot_dname_t *knot_dname_parse(const uint8_t *pkt, size_t *pos, size_t maxpos)
+knot_dname_t *knot_dname_parse(const uint8_t *pkt, size_t *pos, size_t maxpos,
+                               mm_ctx_t *mm)
 {
 	if (pkt == NULL || pos == NULL)
 		return NULL;
@@ -120,7 +121,7 @@ knot_dname_t *knot_dname_parse(const uint8_t *pkt, size_t *pos, size_t maxpos)
 	}
 
 	/* Allocate space for the name. */
-	knot_dname_t *res = malloc(decompressed_len);
+	knot_dname_t *res = mm_alloc(mm, decompressed_len);
 	if (res) {
 		/* Unpack name (expand compression pointers). */
 		if (knot_dname_unpack(res, name, decompressed_len, pkt) > 0) {
@@ -136,22 +137,23 @@ knot_dname_t *knot_dname_parse(const uint8_t *pkt, size_t *pos, size_t maxpos)
 
 /*----------------------------------------------------------------------------*/
 
-knot_dname_t *knot_dname_copy(const knot_dname_t *name)
+knot_dname_t *knot_dname_copy(const knot_dname_t *name, mm_ctx_t *mm)
 {
 	if (name == NULL)
 		return NULL;
 
-	return knot_dname_copy_part(name, knot_dname_size(name));
+	return knot_dname_copy_part(name, knot_dname_size(name), mm);
 }
 
 /*----------------------------------------------------------------------------*/
 
-knot_dname_t *knot_dname_copy_part(const knot_dname_t *name, unsigned len)
+knot_dname_t *knot_dname_copy_part(const knot_dname_t *name, unsigned len,
+                                   mm_ctx_t *mm)
 {
 	if (name == NULL || len == 0)
 		return NULL;
 
-	knot_dname_t *dst = malloc(len);
+	knot_dname_t *dst = mm_alloc(mm, len);
 	if (knot_dname_to_wire(dst, name, len) < 1) {
 		free(dst);
 		return NULL;
@@ -540,12 +542,12 @@ knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned label
 
 /*----------------------------------------------------------------------------*/
 
-void knot_dname_free(knot_dname_t **name)
+void knot_dname_free(knot_dname_t **name, mm_ctx_t *mm)
 {
 	if (name == NULL || *name == NULL)
 		return;
 
-	free(*name);
+	mm_free(mm, *name);
 	*name = NULL;
 }
 
@@ -615,7 +617,7 @@ knot_dname_t *knot_dname_cat(knot_dname_t *d1, const knot_dname_t *d2)
 
 	/* Like if we are reallocating d1. */
 	if (ret != NULL)
-		knot_dname_free(&d1);
+		knot_dname_free(&d1, NULL);
 
 	return ret;
 }
diff --git a/src/libknot/dname.h b/src/libknot/dname.h
index c69e03fb1640730e9db2e4bce10e21e31bacd5b3..cc5af05c320047a96b18319a88873bf2c566ff7a 100644
--- a/src/libknot/dname.h
+++ b/src/libknot/dname.h
@@ -32,6 +32,7 @@
 #include <stdio.h>
 #include <stdbool.h>
 
+#include "common/mempattern.h"
 #include "libknot/consts.h"
 
 typedef uint8_t knot_dname_t;
@@ -58,10 +59,12 @@ int knot_dname_wire_check(const uint8_t *name, const uint8_t *endp,
  * \param pkt Message in wire format.
  * \param pos Position of the domain name on wire.
  * \param maxpos Domain name length.
+ * \param mm Memory context.
  *
  * \return parsed domain name or NULL.
  */
-knot_dname_t *knot_dname_parse(const uint8_t *pkt, size_t *pos, size_t maxpos);
+knot_dname_t *knot_dname_parse(const uint8_t *pkt, size_t *pos, size_t maxpos,
+                               mm_ctx_t *mm);
 
 /*!
  * \brief Duplicates the given domain name.
@@ -70,7 +73,7 @@ knot_dname_t *knot_dname_parse(const uint8_t *pkt, size_t *pos, size_t maxpos);
  *
  * \return New domain name which is an exact copy of \a dname.
  */
-knot_dname_t *knot_dname_copy(const knot_dname_t *name);
+knot_dname_t *knot_dname_copy(const knot_dname_t *name, mm_ctx_t *mm);
 
 /*!
  * \brief Duplicates part of the given domain name.
@@ -80,7 +83,7 @@ knot_dname_t *knot_dname_copy(const knot_dname_t *name);
  *
  * \return New domain name which is an partial copy of \a dname.
  */
-knot_dname_t *knot_dname_copy_part(const knot_dname_t *name, unsigned len);
+knot_dname_t *knot_dname_copy_part(const knot_dname_t *name, unsigned len, mm_ctx_t *mm);
 
 /*!
  * \brief Copy name to wire as is, no compression pointer expansion will be done.
@@ -236,7 +239,7 @@ knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name,
  *
  * \param name Domain name to be destroyed.
  */
-void knot_dname_free(knot_dname_t **name);
+void knot_dname_free(knot_dname_t **name, mm_ctx_t *mm);
 
 /*!
  * \brief Compares two domain names (case sensitive).
diff --git a/src/libknot/dnssec/key.c b/src/libknot/dnssec/key.c
index 07fe937907f731a22b030baafdfdde660cef85a0..45edbefe34f52b6744e117ece6093f92ceb4b1be 100644
--- a/src/libknot/dnssec/key.c
+++ b/src/libknot/dnssec/key.c
@@ -155,7 +155,7 @@ static int get_key_info_from_public_key(const char *filename,
 		return KNOT_KEY_EPUBLIC_KEY_INVALID;
 	}
 
-	knot_dname_t *owner = knot_dname_copy(scanner->r_owner);
+	knot_dname_t *owner = knot_dname_copy(scanner->r_owner, NULL);
 	if (!owner) {
 		zs_scanner_free(scanner);
 		return KNOT_ENOMEM;
@@ -167,7 +167,7 @@ static int get_key_info_from_public_key(const char *filename,
 	                                 &rdata_bin);
 	if (result != KNOT_EOK) {
 		zs_scanner_free(scanner);
-		knot_dname_free(&owner);
+		knot_dname_free(&owner, NULL);
 		return result;
 	}
 
@@ -385,7 +385,7 @@ int knot_load_key_params(const char *filename, knot_key_params_t *key_params)
 	if (!fp) {
 		free(public_key);
 		free(private_key);
-		knot_dname_free(&name);
+		knot_dname_free(&name, NULL);
 		return KNOT_KEY_EPRIVATE_KEY_OPEN;
 	}
 
@@ -425,7 +425,7 @@ int knot_copy_key_params(const knot_key_params_t *src, knot_key_params_t *dst)
 	int ret = 0;
 
 	if (src->name != NULL) {
-		dst->name = knot_dname_copy(src->name);
+		dst->name = knot_dname_copy(src->name, NULL);
 		if (dst->name == NULL) {
 			ret += -1;
 		}
@@ -470,7 +470,7 @@ int knot_free_key_params(knot_key_params_t *key_params)
 		return KNOT_EINVAL;
 	}
 
-	knot_dname_free(&key_params->name);
+	knot_dname_free(&key_params->name, NULL);
 	knot_binary_free(&key_params->rdata);
 
 	knot_binary_free(&key_params->secret);
@@ -541,7 +541,7 @@ int knot_tsig_create_key(const char *name, int algorithm,
 	knot_binary_t secret;
 	int result = knot_binary_from_base64(b64secret, &secret);
 	if (result != KNOT_EOK) {
-		knot_dname_free(&dname);
+		knot_dname_free(&dname, NULL);
 		return result;
 	}
 
@@ -567,7 +567,7 @@ int knot_tsig_key_from_params(const knot_key_params_t *params,
 		return result;
 	}
 
-	key->name = knot_dname_copy(params->name);
+	key->name = knot_dname_copy(params->name, NULL);
 
 	key->algorithm = params->algorithm;
 
@@ -583,7 +583,7 @@ int knot_tsig_key_free(knot_tsig_key_t *key)
 		return KNOT_EINVAL;
 	}
 
-	knot_dname_free(&key->name);
+	knot_dname_free(&key->name, NULL);
 	knot_binary_free(&key->secret);
 	memset(key, '\0', sizeof(knot_tsig_key_t));
 
diff --git a/src/libknot/dnssec/nsec3.c b/src/libknot/dnssec/nsec3.c
index 1f80486a112ade4a11532ebdb0a49d9ec99c17fc..f5c152ee0924513ec6812bda090a05b29042f7ab 100644
--- a/src/libknot/dnssec/nsec3.c
+++ b/src/libknot/dnssec/nsec3.c
@@ -108,23 +108,21 @@ static int nsec3_sha1(const uint8_t *salt, uint8_t salt_length,
  * \brief Initialize the structure with NSEC3 params from NSEC3PARAM RR set.
  */
 int knot_nsec3_params_from_wire(knot_nsec3_params_t *params,
-                                const knot_rrset_t *rrset)
+                                const knot_rrs_t *rrs)
 {
-	if (params == NULL || rrset == NULL || knot_rrset_rr_count(rrset) == 0) {
+	if (params == NULL || rrs == NULL || rrs->rr_count == 0) {
 		return KNOT_EINVAL;
 	}
 
-	assert(rrset->type == KNOT_RRTYPE_NSEC3PARAM);
-
 	knot_nsec3_params_t result = { 0 };
 
-	result.algorithm   = knot_rdata_nsec3param_algorithm(rrset, 0);
-	result.iterations  = knot_rdata_nsec3param_iterations(rrset, 0);
-	result.flags       = knot_rdata_nsec3param_flags(rrset, 0);
-	result.salt_length = knot_rdata_nsec3param_salt_length(rrset, 0);
+	result.algorithm   = knot_rrs_nsec3param_algorithm(rrs, 0);
+	result.iterations  = knot_rrs_nsec3param_iterations(rrs, 0);
+	result.flags       = knot_rrs_nsec3param_flags(rrs, 0);
+	result.salt_length = knot_rrs_nsec3param_salt_length(rrs, 0);
 
 	if (result.salt_length > 0) {
-		result.salt = knot_memdup(knot_rdata_nsec3param_salt(rrset, 0),
+		result.salt = knot_memdup(knot_rrs_nsec3param_salt(rrs, 0),
 		                          result.salt_length);
 		if (!result.salt) {
 			return KNOT_ENOMEM;
diff --git a/src/libknot/dnssec/nsec3.h b/src/libknot/dnssec/nsec3.h
index 01ed467e4797f20303220dd8a0d90c189dfd57ad..533adee112747aad5da053ab7b2b23145b60836c 100644
--- a/src/libknot/dnssec/nsec3.h
+++ b/src/libknot/dnssec/nsec3.h
@@ -37,7 +37,6 @@
 
 /*---------------------------------------------------------------------------*/
 
-
 /*!
  * \brief Get length of the raw NSEC3 hash.
  *
@@ -89,12 +88,12 @@ typedef struct {
  * \brief Initialize the structure with NSEC3 params from NSEC3PARAM RR set.
  *
  * \param params      Structure to initialize.
- * \param nsec3param  The NSEC3PARAM RR set.
+ * \param nsec3param  The NSEC3PARAM RRs.
  *
  * \return Error code, KNOT_EOK on success.
  */
 int knot_nsec3_params_from_wire(knot_nsec3_params_t *params,
-                                const knot_rrset_t *rrset);
+                                const knot_rrs_t *rrs);
 /*!
  * \brief Clean up structure with NSEC3 params (do not deallocate).
  *
diff --git a/src/libknot/dnssec/rrset-sign.c b/src/libknot/dnssec/rrset-sign.c
index 45a312d13efd0ea807a236866ab903249d45bfcc..737cfdf1b808c1cf38c2f9740a4313b7943d54d9 100644
--- a/src/libknot/dnssec/rrset-sign.c
+++ b/src/libknot/dnssec/rrset-sign.c
@@ -100,62 +100,6 @@ int knot_rrsig_write_rdata(uint8_t *rdata, const knot_dnssec_key_t *key,
 	return KNOT_EOK;
 }
 
-/*- Creating of RRSIGs from covered RRs -------------------------------------*/
-
-/*!
- * \brief Create RRSIG RDATA (all fields except signature are filled).
- *
- * \param[in]  rrsigs        RR set with RRSIGS.
- * \param[in]  covered       RR covered by the signature.
- * \param[in]  key           Key used for signing.
- * \param[in]  sig_incepted  Timestamp of signature inception.
- * \param[in]  sig_expires   Timestamp of signature expiration.
- * \param[out] rdata         Created RDATA.
- * \param[out] rdata_size    Size of created RDATA.
- *
- * \return Error code, KNOT_EOK if succesful.
- */
-static int rrsigs_create_rdata(knot_rrset_t *rrsigs,
-                               const knot_rrset_t *covered,
-                               const knot_dnssec_key_t *key,
-                               uint32_t sig_incepted, uint32_t sig_expires,
-                               uint8_t **rdata, size_t *rdata_size)
-{
-	assert(rrsigs);
-	assert(rrsigs->type == KNOT_RRTYPE_RRSIG);
-	assert(covered);
-	assert(key);
-	assert(rdata);
-	assert(rdata_size);
-
-	size_t size = knot_rrsig_rdata_size(key);
-	assert(size != 0);
-
-	uint8_t *result = knot_rrset_create_rr(rrsigs, size,
-	                                       knot_rrset_rr_ttl(covered, 0),
-	                                       NULL);
-	if (!result) {
-		return KNOT_ENOMEM;
-	}
-
-	uint8_t owner_labels = knot_dname_labels(covered->owner, NULL);
-	if (knot_dname_is_wildcard(covered->owner)) {
-		owner_labels -= 1;
-	}
-
-	int res = knot_rrsig_write_rdata(result, key, covered->type, owner_labels,
-	                                 knot_rrset_rr_ttl(covered, 0),
-	                                 sig_incepted, sig_expires);
-
-	assert(res == KNOT_EOK);
-	UNUSED(res);
-
-	*rdata = result;
-	*rdata_size = size;
-
-	return KNOT_EOK;
-}
-
 /*- Computation of signatures ------------------------------------------------*/
 
 /*!
@@ -242,6 +186,66 @@ static int sign_ctx_add_data(knot_dnssec_sign_context_t *ctx,
 	return sign_ctx_add_records(ctx, covered);
 }
 
+/*!
+ * \brief Create RRSIG RDATA (all fields except signature are filled).
+ *
+ * \param[in]  rrsigs        RR set with RRSIGS.
+ * \param[in]  rrsigs        DNSSEC signing context.
+ * \param[in]  covered       RR covered by the signature.
+ * \param[in]  key           Key used for signing.
+ * \param[in]  sig_incepted  Timestamp of signature inception.
+ * \param[in]  sig_expires   Timestamp of signature expiration.
+ *
+ * \return Error code, KNOT_EOK if succesful.
+ */
+static int rrsigs_create_rdata(knot_rrset_t *rrsigs,
+                               knot_dnssec_sign_context_t *context,
+                               const knot_rrset_t *covered,
+                               const knot_dnssec_key_t *key,
+                               uint32_t sig_incepted, uint32_t sig_expires)
+{
+	assert(rrsigs);
+	assert(rrsigs->type == KNOT_RRTYPE_RRSIG);
+	assert(!knot_rrset_empty(covered));
+	assert(key);
+
+	size_t size = knot_rrsig_rdata_size(key);
+	assert(size != 0);
+
+	uint8_t owner_labels = knot_dname_labels(covered->owner, NULL);
+	if (knot_dname_is_wildcard(covered->owner)) {
+		owner_labels -= 1;
+	}
+
+	uint8_t result[size];
+	int res = knot_rrsig_write_rdata(result, key, covered->type, owner_labels,
+	                                 knot_rrset_rr_ttl(covered, 0),
+	                                 sig_incepted, sig_expires);
+	assert(res == KNOT_EOK);
+
+	res = knot_dnssec_sign_new(context);
+	if (res != KNOT_EOK) {
+		return res;
+	}
+
+	res = sign_ctx_add_data(context, result, covered);
+	if (res != KNOT_EOK) {
+		return res;
+	}
+
+	const size_t signature_offset = RRSIG_RDATA_SIGNER_OFFSET + knot_dname_size(key->name);
+	uint8_t *signature = result + signature_offset;
+	const size_t signature_size = size - signature_offset;
+
+	res = knot_dnssec_sign_write(context, signature, signature_size);
+	if (res != KNOT_EOK) {
+		return res;
+	}
+
+	return knot_rrset_add_rr(rrsigs, result, size,
+	                         knot_rrset_rr_ttl(covered, 0), NULL);
+}
+
 /*!
  * \brief Create RRSIG RR for given RR set.
  */
@@ -250,7 +254,7 @@ int knot_sign_rrset(knot_rrset_t *rrsigs, const knot_rrset_t *covered,
                     knot_dnssec_sign_context_t *sign_ctx,
                     const knot_dnssec_policy_t *policy)
 {
-	if (!rrsigs || !covered || !key || !sign_ctx || !policy ||
+	if (knot_rrset_empty(covered) || !key || !sign_ctx || !policy ||
 	    rrsigs->type != KNOT_RRTYPE_RRSIG ||
 	    (knot_dname_cmp(rrsigs->owner, covered->owner) != 0)
 	) {
@@ -260,30 +264,33 @@ int knot_sign_rrset(knot_rrset_t *rrsigs, const knot_rrset_t *covered,
 	uint32_t sig_incept = policy->now;
 	uint32_t sig_expire = sig_incept + policy->sign_lifetime;
 
-	uint8_t *rdata = NULL;
-	size_t rdata_size = 0;
+	return rrsigs_create_rdata(rrsigs, sign_ctx, covered, key, sig_incept,
+	                           sig_expire);
+}
 
-	int result = rrsigs_create_rdata(rrsigs, covered, key, sig_incept,
-	                                 sig_expire, &rdata, &rdata_size);
-	if (result != KNOT_EOK) {
-		return result;
+int knot_synth_rrsig(uint16_t type, const knot_rrs_t *rrsig_rrs,
+                     knot_rrs_t *out_sig, mm_ctx_t *mm)
+{
+	if (rrsig_rrs == NULL) {
+		return KNOT_ENOENT;
 	}
 
-	result = knot_dnssec_sign_new(sign_ctx);
-	if (result != KNOT_EOK) {
-		return result;
+	if (out_sig == NULL || out_sig->rr_count > 0) {
+		return KNOT_EINVAL;
 	}
 
-	result = sign_ctx_add_data(sign_ctx, rdata, covered);
-	if (result != KNOT_EOK) {
-		return result;
+	for (int i = 0; i < rrsig_rrs->rr_count; ++i) {
+		if (type == knot_rrs_rrsig_type_covered(rrsig_rrs, i)) {
+			const knot_rr_t *rr_to_copy = knot_rrs_rr(rrsig_rrs, i);
+			int ret = knot_rrs_add_rr(out_sig, rr_to_copy, mm);
+			if (ret != KNOT_EOK) {
+				knot_rrs_clear(out_sig, mm);
+				return ret;
+			}
+		}
 	}
 
-	size_t signature_offset = RRSIG_RDATA_SIGNER_OFFSET + knot_dname_size(key->name);
-	uint8_t *signature = rdata + signature_offset;
-	size_t signature_size = rdata_size - signature_offset;
-
-	return knot_dnssec_sign_write(sign_ctx, signature, signature_size);
+	return out_sig->rr_count > 0 ? KNOT_EOK : KNOT_ENOENT;
 }
 
 /*- Verification of signatures -----------------------------------------------*/
@@ -300,11 +307,11 @@ int knot_sign_rrset(knot_rrset_t *rrsigs, const knot_rrset_t *covered,
 static bool is_expired_signature(const knot_rrset_t *rrsigs, size_t pos,
                                  const knot_dnssec_policy_t *policy)
 {
-	assert(rrsigs);
+	assert(!knot_rrset_empty(rrsigs));
 	assert(rrsigs->type == KNOT_RRTYPE_RRSIG);
 	assert(policy);
 
-	uint32_t expiration = knot_rdata_rrsig_sig_expiration(rrsigs, pos);
+	uint32_t expiration = knot_rrs_rrsig_sig_expiration(&rrsigs->rrs, pos);
 
 	return (expiration <= policy->refresh_before);
 }
@@ -318,7 +325,8 @@ int knot_is_valid_signature(const knot_rrset_t *covered,
                             knot_dnssec_sign_context_t *ctx,
                             const knot_dnssec_policy_t *policy)
 {
-	if (!covered || !rrsigs || !key || !ctx || !policy) {
+	if (knot_rrset_empty(covered) ||
+	    knot_rrset_empty(rrsigs) || !key || !ctx || !policy) {
 		return KNOT_EINVAL;
 	}
 
@@ -335,7 +343,7 @@ int knot_is_valid_signature(const knot_rrset_t *covered,
 
 	uint8_t *signature = NULL;
 	size_t signature_size = 0;
-	knot_rdata_rrsig_signature(rrsigs, pos, &signature, &signature_size);
+	knot_rrs_rrsig_signature(&rrsigs->rrs, pos, &signature, &signature_size);
 	if (!signature) {
 		return KNOT_EINVAL;
 	}
@@ -354,3 +362,4 @@ int knot_is_valid_signature(const knot_rrset_t *covered,
 
 	return knot_dnssec_sign_verify(ctx, signature, signature_size);
 }
+
diff --git a/src/libknot/dnssec/rrset-sign.h b/src/libknot/dnssec/rrset-sign.h
index 82867e80d2783c4a4670e7f68dbd38bf06c66125..585883d0fd4e9c214b1692d6a6bdbfea44759d53 100644
--- a/src/libknot/dnssec/rrset-sign.h
+++ b/src/libknot/dnssec/rrset-sign.h
@@ -77,6 +77,22 @@ int knot_sign_rrset(knot_rrset_t *rrsigs,
                     knot_dnssec_sign_context_t *sign_ctx,
                     const knot_dnssec_policy_t *policy);
 
+/*!
+ * \brief Creates new RRS using \a rrsig_rrs as a source. Only those RRs that
+ *        cover given \a type are copied into \a out_sig
+ *
+ * \param type       Covered type.
+ * \param rrsig_rrs  Source RRS.
+ * \param out_sig    Output RRS.
+ * \param mm         Memory context.
+ *
+ * \retval KNOT_EOK if some RRSIG was found.
+ * \retval KNOT_EINVAL if no RRSIGs were found.
+ * \retval Error code other than EINVAL on error.
+ */
+int knot_synth_rrsig(uint16_t type, const knot_rrs_t *rrsig_rrs,
+                     knot_rrs_t *out_sig, mm_ctx_t *mm);
+
 /*!
  * \brief Check if RRSIG signature is valid.
  *
diff --git a/src/libknot/dnssec/sig0.c b/src/libknot/dnssec/sig0.c
index ff4de0d1dc150dfe77bda3b1479eecb60ee6514f..48ba754f408c9eca193e3d6b79a760929555bcb7 100644
--- a/src/libknot/dnssec/sig0.c
+++ b/src/libknot/dnssec/sig0.c
@@ -38,11 +38,7 @@
  */
 static knot_rrset_t *sig0_create_rrset(void)
 {
-	knot_dname_t *root = knot_dname_from_str(".");
-	knot_rrset_t *sig_record = knot_rrset_new(root, KNOT_RRTYPE_SIG,
-	                                          KNOT_CLASS_ANY, NULL);
-
-	return sig_record;
+	return knot_rrset_new((uint8_t *)"", KNOT_RRTYPE_SIG, KNOT_CLASS_ANY, NULL);
 }
 
 static void sig0_write_rdata(uint8_t *rdata, const knot_dnssec_key_t *key)
@@ -71,15 +67,14 @@ static uint8_t *sig0_create_rdata(knot_rrset_t *rrset, knot_dnssec_key_t *key)
 	assert(key);
 
 	size_t rdata_size = knot_rrsig_rdata_size(key);
-	uint32_t ttl = 0;
-	uint8_t *rdata = knot_rrset_create_rr(rrset, rdata_size, ttl, NULL);
-	if (!rdata) {
+	const uint32_t ttl = 0;
+	uint8_t rdata[rdata_size];
+	sig0_write_rdata(rdata, key);
+	if (knot_rrset_add_rr(rrset, rdata, rdata_size, ttl, NULL) != KNOT_EOK) {
 		return NULL;
 	}
 
-	sig0_write_rdata(rdata, key);
-
-	return rdata;
+	return knot_rrset_rr_rdata(rrset, 0);
 }
 
 /*!
diff --git a/src/libknot/dnssec/sign.c b/src/libknot/dnssec/sign.c
index 409a1d7cd81c26704c2e9286ee06e02247f9af0f..2fe724d571ee3d82d1b578dae7445b6c870e14ac 100644
--- a/src/libknot/dnssec/sign.c
+++ b/src/libknot/dnssec/sign.c
@@ -1116,7 +1116,7 @@ int knot_dnssec_key_from_params(const knot_key_params_t *params,
 		return KNOT_EINVAL;
 	}
 
-	knot_dname_t *name = knot_dname_copy(params->name);
+	knot_dname_t *name = knot_dname_copy(params->name, NULL);
 	if (!name) {
 		return KNOT_ENOMEM;
 	}
@@ -1124,21 +1124,21 @@ int knot_dnssec_key_from_params(const knot_key_params_t *params,
 	knot_dnssec_key_data_t *data;
 	data = calloc(1, sizeof(knot_dnssec_key_data_t));
 	if (!data) {
-		knot_dname_free(&name);
+		knot_dname_free(&name, NULL);
 		return KNOT_ENOMEM;
 	}
 
 	knot_binary_t rdata_copy = { 0 };
 	int result = knot_binary_dup(&params->rdata, &rdata_copy);
 	if (result != KNOT_EOK) {
-		knot_dname_free(&name);
+		knot_dname_free(&name, NULL);
 		free(data);
 		return result;
 	}
 
 	result = init_algorithm_data(params, data);
 	if (result != KNOT_EOK) {
-		knot_dname_free(&name);
+		knot_dname_free(&name, NULL);
 		free(data);
 		knot_binary_free(&rdata_copy);
 		return result;
@@ -1162,7 +1162,7 @@ int knot_dnssec_key_free(knot_dnssec_key_t *key)
 		return KNOT_EINVAL;
 	}
 
-	knot_dname_free(&key->name);
+	knot_dname_free(&key->name, NULL);
 
 	if (key->data) {
 		clean_algorithm_data(key->data);
diff --git a/src/libknot/edns.c b/src/libknot/edns.c
index ce5be31053f36aa458a4b253f662426c117c21be..b9d4eb744af05fbf81fb1c26cbb992845dbae298 100644
--- a/src/libknot/edns.c
+++ b/src/libknot/edns.c
@@ -54,13 +54,13 @@ knot_opt_rr_t *knot_edns_new()
 int knot_edns_new_from_rr(knot_opt_rr_t *opt_rr, const knot_rrset_t *rrset)
 {
 	if (opt_rr == NULL || rrset == NULL
-	    || knot_rrset_type(rrset) != KNOT_RRTYPE_OPT ||
+	    || rrset->type != KNOT_RRTYPE_OPT ||
 	    knot_rrset_rr_count(rrset) == 0) {
 		return KNOT_EINVAL;
 	}
 
 	dbg_edns_verb("Parsing payload.\n");
-	opt_rr->payload = knot_rrset_class(rrset);
+	opt_rr->payload = rrset->rclass;
 
 	/* RFC6891, 6.2.5 Value < 512B should be treated as 512. */
 	if (opt_rr->payload < EDNS_MIN_UDP_PAYLOAD) {
@@ -118,7 +118,6 @@ int knot_edns_new_from_rr(knot_opt_rr_t *opt_rr, const knot_rrset_t *rrset)
 		}
 	}
 
-
 	dbg_edns_verb("EDNS created.\n");
 
 	return KNOT_EOK;
diff --git a/src/libknot/packet/pkt.c b/src/libknot/packet/pkt.c
index c204b02d9222d7fc8b7c5c87193b2ea21390451e..82bb1905995e277d5d4453555aefa9b8e8149631 100644
--- a/src/libknot/packet/pkt.c
+++ b/src/libknot/packet/pkt.c
@@ -28,14 +28,15 @@
 
 /*! \brief Scan packet for RRSet existence. */
 static bool pkt_contains(const knot_pkt_t *packet,
-			 const knot_rrset_t *rrset,
-			 knot_rrset_compare_type_t cmp)
+			 const knot_rrset_t *rrset)
 {
 	assert(packet);
 	assert(rrset);
 
 	for (int i = 0; i < packet->rrset_count; ++i) {
-		if (knot_rrset_equal(packet->rr[i], rrset, cmp)) {
+		const uint16_t type = packet->rr[i].type;
+		const uint8_t *data = packet->rr[i].rrs.data;
+		if (type == rrset->type && data == rrset->rrs.data) {
 			return true;
 		}
 	}
@@ -43,18 +44,15 @@ static bool pkt_contains(const knot_pkt_t *packet,
 	return false;
 }
 
-
 /*! \brief Free all RRSets and reset RRSet count. */
 static void pkt_free_data(knot_pkt_t *pkt)
 {
 	assert(pkt);
 
 	/* Free RRSets if applicable. */
-	knot_rrset_t *rr  = NULL;
 	for (uint16_t i = 0; i < pkt->rrset_count; ++i) {
 		if (pkt->rr_info[i].flags & KNOT_PF_FREE) {
-			rr = (knot_rrset_t *)pkt->rr[i];
-			knot_rrset_free(&rr, &pkt->mm);
+			knot_rrset_clear(&pkt->rr[i], &pkt->mm);
 		}
 	}
 
@@ -506,11 +504,11 @@ int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr, u
 	rrinfo->pos = pkt->size;
 	rrinfo->flags = flags;
 	rrinfo->compress_ptr[0] = compr_hint;
-	pkt->rr[pkt->rrset_count] = rr;
+	pkt->rr[pkt->rrset_count] = *rr;
 
 	/* Check for double insertion. */
 	if ((flags & KNOT_PF_CHECKDUP) &&
-	    pkt_contains(pkt, rr, KNOT_RRSET_COMPARE_PTR)) {
+	    pkt_contains(pkt, rr)) {
 		return KNOT_EOK; /*! \todo return rather a number of added RRs */
 	}
 
@@ -585,7 +583,7 @@ int knot_pkt_parse_question(knot_pkt_t *pkt)
 	if (pkt == NULL) {
 		return KNOT_EINVAL;
 	}
-	
+
 	/* Check at least header size. */
 	if (pkt->size < KNOT_WIRE_HEADER_SIZE) {
 		dbg_packet("%s: smaller than DNS header, NOREPLY\n", __func__);
@@ -615,7 +613,7 @@ int knot_pkt_parse_question(knot_pkt_t *pkt)
 	if (len <= 0) {
 		return KNOT_EMALF;
 	}
-	
+
 	/* Check QCLASS/QTYPE size. */
 	uint16_t question_size = len + 2 * sizeof(uint16_t); /* QCLASS + QTYPE */
 	if (pkt->parsed + question_size > pkt->size) {
@@ -623,7 +621,7 @@ int knot_pkt_parse_question(knot_pkt_t *pkt)
 		return KNOT_EMALF;
 	}
 
-	pkt->parsed += question_size; 
+	pkt->parsed += question_size;
 	pkt->qname_size = len;
 
 	return KNOT_EOK;
@@ -633,23 +631,23 @@ int knot_pkt_parse_question(knot_pkt_t *pkt)
  *        RRSets should use packet memory context for allocation and
  *        should be copied if they are supposed to be stored in zone permanently.
  */
-static knot_rrset_t *knot_pkt_rr_from_wire(const uint8_t *wire, size_t *pos,
-                                           size_t size, mm_ctx_t *mm)
+static int knot_pkt_rr_from_wire(const uint8_t *wire, size_t *pos,
+                                 size_t size, mm_ctx_t *mm, knot_rrset_t *rrset)
 {
 	dbg_packet("%s(%p, %zu, %zu)\n", __func__, wire, *pos, size);
 	assert(wire);
 	assert(pos);
 
-	knot_dname_t *owner = knot_dname_parse(wire, pos, size);
+	knot_dname_t *owner = knot_dname_parse(wire, pos, size, mm);
 	if (owner == NULL) {
-		return NULL;
+		return KNOT_EMALF;
 	}
 	knot_dname_to_lower(owner);
 
 	if (size - *pos < KNOT_RR_HEADER_SIZE) {
 		dbg_packet("%s: not enough data to parse RR HEADER\n", __func__);
-		knot_dname_free(&owner);
-		return NULL;
+		knot_dname_free(&owner, mm);
+		return KNOT_EMALF;
 	}
 
 	uint16_t type = knot_wire_read_u16(wire + *pos);
@@ -657,11 +655,6 @@ static knot_rrset_t *knot_pkt_rr_from_wire(const uint8_t *wire, size_t *pos,
 	uint32_t ttl = knot_wire_read_u32(wire + *pos + 2 * sizeof(uint16_t));
 	uint16_t rdlength = knot_wire_read_u16(wire + *pos + 4 * sizeof(uint16_t));
 
-	knot_rrset_t *rrset = knot_rrset_new(owner, type, rclass, mm);
-	if (rrset == NULL) {
-		knot_dname_free(&owner);
-		return NULL;
-	}
 	*pos += KNOT_RR_HEADER_SIZE;
 
 	dbg_packet_verb("%s: read type %u, class %u, ttl %u, rdlength %u\n",
@@ -669,22 +662,20 @@ static knot_rrset_t *knot_pkt_rr_from_wire(const uint8_t *wire, size_t *pos,
 
 	if (size - *pos < rdlength) {
 		dbg_packet("%s: not enough data to parse RDATA\n", __func__);
-		knot_rrset_free(&rrset, mm);
-		return NULL;
+		knot_dname_free(&owner, mm);
+		return KNOT_EMALF;
 	}
 
-	// parse RDATA
-	/*! \todo Merge with add_rdata_to_rr in zcompile, should be a rrset func
-	 *        probably. */
+	knot_rrset_init(rrset, owner, type, rclass);
 	int ret = knot_rrset_rdata_from_wire_one(rrset, wire, pos, size, ttl,
 	                                         rdlength, mm);
 	if (ret != KNOT_EOK) {
 		dbg_packet("%s: couldn't parse RDATA (%d)\n", __func__, ret);
-		knot_rrset_free(&rrset, mm);
-		return NULL;
+		knot_rrset_clear(rrset, mm);
+		return ret;
 	}
 
-	return rrset;
+	return KNOT_EOK;
 }
 
 int knot_pkt_parse_rr(knot_pkt_t *pkt, unsigned flags)
@@ -707,26 +698,25 @@ int knot_pkt_parse_rr(knot_pkt_t *pkt, unsigned flags)
 
 	/* Parse wire format. */
 	size_t rr_size = pkt->parsed;
-	knot_rrset_t *rr = NULL;
-	rr = knot_pkt_rr_from_wire(pkt->wire, &pkt->parsed, pkt->max_size,
-	                           &pkt->mm);
-	if (rr == NULL) {
+	knot_rrset_t *rr = &pkt->rr[pkt->rrset_count];
+	ret = knot_pkt_rr_from_wire(pkt->wire, &pkt->parsed, pkt->max_size,
+	                           &pkt->mm, rr);
+	if (ret != KNOT_EOK) {
 		dbg_packet("%s: failed to parse RR\n", __func__);
-		return KNOT_EMALF;
+		return ret;
 	}
-	
+
 	/* Calculate parsed RR size from before/after parsing. */
 	rr_size = (pkt->parsed - rr_size);
 
-	/* Append to RR list. */
-	pkt->rr[pkt->rrset_count] = rr;
+	/* Update packet RRSet count. */
 	++pkt->rrset_count;
 
 	/* Update section RRSet count. */
 	++pkt->sections[pkt->current].count;
 
 	/* Check RR constraints. */
-	switch(knot_rrset_type(rr)) {
+	switch(rr->type) {
 	case KNOT_RRTYPE_TSIG:
 		// if there is some TSIG already, treat as malformed
 		if (pkt->tsig_rr != NULL) {
@@ -814,7 +804,7 @@ int knot_pkt_parse_payload(knot_pkt_t *pkt, unsigned flags)
 	/* TSIG must be last record of AR if present. */
 	const knot_pktsection_t *ar = knot_pkt_section(pkt, KNOT_ADDITIONAL);
 	if (pkt->tsig_rr != NULL) {
-		if (ar->count > 0 && pkt->tsig_rr != ar->rr[ar->count - 1]) {
+		if (ar->count > 0 && pkt->tsig_rr->rrs.data != ar->rr[ar->count - 1].rrs.data) {
 			dbg_packet("%s: TSIG not last RR in AR.\n", __func__);
 			return KNOT_EMALF;
 		}
diff --git a/src/libknot/packet/pkt.h b/src/libknot/packet/pkt.h
index de9cc2ff126530ef2c26ced5c8c80011c0b84b4b..b5aefa0de3694a57af51efdaa537adaeba978698 100644
--- a/src/libknot/packet/pkt.h
+++ b/src/libknot/packet/pkt.h
@@ -80,9 +80,9 @@ enum {
  * This structure is required for random access to packet sections.
  */
 typedef struct {
-	const knot_rrset_t **rr;     /*!< Array of RRSets for this section. */
-	knot_rrinfo_t *rrinfo;       /*!< Compression info for each RRSet. */
-	uint16_t count;              /*!< Number of RRSets in this section. */
+	const knot_rrset_t *rr;     /*!< Array of RRSets for this section. */
+	knot_rrinfo_t *rrinfo;      /*!< Compression info for each RRSet. */
+	uint16_t count;             /*!< Number of RRSets in this section. */
 } knot_pktsection_t;
 
 /*!
@@ -106,13 +106,13 @@ typedef struct knot_pkt {
 	/* Packet sections. */
 	knot_section_t current;
 	knot_pktsection_t sections[KNOT_PKT_SECTIONS];
-	
+
 	/*! \note <== Memory below this point is not cleared on init for performance reasons. */
-	
+
 	/* Packet RRSet (meta)data. */
 	knot_rrinfo_t rr_info[KNOT_PKT_MAX_RRS];
-	const knot_rrset_t *rr[KNOT_PKT_MAX_RRS];
-	
+	knot_rrset_t rr[KNOT_PKT_MAX_RRS];
+
 	mm_ctx_t mm; /*!< Memory allocation context. */
 } knot_pkt_t;
 
@@ -154,7 +154,6 @@ void knot_pkt_free(knot_pkt_t **pkt);
  */
 int knot_pkt_reserve(knot_pkt_t *pkt, uint16_t size);
 
-
 /*! \brief Classify packet according to the question.
  *  \return see enum knot_pkt_type_t
  */
@@ -174,7 +173,7 @@ uint16_t knot_pkt_qclass(const knot_pkt_t *pkt);
 
 /*!
  * \brief Begin reading/writing packet section.
- * 
+ *
  * \note You must proceed in the natural order (ANSWER, AUTHORITY, ADDITIONAL).
  *
  * \param pkt
@@ -211,7 +210,7 @@ int knot_pkt_opt_set(knot_pkt_t *pkt, unsigned opt, const void *data, uint16_t l
  */
 int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname, uint16_t qclass, uint16_t qtype);
 
-/*! \brief Write OPT RR to wireformat. 
+/*! \brief Write OPT RR to wireformat.
  *  \note Legacy API.
  */
 int knot_pkt_put_opt(knot_pkt_t *pkt);
@@ -333,7 +332,6 @@ int knot_pkt_add_opt(knot_pkt_t *resp,
 /*----------------------------------------------------------------------------*/
 /*** >>> #190 DEPRECATED */
 
-
 #endif /* _KNOT_PACKET_H_ */
 
 /*! @} */
diff --git a/src/libknot/processing/process.h b/src/libknot/processing/process.h
index 8be678a11284389ebf7e746a611b889384999cbf..a6bc1060ea087a4885b9140e8a05251310f4fe37 100644
--- a/src/libknot/processing/process.h
+++ b/src/libknot/processing/process.h
@@ -36,6 +36,7 @@
  *         Each state describes the current machine processing step
  *         and determines readiness for next action.
  */
+
 enum knot_process_state {
 	NS_PROC_NOOP = 0,      /* N/A */
 	NS_PROC_MORE = 1 << 0, /* More input data. */
diff --git a/src/libknot/rdata.h b/src/libknot/rdata.h
index 3af16ef6aa7ef5eea6d9f3417f0d22587fbc2218..fc7f9a0431568330984fb9ac3013b594f66db9c2 100644
--- a/src/libknot/rdata.h
+++ b/src/libknot/rdata.h
@@ -29,228 +29,183 @@
 
 #include "common/descriptor.h"
 #include "libknot/dname.h"
-#include "libknot/rrset.h"
+#include "libknot/rr.h"
 #include "libknot/util/utils.h"
 
 #define KNOT_RDATA_DNSKEY_FLAG_KSK 1
 
-static inline
-const knot_dname_t *knot_rdata_cname_name(const knot_rrset_t *rrset)
-{
-	if (rrset == NULL) {
-		return NULL;
+#define RRS_CHECK(rrs, pos, code) \
+	if (rrs == NULL || rrs->data == NULL || rrs->rr_count == 0 || \
+	    pos >= rrs->rr_count) { \
+		code; \
 	}
 
-	return knot_rrset_rr_rdata(rrset, 0);
+static inline uint8_t *data_offset(const knot_rrs_t *rrs, size_t pos,
+                                   size_t offset) {
+	knot_rr_t *rr = knot_rrs_rr(rrs, pos);
+	return knot_rr_rdata(rr) + offset;
 }
 
 static inline
-const knot_dname_t *knot_rdata_dname_target(const knot_rrset_t *rrset)
+const knot_dname_t *knot_rrs_cname_name(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, 0);
+	RRS_CHECK(rrs, 0, return NULL);
+	return data_offset(rrs, 0, 0);
 }
 
 static inline
-const knot_dname_t *knot_rdata_soa_primary_ns(const knot_rrset_t *rrset)
+const knot_dname_t *knot_rrs_dname_target(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, 0);
+	RRS_CHECK(rrs, 0, return NULL);
+	return data_offset(rrs, 0, 0);
 }
 
 static inline
-const knot_dname_t *knot_rdata_soa_mailbox(const knot_rrset_t *rrset)
+const knot_dname_t *knot_rrs_soa_primary_ns(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) == 0) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, 0) +
-	       knot_dname_size(knot_rrset_rr_rdata(rrset, 0));
+	RRS_CHECK(rrs, 0, return NULL);
+	return data_offset(rrs, 0, 0);
 }
 
 static inline
-size_t knot_rrset_rdata_soa_names_len(const knot_rrset_t *rrset)
+const knot_dname_t *knot_rrs_soa_mailbox(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return 0;
-	}
-
-	return knot_dname_size(knot_rdata_soa_primary_ns(rrset))
-	       + knot_dname_size(knot_rdata_soa_mailbox(rrset));
+	RRS_CHECK(rrs, 0, return NULL);
+	return data_offset(rrs, 0, knot_dname_size(knot_rrs_soa_primary_ns(rrs)));
 }
 
 static inline
-uint32_t knot_rdata_soa_serial(const knot_rrset_t *rrset)
+size_t knot_rrs_soa_names_len(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return 0;
-	}
-
-	return knot_wire_read_u32(knot_rrset_rr_rdata(rrset, 0)
-	                          + knot_rrset_rdata_soa_names_len(rrset));
+	RRS_CHECK(rrs, 0, return 0);
+	return knot_dname_size(knot_rrs_soa_primary_ns(rrs))
+	       + knot_dname_size(knot_rrs_soa_mailbox(rrs));
 }
 
 static inline
-void knot_rdata_soa_serial_set(knot_rrset_t *rrset, uint32_t serial)
+uint32_t knot_rrs_soa_serial(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return;
-	}
+	RRS_CHECK(rrs, 0, return 0);
+	return knot_wire_read_u32(data_offset(rrs, 0,
+	                                      knot_rrs_soa_names_len(rrs)));
+}
 
+static inline
+void knot_rrs_soa_serial_set(knot_rrs_t *rrs, uint32_t serial)
+{
+	RRS_CHECK(rrs, 0, return);
 	// the number is in network byte order, transform it
-	knot_wire_write_u32(knot_rrset_rr_rdata(rrset, 0)
-	                    + knot_rrset_rdata_soa_names_len(rrset), serial);
+	knot_wire_write_u32(data_offset(rrs, 0, knot_rrs_soa_names_len(rrs)),
+	                    serial);
 }
 
 static inline
-uint32_t knot_rdata_soa_refresh(const knot_rrset_t *rrset)
+uint32_t knot_rrs_soa_refresh(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return 0;
-	}
-
-	return knot_wire_read_u32(knot_rrset_rr_rdata(rrset, 0)
-	                          + knot_rrset_rdata_soa_names_len(rrset) + 4);
+	RRS_CHECK(rrs, 0, return 0);
+	return knot_wire_read_u32(data_offset(rrs, 0,
+	                                      knot_rrs_soa_names_len(rrs) + 4));
 }
 
 static inline
-uint32_t knot_rdata_soa_retry(const knot_rrset_t *rrset)
+uint32_t knot_rrs_soa_retry(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return 0;
-	}
-
-	return knot_wire_read_u32(knot_rrset_rr_rdata(rrset, 0)
-	                          + knot_rrset_rdata_soa_names_len(rrset) + 8);
+	RRS_CHECK(rrs, 0, return 0);
+	return knot_wire_read_u32(data_offset(rrs, 0,
+	                                      knot_rrs_soa_names_len(rrs) + 8));
 }
 
 static inline
-uint32_t knot_rdata_soa_expire(const knot_rrset_t *rrset)
+uint32_t knot_rrs_soa_expire(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return 0;
-	}
-
-	return knot_wire_read_u32(knot_rrset_rr_rdata(rrset, 0)
-	                          + knot_rrset_rdata_soa_names_len(rrset) + 12);
+	RRS_CHECK(rrs, 0, return 0);
+	return knot_wire_read_u32(data_offset(rrs, 0,
+	                                      knot_rrs_soa_names_len(rrs) + 12));
 }
 
 static inline
-uint32_t knot_rdata_soa_minimum(const knot_rrset_t *rrset)
+uint32_t knot_rrs_soa_minimum(const knot_rrs_t *rrs)
 {
-	if (rrset == NULL) {
-		return 0;
-	}
-
-	return knot_wire_read_u32(knot_rrset_rr_rdata(rrset, 0)
-	                          + knot_rrset_rdata_soa_names_len(rrset) + 16);
+	RRS_CHECK(rrs, 0, return 0);
+	return knot_wire_read_u32(data_offset(rrs, 0,
+	                                      knot_rrs_soa_names_len(rrs) + 16));
 }
 
 static inline
-uint16_t knot_rdata_rrsig_type_covered(const knot_rrset_t *rrset, size_t pos)
+uint16_t knot_rrs_rrsig_type_covered(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return knot_wire_read_u16(knot_rrset_rr_rdata(rrset, pos));
+	RRS_CHECK(rrs, pos, return 0);
+	return knot_wire_read_u16(data_offset(rrs, pos, 0));
 }
 
 static inline
-uint8_t knot_rdata_rrsig_algorithm(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_rrsig_algorithm(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos) + 2);
+	RRS_CHECK(rrs, pos, return 0);
+	return *data_offset(rrs, pos, 2);
 }
 
 static inline
-uint8_t knot_rdata_rrsig_labels(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_rrsig_labels(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos) + 3);
+	RRS_CHECK(rrs, pos, return 0);
+	return *data_offset(rrs, pos, 3);
 }
 
 static inline
-uint32_t knot_rdata_rrsig_original_ttl(const knot_rrset_t *rrset, size_t pos)
+uint32_t knot_rrs_rrsig_original_ttl(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return knot_wire_read_u32(knot_rrset_rr_rdata(rrset, pos) + 4);
+	RRS_CHECK(rrs, pos, return 0);
+	return knot_wire_read_u32(data_offset(rrs, pos, 4));
 }
 
 static inline
-uint32_t knot_rdata_rrsig_sig_expiration(const knot_rrset_t *rrset, size_t pos)
+uint32_t knot_rrs_rrsig_sig_expiration(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return knot_wire_read_u32(knot_rrset_rr_rdata(rrset, pos) + 8);
+	RRS_CHECK(rrs, pos, return 0);
+	return knot_wire_read_u32(data_offset(rrs, pos, 8));
 }
 
 static inline
-uint32_t knot_rdata_rrsig_sig_inception(const knot_rrset_t *rrset, size_t pos)
+uint32_t knot_rrs_rrsig_sig_inception(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return knot_wire_read_u32(knot_rrset_rr_rdata(rrset, pos) + 12);
+	RRS_CHECK(rrs, pos, return 0);
+	return knot_wire_read_u32(data_offset(rrs, pos, 12));
 }
 
 static inline
-uint16_t knot_rdata_rrsig_key_tag(const knot_rrset_t *rrset, size_t pos)
+uint16_t knot_rrs_rrsig_key_tag(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return knot_wire_read_u16(knot_rrset_rr_rdata(rrset, pos) + 16);
+	RRS_CHECK(rrs, pos, return 0);
+	return knot_wire_read_u16(data_offset(rrs, pos, 16));
 }
 
 static inline
-const knot_dname_t *knot_rdata_rrsig_signer_name(const knot_rrset_t *rrset,
+const knot_dname_t *knot_rrs_rrsig_signer_name(const knot_rrs_t *rrs,
                                                  size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, pos) + 18;
+	RRS_CHECK(rrs, pos, return 0);
+	return data_offset(rrs, pos, 18);
 }
 
 static inline
-void knot_rdata_rrsig_signature(const knot_rrset_t *rrset, size_t pos,
+void knot_rrs_rrsig_signature(const knot_rrs_t *rrs, size_t pos,
                                 uint8_t **signature, size_t *signature_size)
 {
 	if (!signature || !signature_size) {
 		return;
 	}
 
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
+	if (rrs == NULL || pos >= rrs->rr_count) {
 		*signature = NULL;
 		*signature_size = 0;
 		return;
 	}
 
-	uint8_t *rdata = knot_rrset_rr_rdata(rrset, pos);
+	uint8_t *rdata = data_offset(rrs, pos, 0);
 	uint8_t *signer = rdata + 18;
-	size_t total_size = knot_rrset_rr_size(rrset, pos);
+	const knot_rr_t *rr = knot_rrs_rr(rrs, pos);
+	size_t total_size = knot_rr_rdata_size(rr);
 	size_t header_size = 18 + knot_dname_size(signer);
 
 	*signature = rdata + header_size;
@@ -258,245 +213,190 @@ void knot_rdata_rrsig_signature(const knot_rrset_t *rrset, size_t pos,
 }
 
 static inline
-uint16_t knot_rdata_dnskey_flags(const knot_rrset_t *rrset, size_t pos)
+uint16_t knot_rrs_dnskey_flags(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return knot_wire_read_u16(knot_rrset_rr_rdata(rrset, pos));
+	RRS_CHECK(rrs, pos, return 0);
+	return knot_wire_read_u16(data_offset(rrs, pos, 0));
 }
 
 static inline
-uint8_t knot_rdata_dnskey_proto(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_dnskey_proto(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
+	RRS_CHECK(rrs, pos, return 0);
 
-	return *(knot_rrset_rr_rdata(rrset, pos) + 2);
+	return *data_offset(rrs, pos, 2);
 }
 
 static inline
-uint8_t knot_rdata_dnskey_alg(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_dnskey_alg(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos) + 3);
+	RRS_CHECK(rrs, pos, return 0);
+	return *data_offset(rrs, pos, 3);
 }
 
 static inline
-void knot_rdata_dnskey_key(const knot_rrset_t *rrset, size_t pos, uint8_t **key,
+void knot_rrs_dnskey_key(const knot_rrs_t *rrs, size_t pos, uint8_t **key,
                            uint16_t *key_size)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return;
-	}
-
-	*key = knot_rrset_rr_rdata(rrset, pos) + 4;
-	*key_size = knot_rrset_rr_size(rrset, pos) - 4;
+	RRS_CHECK(rrs, pos, return);
+	*key = data_offset(rrs, pos, 4);
+	const knot_rr_t *rr = knot_rrs_rr(rrs, pos);
+	*key_size = knot_rr_rdata_size(rr) - 4;
 }
 
 static inline
-const knot_dname_t *knot_rdata_nsec_next(const knot_rrset_t *rrset)
+const knot_dname_t *knot_rrs_nsec_next(const knot_rrs_t *rrs)
 {
-	return knot_rrset_rr_rdata(rrset, 0);
+	RRS_CHECK(rrs, 0, return NULL);
+	return data_offset(rrs, 0, 0);
 }
 
 static inline
-void knot_rdata_nsec_bitmap(const knot_rrset_t *rrset,
+void knot_rrs_nsec_bitmap(const knot_rrs_t *rrs,
                             uint8_t **bitmap, uint16_t *size)
 {
-	uint8_t *rdata = knot_rrset_rr_rdata(rrset, 0);
-	int next_size = knot_dname_size(rdata);
+	RRS_CHECK(rrs, 0, return);
+	knot_rr_t *rr = knot_rrs_rr(rrs, 0);
+	int next_size = knot_dname_size(knot_rrs_nsec_next(rrs));
 
-	*bitmap = rdata + next_size;
-	*size = knot_rrset_rr_size(rrset, 0) - next_size;
+	*bitmap = knot_rr_rdata(rr) + next_size;
+	*size = knot_rr_rdata_size(rr) - next_size;
 }
 
 static inline
-uint8_t knot_rdata_nsec3_algorithm(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_nsec3_algorithm(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos));
+	RRS_CHECK(rrs, pos, return 0);
+	return *data_offset(rrs, pos, 0);
 }
 
 static inline
-uint8_t knot_rdata_nsec3_flags(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_nsec3_flags(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos) + 1);
+	RRS_CHECK(rrs, pos, return 0);
+	return *data_offset(rrs, pos, 1);
 }
 
 static inline
-uint16_t knot_rdata_nsec3_iterations(const knot_rrset_t *rrset, size_t pos)
+uint16_t knot_rrs_nsec3_iterations(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return knot_wire_read_u16(knot_rrset_rr_rdata(rrset, pos) + 2);
+	RRS_CHECK(rrs, pos, return 0);
+	return knot_wire_read_u16(data_offset(rrs, pos, 2));
 }
 
 static inline
-uint8_t knot_rdata_nsec3_salt_length(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_nsec3_salt_length(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos) + 4);
+	RRS_CHECK(rrs, pos, return 0);
+	return *(data_offset(rrs, pos, 0) + 4);
 }
 
 static inline
-const uint8_t *knot_rdata_nsec3_salt(const knot_rrset_t *rrset, size_t pos)
+const uint8_t *knot_rrs_nsec3_salt(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, pos) + 5;
+	RRS_CHECK(rrs, pos, return NULL);
+	return data_offset(rrs, pos, 5);
 }
 
 static inline
-void knot_rdata_nsec3_next_hashed(const knot_rrset_t *rrset, size_t pos,
+void knot_rrs_nsec3_next_hashed(const knot_rrs_t *rrs, size_t pos,
                                   uint8_t **name, uint8_t *name_size)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return;
-	}
-
-	uint8_t salt_size = knot_rdata_nsec3_salt_length(rrset, pos);
-	*name_size = *(knot_rrset_rr_rdata(rrset, pos) + 4 + salt_size + 1);
-	*name = knot_rrset_rr_rdata(rrset, pos) + 4 + salt_size + 2;
+	RRS_CHECK(rrs, pos, return);
+	uint8_t salt_size = knot_rrs_nsec3_salt_length(rrs, pos);
+	*name_size = *data_offset(rrs, pos, 4 + salt_size + 1);
+	*name = data_offset(rrs, pos, 4 + salt_size + 2);
 }
 
 static inline
-void knot_rdata_nsec3_bitmap(const knot_rrset_t *rrset, size_t pos,
+void knot_rrs_nsec3_bitmap(const knot_rrs_t *rrs, size_t pos,
                              uint8_t **bitmap, uint16_t *size)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return;
-	}
+	RRS_CHECK(rrs, pos, return);
 
 	/* Bitmap is last, skip all the items. */
 	size_t offset = 6; //hash alg., flags, iterations, salt len., hash len.
-	offset += knot_rdata_nsec3_salt_length(rrset, pos); //salt
+	offset += knot_rrs_nsec3_salt_length(rrs, pos); //salt
 
 	uint8_t *next_hashed = NULL;
 	uint8_t next_hashed_size = 0;
-	knot_rdata_nsec3_next_hashed(rrset, pos, &next_hashed,
-	                             &next_hashed_size);
+	knot_rrs_nsec3_next_hashed(rrs, pos, &next_hashed, &next_hashed_size);
 	offset += next_hashed_size; //hash
 
-	*bitmap = knot_rrset_rr_rdata(rrset, pos) + offset;
-	*size = knot_rrset_rr_size(rrset, pos) - offset;
+	*bitmap = data_offset(rrs, pos, offset);
+	const knot_rr_t *rr = knot_rrs_rr(rrs, pos);
+	*size = knot_rr_rdata_size(rr) - offset;
 }
 
 static inline
-uint8_t knot_rdata_nsec3param_algorithm(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_nsec3param_algorithm(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos));
+	RRS_CHECK(rrs, pos, return 0);
+	return *data_offset(rrs, pos, 0);
 }
 
 static inline
-uint8_t knot_rdata_nsec3param_flags(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_nsec3param_flags(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos) + 1);
+	RRS_CHECK(rrs, pos, return 0);
+	return *data_offset(rrs, pos, 1);
 }
 
 static inline
-uint16_t knot_rdata_nsec3param_iterations(const knot_rrset_t *rrset, size_t pos)
+uint16_t knot_rrs_nsec3param_iterations(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return 0;
-	}
-
-	return knot_wire_read_u16(knot_rrset_rr_rdata(rrset, pos) + 2);
+	RRS_CHECK(rrs, pos, return 0);
+	return knot_wire_read_u16(data_offset(rrs, pos, 2));
 }
 
 static inline
-uint8_t knot_rdata_nsec3param_salt_length(const knot_rrset_t *rrset, size_t pos)
+uint8_t knot_rrs_nsec3param_salt_length(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return 0;
-	}
-
-	return *(knot_rrset_rr_rdata(rrset, pos) + 4);
+	RRS_CHECK(rrs, pos, return 0);
+	return *data_offset(rrs, pos, 4);
 }
 
 static inline
-const uint8_t *knot_rdata_nsec3param_salt(const knot_rrset_t *rrset, size_t pos)
+const uint8_t *knot_rrs_nsec3param_salt(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, pos) + 5;
+	RRS_CHECK(rrs, pos, return 0);
+	return data_offset(rrs, pos, 5);
 }
 
 static inline
-const knot_dname_t *knot_rdata_ns_name(const knot_rrset_t *rrset, size_t pos)
+const knot_dname_t *knot_rrs_ns_name(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, pos);
+	RRS_CHECK(rrs, pos, return 0);
+	return data_offset(rrs, pos, 0);
 }
 
 static inline
-const knot_dname_t *knot_rdata_mx_name(const knot_rrset_t *rrset, size_t pos)
+const knot_dname_t *knot_rrs_mx_name(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, pos) + 2;
+	RRS_CHECK(rrs, pos, return 0);
+	return data_offset(rrs, pos, 2);
 }
 
 static inline
-const knot_dname_t *knot_rdata_srv_name(const knot_rrset_t *rrset, size_t pos)
+const knot_dname_t *knot_rrs_srv_name(const knot_rrs_t *rrs, size_t pos)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return NULL;
-	}
-
-	return knot_rrset_rr_rdata(rrset, pos) + 6;
+	RRS_CHECK(rrs, pos, return 0);
+	return data_offset(rrs, pos, 6);
 }
 
 static inline
-const knot_dname_t *knot_rdata_name(const knot_rrset_t *rrset, size_t pos)
+const knot_dname_t *knot_rrs_name(const knot_rrs_t *rrs, size_t pos,
+                                  uint16_t type)
 {
-	if (rrset == NULL || knot_rrset_rr_count(rrset) <= pos) {
-		return NULL;
-	}
-
-	switch (rrset->type) {
+	switch (type) {
 		case KNOT_RRTYPE_NS:
-			return knot_rdata_ns_name(rrset, pos);
+			return knot_rrs_ns_name(rrs, pos);
 		case KNOT_RRTYPE_MX:
-			return knot_rdata_mx_name(rrset, pos);
+			return knot_rrs_mx_name(rrs, pos);
 		case KNOT_RRTYPE_SRV:
-			return knot_rdata_srv_name(rrset, pos);
+			return knot_rrs_srv_name(rrs, pos);
 		case KNOT_RRTYPE_CNAME:
-			return knot_rdata_cname_name(rrset);
+			return knot_rrs_cname_name(rrs);
 	}
 
 	return NULL;
diff --git a/src/libknot/rr.c b/src/libknot/rr.c
new file mode 100644
index 0000000000000000000000000000000000000000..ddf1df7ed3bba174bebae3e8ad2e0f9c5193b748
--- /dev/null
+++ b/src/libknot/rr.c
@@ -0,0 +1,409 @@
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "libknot/rr.h"
+#include "libknot/rdata.h"
+#include "libknot/common.h"
+#include "common/errcode.h"
+
+#ifndef STRICT_ALIGNMENT
+#pragma pack(push, 1)
+#endif
+
+/*!< \brief Helper structure - offsets in RR array. */
+struct rr_offsets {
+	uint32_t ttl;
+	uint16_t size;
+	uint8_t rdata[];
+};
+
+#ifndef STRICT_ALIGNMENT
+#pragma pack(pop)
+#endif
+
+static void *mm_realloc(mm_ctx_t *mm, void *what, size_t size, size_t prev_size)
+{
+	if (mm) {
+		void *p = mm->alloc(mm->ctx, size);
+		if (knot_unlikely(p == NULL)) {
+			return NULL;
+		} else {
+			if (what) {
+				memcpy(p, what,
+				       prev_size < size ? prev_size : size);
+			}
+			if (mm->free) {
+				mm->free(what);
+			}
+			return p;
+		}
+	} else {
+		return realloc(what, size);
+	}
+}
+
+static knot_rr_t *rr_seek(knot_rr_t *d, size_t pos)
+{
+	if (d == NULL) {
+		return NULL;
+	}
+
+	size_t offset = 0;
+	for (size_t i = 0; i < pos; i++) {
+		knot_rr_t *rr = d + offset;
+		offset += knot_rr_array_size(knot_rr_rdata_size(rr));
+	}
+
+	return d + offset;
+}
+
+static int find_rr_pos(const knot_rrs_t *search_in,
+                       const knot_rr_t *rr)
+{
+	for (uint16_t i = 0; i < search_in->rr_count; ++i) {
+		const knot_rr_t *search_rr = knot_rrs_rr(search_in, i);
+		if (knot_rr_cmp(rr, search_rr) == 0) {
+			return i;
+		}
+	}
+
+	return KNOT_ENOENT;
+}
+
+static int add_rr_at(knot_rrs_t *rrs, const knot_rr_t *rr, size_t pos,
+                     mm_ctx_t *mm)
+{
+	if (rrs == NULL || pos > rrs->rr_count) {
+		return KNOT_EINVAL;
+	}
+	const uint16_t size = knot_rr_rdata_size(rr);
+	const uint32_t ttl = knot_rr_ttl(rr);
+	const uint8_t *rdata = knot_rr_rdata(rr);
+
+	size_t total_size = knot_rrs_size(rrs);
+
+	// Realloc data.
+	void *tmp = mm_realloc(mm, rrs->data,
+	                       total_size + knot_rr_array_size(size),
+	                       total_size);
+	if (tmp) {
+		rrs->data = tmp;
+	} else {
+		ERR_ALLOC_FAILED;
+		return KNOT_ENOMEM;
+	}
+
+	if (rrs->rr_count == 0 || pos == rrs->rr_count) {
+		// No need to rearange RDATA
+		rrs->rr_count++;
+		knot_rr_t *new_rr = knot_rrs_rr(rrs, pos);
+		knot_rr_set_size(new_rr, size);
+		knot_rr_set_ttl(new_rr, ttl);
+		memcpy(knot_rr_rdata(new_rr), rdata, size);
+		return KNOT_EOK;
+	}
+
+	// RDATA have to be rearanged.
+	knot_rr_t *last_rr = knot_rrs_rr(rrs, rrs->rr_count - 1);
+	knot_rr_t *old_rr = knot_rrs_rr(rrs, pos);
+	assert(last_rr);
+	assert(old_rr);
+
+	// Make space for new data by moving the array
+	memmove(old_rr + knot_rr_array_size(size), old_rr,
+	        (last_rr + knot_rr_array_size(knot_rr_rdata_size(last_rr))) - old_rr);
+
+	// Set new RR
+	knot_rr_set_size(old_rr, size);
+	knot_rr_set_ttl(old_rr, ttl);
+	memcpy(knot_rr_rdata(old_rr), rdata, size);
+
+	rrs->rr_count++;
+	return KNOT_EOK;
+}
+
+static int remove_rr_at(knot_rrs_t *rrs, size_t pos, mm_ctx_t *mm)
+{
+	if (rrs == NULL || pos >= rrs->rr_count) {
+		return KNOT_EINVAL;
+	}
+
+	knot_rr_t *old_rr = knot_rrs_rr(rrs, pos);
+	knot_rr_t *last_rr = knot_rrs_rr(rrs, rrs->rr_count - 1);
+	assert(old_rr);
+	assert(last_rr);
+
+	size_t total_size = knot_rrs_size(rrs);
+	uint16_t old_size = knot_rr_rdata_size(old_rr);
+
+	void *old_threshold = old_rr + knot_rr_array_size(old_size);
+	void *last_threshold = last_rr + knot_rr_array_size(knot_rr_rdata_size(last_rr));
+	// Move RDATA
+	memmove(old_rr, old_threshold,
+	        last_threshold - old_threshold);
+
+	if (rrs->rr_count > 1) {
+		// Realloc RDATA
+		void *tmp = mm_realloc(mm, rrs->data,
+		                       total_size - (knot_rr_array_size(old_size)),
+		                       total_size);
+		if (tmp == NULL) {
+			ERR_ALLOC_FAILED;
+			return KNOT_ENOMEM;
+		} else {
+			rrs->data = tmp;
+		}
+	} else {
+		// Free RDATA
+		mm_free(mm, rrs->data);
+		rrs->data = NULL;
+	}
+	rrs->rr_count--;
+
+	return KNOT_EOK;
+}
+
+uint16_t knot_rr_rdata_size(const knot_rr_t *rr)
+{
+	return ((struct rr_offsets *)rr)->size;
+}
+
+void knot_rr_set_size(knot_rr_t *rr, uint16_t size)
+{
+	((struct rr_offsets *)rr)->size = size;
+}
+
+uint32_t knot_rr_ttl(const knot_rr_t *rr)
+{
+	return ((struct rr_offsets *)rr)->ttl;
+}
+
+void knot_rr_set_ttl(knot_rr_t *rr, uint32_t ttl)
+{
+	((struct rr_offsets *)rr)->ttl = ttl;
+}
+
+uint8_t *knot_rr_rdata(const knot_rr_t *rr)
+{
+	return ((struct rr_offsets *)rr)->rdata;
+}
+
+size_t knot_rr_array_size(uint16_t size)
+{
+	return size + sizeof(struct rr_offsets);
+}
+
+int knot_rr_cmp(const knot_rr_t *rr1, const knot_rr_t *rr2)
+{
+	assert(rr1 && rr2);
+	const uint8_t *r1 = knot_rr_rdata(rr1);
+	const uint8_t *r2 = knot_rr_rdata(rr2);
+	uint16_t l1 = knot_rr_rdata_size(rr1);
+	uint16_t l2 = knot_rr_rdata_size(rr2);
+	int cmp = memcmp(r1, r2, MIN(l1, l2));
+	if (cmp == 0 && l1 != l2) {
+		cmp = l1 < l2 ? -1 : 1;
+	}
+	return cmp;
+}
+
+void knot_rrs_init(knot_rrs_t *rrs)
+{
+	if (rrs) {
+		rrs->rr_count = 0;
+		rrs->data = NULL;
+	}
+}
+
+void knot_rrs_clear(knot_rrs_t *rrs, mm_ctx_t *mm)
+{
+	if (rrs) {
+		mm_free(mm, rrs->data);
+		rrs->data = NULL;
+		rrs->rr_count = 0;
+	}
+}
+
+int knot_rrs_copy(knot_rrs_t *dst, const knot_rrs_t *src, mm_ctx_t *mm)
+{
+	if (dst == NULL || src == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	dst->rr_count = src->rr_count;
+	size_t src_size = knot_rrs_size(src);
+	dst->data = mm_alloc(mm, src_size);
+	if (dst->data == NULL) {
+		ERR_ALLOC_FAILED;
+		return KNOT_ENOMEM;
+	}
+
+	memcpy(dst->data, src->data, src_size);
+	return KNOT_EOK;
+}
+
+knot_rr_t *knot_rrs_rr(const knot_rrs_t *rrs, size_t pos)
+{
+	if (rrs == NULL || pos >= rrs->rr_count) {
+		return NULL;
+	}
+
+	return rr_seek(rrs->data, pos);
+}
+
+size_t knot_rrs_size(const knot_rrs_t *rrs)
+{
+	if (rrs == NULL) {
+		return 0;
+	}
+
+	size_t total_size = 0;
+	for (size_t i = 0; i < rrs->rr_count; ++i) {
+		const knot_rr_t *rr = knot_rrs_rr(rrs, i);
+		assert(rr);
+		total_size += knot_rr_array_size(knot_rr_rdata_size(rr));
+	}
+
+	return total_size;
+}
+
+int knot_rrs_add_rr(knot_rrs_t *rrs, const knot_rr_t *rr, mm_ctx_t *mm)
+{
+	if (rrs == NULL || rr == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	for (uint16_t i = 0; i < rrs->rr_count; ++i) {
+		const knot_rr_t *rrset_rr = knot_rrs_rr(rrs, i);
+		int cmp = knot_rr_cmp(rrset_rr, rr);
+		if (cmp == 0) {
+			// Duplication - no need to add this RR
+			return KNOT_EOK;
+		} else if (cmp > 0) {
+			// Found position to insert
+			return add_rr_at(rrs, rr, i, mm);
+		}
+	}
+
+	// If flow gets here, it means that we should insert at the last position
+	return add_rr_at(rrs, rr, rrs->rr_count, mm);
+}
+
+bool knot_rrs_eq(const knot_rrs_t *rrs1, const knot_rrs_t *rrs2)
+{
+	if (rrs1->rr_count != rrs2->rr_count) {
+		return false;
+	}
+
+	for (uint16_t i = 0; i < rrs1->rr_count; ++i) {
+		const knot_rr_t *rr1 = knot_rrs_rr(rrs1, i);
+		const knot_rr_t *rr2 = knot_rrs_rr(rrs2, i);
+		if (knot_rr_cmp(rr1, rr2) != 0) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+bool knot_rrs_member(const knot_rrs_t *rrs, const knot_rr_t *rr, bool cmp_ttl)
+{
+	for (uint16_t i = 0; i < rrs->rr_count; ++i) {
+		const knot_rr_t *cmp_rr = knot_rrs_rr(rrs, i);
+		if (cmp_ttl) {
+			if (knot_rr_ttl(rr) != knot_rr_ttl(cmp_rr)) {
+				continue;
+			}
+		}
+		int cmp = knot_rr_cmp(cmp_rr, rr);
+		if (cmp == 0) {
+			// Match.
+			return true;
+		}
+		if (cmp > 0) {
+			// 'Bigger' RR present, no need to continue.
+			return false;
+		}
+	}
+	return false;
+}
+
+int knot_rrs_merge(knot_rrs_t *rrs1, const knot_rrs_t *rrs2, mm_ctx_t *mm)
+{
+	if (rrs1 == NULL || rrs2 == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	for (uint16_t i = 0; i < rrs2->rr_count; ++i) {
+		const knot_rr_t *rr = knot_rrs_rr(rrs2, i);
+		int ret = knot_rrs_add_rr(rrs1, rr, mm);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+	}
+
+	return KNOT_EOK;
+}
+
+int knot_rrs_intersect(const knot_rrs_t *a, const knot_rrs_t *b,
+                       knot_rrs_t *out, mm_ctx_t *mm)
+{
+	if (a == NULL || b == NULL || out == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	knot_rrs_init(out);
+	const bool compare_ttls = false;
+	for (uint16_t i = 0; i < a->rr_count; ++i) {
+		const knot_rr_t *rr = knot_rrs_rr(a, i);
+		if (knot_rrs_member(b, rr, compare_ttls)) {
+			// Add RR into output intersection RRSet.
+			int ret = knot_rrs_add_rr(out, rr, mm);
+			if (ret != KNOT_EOK) {
+				knot_rrs_clear(out, mm);
+				return ret;
+			}
+		}
+	}
+
+	return KNOT_EOK;
+}
+
+int knot_rrs_subtract(knot_rrs_t *from, const knot_rrs_t *what, mm_ctx_t *mm)
+{
+	if (from == NULL || what == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	for (uint16_t i = 0; i < what->rr_count; ++i) {
+		const knot_rr_t *to_remove = knot_rrs_rr(what, i);
+		int pos_to_remove = find_rr_pos(from, to_remove);
+		if (pos_to_remove >= 0) {
+			int ret = remove_rr_at(from, pos_to_remove, mm);
+			if (ret != KNOT_EOK) {
+				return ret;
+			}
+		}
+	}
+
+	return KNOT_EOK;
+}
+
diff --git a/src/libknot/rr.h b/src/libknot/rr.h
new file mode 100644
index 0000000000000000000000000000000000000000..4fd46b85a6fcb4877447132bd89a6b14ebf5868b
--- /dev/null
+++ b/src/libknot/rr.h
@@ -0,0 +1,208 @@
+/*!
+ * \file rr.h
+ *
+ * \author Jan Kadlec <jan.kadlec@nic.cz>
+ *
+ * \brief API for manipulating RRs and RR arrays.
+ *
+ * \addtogroup libknot
+ * @{
+ */
+/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "common/mempattern.h"
+
+/* ---------------------------- Single RR ----------------------------------- */
+
+/*!
+ * \brief knot_rr_t Array holding single RR payload, i.e. ttl, size and RDATA.
+ */
+typedef uint8_t knot_rr_t;
+
+/* ------------------------- RR getters/setters ----------------------------- */
+
+/*!
+ * \brief Returns RDATA size of single RR.
+ * \param rr  RR whose size we want.
+ * \return  RR size.
+ */
+uint16_t knot_rr_rdata_size(const knot_rr_t *rr);
+
+/*!
+ * \brief Sets size for given RR.
+ * \param rr    RR whose size we want to set.
+ * \param size  Size to be set.
+ */
+void knot_rr_set_size(knot_rr_t *rr, uint16_t size);
+
+/*!
+ * \brief Returns TTL of single RR.
+ * \param rr  RR whose TTL we want.
+ * \return  RR TTL.
+ */
+uint32_t knot_rr_ttl(const knot_rr_t *rr);
+
+/*!
+ * \brief Sets TTL for given RR.
+ * \param rr   RR whose TTL we want to set.
+ * \param ttl  TTL to be set.
+ */
+void knot_rr_set_ttl(knot_rr_t *rr, uint32_t ttl);
+
+/*!
+ * \brief Returns pointer to RR data.
+ * \param rr  RR whose data we want.
+ * \return RR data pointer.
+ */
+uint8_t *knot_rr_rdata(const knot_rr_t *rr);
+
+/* ----------------------------- RR misc ------------------------------------ */
+
+/*!
+ * \brief Returns actual size of RR structure for given RDATA size.
+ * \param size  RDATA size.
+ * \return Actual structure size.
+ */
+size_t knot_rr_array_size(uint16_t size);
+
+/*!
+ * \brief Canonical comparison of two RRs. Both RRs *must* exist.
+ * \param rr1  First RR to compare.
+ * \param rr2  Second RR to compare.
+ * \retval 0 if rr1 == rr2.
+ * \retval < 0 if rr1 < rr2.
+ * \retval > 0 if rr1 > rr2.
+ */
+int knot_rr_cmp(const knot_rr_t *rr1, const knot_rr_t *rr2);
+
+/* --------------------------- Multiple RRs ----------------------------------*/
+
+/*!< \brief Array of RRs. */
+typedef struct knot_rrs {
+	uint16_t rr_count;  /*!< \brief Count of RRs stored in the structure. */
+	knot_rr_t *data;    /*!< \brief Actual data, canonically sorted. */
+} knot_rrs_t;
+
+/* -------------------------- RRs init/clear ---------------------------------*/
+
+/*!
+ * \brief Initializes RRS structure.
+ * \param rrs  Structure to be initialized.
+ */
+void knot_rrs_init(knot_rrs_t *rrs);
+
+/*!
+ * \brief Frees data initialized by RRS structure, but not the structure itself.
+ * \param rrs  Structure to be cleared.
+ * \param mm   Memory context used to create allocations.
+ */
+void knot_rrs_clear(knot_rrs_t *rrs, mm_ctx_t *mm);
+
+/*!
+ * \brief Deep copies RRS structure. All data are duplicated.
+ * \param dst  Copy destination.
+ * \param src  Copy source.
+ * \param mm   Memory context.
+ * \return KNOT_E*
+ */
+int knot_rrs_copy(knot_rrs_t *dst, const knot_rrs_t *src, mm_ctx_t *mm);
+
+/* ----------------------- RRs getters/setters ------------------------------ */
+
+/*!
+ * \brief Gets RR from RRS structure, using given position.
+ * \param rrs  RRS structure to get RR from.
+ * \param pos  Position to use.
+ * \return Pointer to RR at \a pos position.
+ */
+knot_rr_t *knot_rrs_rr(const knot_rrs_t *rrs, size_t pos);
+
+/*!
+ * \brief Returns size of array with RRs.
+ * \param rrs  RR array.
+ * \return Array size.
+ */
+size_t knot_rrs_size(const knot_rrs_t *rrs);
+
+/* ----------------------- RRs RR manipulation ------------------------------ */
+
+/*!
+ * \brief Adds single RR into RRS structure. All data are copied.
+ * \param rrs  RRS structure to add RR into.
+ * \param rr   RR to add.
+ * \param mm   Memory context.
+ * \return KNOT_E*
+ */
+int knot_rrs_add_rr(knot_rrs_t *rrs, const knot_rr_t *rr, mm_ctx_t *mm);
+
+/* ---------------------- RRs set-like operations --------------------------- */
+
+/*!
+ * \brief RRS equality check.
+ * \param rrs1  First RRS to be compared.
+ * \param rrs2  Second RRS to be compared.
+ * \retval true if rrs1 == rrs2.
+ * \retval false if rrs1 != rrs2.
+ */
+bool knot_rrs_eq(const knot_rrs_t *rrs1, const knot_rrs_t *rrs2);
+
+/*!
+ * \brief Returns true if \a rr is present in \a rrs, false otherwise.
+ * \param rrs      RRS to search in.
+ * \param rr       RR to compare with.
+ * \param cmp_ttl  If set to true, TTLs will be compared as well.
+ * \retval true if \a rr is present in \a rrs.
+ * \retval false if \a rr is not present in \a rrs.
+ */
+bool knot_rrs_member(const knot_rrs_t *rrs, const knot_rr_t *rr, bool cmp_ttl);
+
+/*!
+ * \brief Merges two RRS into the first one. Second RRS is left intact.
+ *        Canonical order is preserved.
+ * \param rrs1  Destination RRS (merge here).
+ * \param rrs2  RRS to be merged (merge from).
+ * \param mm    Memory context.
+ * \return KNOT_E*
+ */
+int knot_rrs_merge(knot_rrs_t *rrs1, const knot_rrs_t *rrs2, mm_ctx_t *mm);
+
+/*!
+ * \brief RRS set-like intersection. Full compare is done.
+ * \param a        First RRS to intersect.
+ * \param b        Second RRS to intersect.
+ * \param out      Output RRS with intersection, RDATA are created anew.
+ * \param mm       Memory context. Will be used to create new RDATA.
+ * \return KNOT_E*
+ */
+int knot_rrs_intersect(const knot_rrs_t *a, const knot_rrs_t *b,
+                       knot_rrs_t *out, mm_ctx_t *mm);
+
+/*!
+ * \brief Does set-like RRS subtraction. \a from RRS is changed.
+ * \param from  RRS to subtract from.
+ * \param what  RRS to subtract.
+ * \param mm    Memory context use to reallocated \a from data.
+ * \return KNOT_E*
+ */
+int knot_rrs_subtract(knot_rrs_t *from, const knot_rrs_t *what,
+                      mm_ctx_t *mm);
diff --git a/src/libknot/rrset-dump.c b/src/libknot/rrset-dump.c
index 64823b0697d9d8e5e8e88dde695f5347721ab4ad..ab98d68f2c4ca0bfd52b3f9fb1e51d73997df1f6 100644
--- a/src/libknot/rrset-dump.c
+++ b/src/libknot/rrset-dump.c
@@ -316,7 +316,6 @@ static void wire_type_to_str(rrset_dump_params_t *p)
 	p->ret = 0;
 }
 
-
 static int hex_encode(const uint8_t  *in,
                       const uint32_t in_len,
                       uint8_t        *out,
@@ -1797,7 +1796,7 @@ int knot_rrset_txt_dump_data(const knot_rrset_t      *rrset,
 
 	int ret = 0;
 
-	switch (knot_rrset_type(rrset)) {
+	switch (rrset->type) {
 		case KNOT_RRTYPE_A:
 			ret = dump_a(data, data_len, dst, maxlen, style);
 			break;
@@ -1978,7 +1977,7 @@ int knot_rrset_txt_dump(const knot_rrset_t      *rrset,
                         const size_t            maxlen,
                         const knot_dump_style_t *style)
 {
-	if (rrset == NULL || dst == NULL || style == NULL) {
+	if (knot_rrset_empty(rrset) || dst == NULL || style == NULL) {
 		return KNOT_EINVAL;
 	}
 
diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c
index 729f0828a493da5376103fc66fbab449368ef0a8..5b79af261db38bf67cd35ae63f0545ac3f4a729e 100644
--- a/src/libknot/rrset.c
+++ b/src/libknot/rrset.c
@@ -34,85 +34,6 @@
 #include "libknot/dname.h"
 #include "libknot/rdata.h"
 
-typedef uint16_t rr_size_t;
-#define RR_META_SIZE (sizeof(rr_size_t) + sizeof(uint32_t))
-
-typedef uint8_t rr_t;
-
-static uint16_t rr_size(const rr_t *rr)
-{
-	return *((rr_size_t *)rr);
-}
-
-static uint32_t rr_ttl(const rr_t *rr)
-{
-	return *((uint32_t *)(rr + sizeof(rr_size_t)));
-}
-
-static uint8_t *rr_rdata(rr_t *rr)
-{
-	return rr + RR_META_SIZE;
-}
-
-static void rr_set_size(rr_t *rr, rr_size_t size)
-{
-	*((rr_size_t *)rr) = size;
-}
-
-static void rr_set_ttl(rr_t *rr, uint32_t ttl)
-{
-	*((uint32_t *)(rr + sizeof(rr_size_t))) = ttl;
-}
-
-static void *rdata_seek(void *d, size_t pos)
-{
-	if (d == NULL) {
-		return NULL;
-	}
-
-	size_t offset = 0;
-	for (size_t i = 0; i < pos; i++) {
-		rr_t *rr = (rr_t *)(d + offset);
-		offset += rr_size(rr) + RR_META_SIZE;
-	}
-
-	return d + offset;
-}
-
-static rr_t *get_rr(const knot_rrset_t *rrset, size_t pos)
-{
-	if (rrset == NULL || pos >= rrset->rr_count) {
-		return NULL;
-	}
-
-	return (rr_t *)rdata_seek(rrset->rrs, pos);
-}
-
-static size_t rrset_rdata_size_total(const knot_rrset_t *rrset)
-{
-	if (rrset == NULL || rrset->rrs == NULL) {
-		return 0;
-	}
-
-	size_t total_size = 0;
-	for (size_t i = 0; i < rrset->rr_count; ++i) {
-		rr_t *rr = get_rr(rrset, i);
-		assert(rr);
-		total_size += rr_size(rr) + RR_META_SIZE;
-	}
-
-	return total_size;
-}
-
-uint16_t knot_rrset_rr_count(const knot_rrset_t *rrset)
-{
-	if (rrset == NULL || rrset->rrs == NULL) {
-		return 0;
-	}
-
-	return rrset->rr_count;
-}
-
 static uint16_t rrset_rdata_naptr_bin_chunk_size(const knot_rrset_t *rrset,
                                                size_t pos)
 {
@@ -146,65 +67,6 @@ static size_t rrset_rdata_remainder_size(const knot_rrset_t *rrset,
 	return ret;
 }
 
-/*! \brief Canonical order RDATA comparison. */
-static int rrset_rdata_compare_one(const knot_rrset_t *rrset1,
-                                   const knot_rrset_t *rrset2,
-                                   size_t pos1, size_t pos2)
-{
-	assert(rrset1 != NULL);
-	assert(rrset2 != NULL);
-	assert(rrset1->type == rrset2->type);
-
-	uint8_t *r1 = knot_rrset_rr_rdata(rrset1, pos1);
-	uint8_t *r2 = knot_rrset_rr_rdata(rrset2, pos2);
-	uint16_t l1 = knot_rrset_rr_size(rrset1, pos1);
-	uint16_t l2 = knot_rrset_rr_size(rrset2, pos2);
-	int cmp = memcmp(r1, r2, MIN(l1, l2));
-	if (cmp == 0 && l1 != l2) {
-		cmp = l1 < l2 ? -1 : 1;
-	}
-	return cmp;
-}
-
-/*!
- * \brief RRSet RDATA equality check.
- *
- * \param r1  First RRSet.
- * \param r2  Second RRSet.
- *
- * \return True if RRs in r1 are equal to RRs in r2, false otherwise.
- */
-static bool knot_rrset_rdata_equal(const knot_rrset_t *r1, const knot_rrset_t *r2)
-{
-	if (r1 == NULL || r2 == NULL || (r1->type != r2->type) ||
-	    r1->rrs == NULL || r2->rrs == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	uint16_t r1_rdata_count = knot_rrset_rr_count(r1);
-	uint16_t r2_rdata_count = knot_rrset_rr_count(r2);
-
-	if (r1_rdata_count != r2_rdata_count) {
-		return false;
-	}
-
-	for (uint16_t i = 0; i < r1_rdata_count; i++) {
-		bool found = false;
-		for (uint16_t j = 0; j < r2_rdata_count; j++) {
-			if (rrset_rdata_compare_one(r1, r2, i, j) == 0) {
-				found = true;
-				break;
-			}
-		}
-
-		if (!found) {
-			return false;
-		}
-	}
-
-	return true;
-}
-
 static int knot_rrset_header_to_wire(const knot_rrset_t *rrset, uint32_t ttl,
                                      uint8_t **pos, size_t max_size,
                                      knot_compr_t *compr, size_t *size)
@@ -279,7 +141,7 @@ static int knot_rrset_header_to_wire(const knot_rrset_t *rrset, uint32_t ttl,
 /* [code-review] Split to more functions, this one's too long. */
 static int knot_rrset_rdata_to_wire_one(const knot_rrset_t *rrset,
                                         uint16_t rdata_pos, uint8_t **pos,
-                                        size_t max_size, size_t *rr_size,
+                                        size_t max_size, size_t *knot_rr_size,
                                         knot_compr_t *compr)
 {
 	assert(rrset);
@@ -321,7 +183,6 @@ static int knot_rrset_rdata_to_wire_one(const knot_rrset_t *rrset,
 	for (int i = 0; desc->block_types[i] != KNOT_RDATA_WF_END; i++) {
 		int item = desc->block_types[i];
 		if (compr && descriptor_item_is_compr_dname(item)) {
-			dbg_packet("%s: putting compressed name\n", __func__);
 			const knot_dname_t *dname = rdata + offset;
 			int ret = knot_compr_put_dname(dname, *pos,
 			                             max_size - size - rdlength,
@@ -333,25 +194,12 @@ static int knot_rrset_rdata_to_wire_one(const knot_rrset_t *rrset,
 			if (!knot_pkt_compr_hint(compr->rrinfo, hint_id)) {
 				knot_pkt_compr_hint_set(compr->rrinfo, hint_id, compr->wire_pos, ret);
 			}
-			assert(ret + size + rdlength <= max_size);
-dbg_response_exec_detail(
-			char *name = knot_dname_to_str(dname);
-			dbg_response_detail("Compressed dname=%s size: %d\n",
-			                    name, ret);
-			free(name);
-);
 			*pos += ret;
 			rdlength += ret;
 			offset += knot_dname_size(dname);
 			compr->wire_pos += ret;
 		} else if (descriptor_item_is_dname(item)) {
-			dbg_packet("%s: putting uncompressed name\n", __func__);
 			const knot_dname_t *dname = rdata + offset;
-dbg_rrset_exec_detail(
-			char *name = knot_dname_to_str(dname);
-			dbg_rrset_detail("Saving this DNAME=%s\n", name);
-			free(name);
-);
 			// save whole domain name
 			size_t maxb = max_size - size - rdlength;
 			int dname_size = knot_dname_to_wire(*pos, dname, maxb);
@@ -361,8 +209,6 @@ dbg_rrset_exec_detail(
 			if (compr && !knot_pkt_compr_hint(compr->rrinfo, hint_id)) {
 				knot_pkt_compr_hint_set(compr->rrinfo, hint_id, compr->wire_pos, dname_size);
 			}
-			dbg_rrset_detail("Uncompressed dname size: %d\n",
-			                 dname_size);
 			*pos += dname_size;
 			rdlength += dname_size;
 			offset += dname_size;
@@ -370,8 +216,6 @@ dbg_rrset_exec_detail(
 				compr->wire_pos += dname_size;
 			}
 		} else if (descriptor_item_is_fixed(item)) {
-			dbg_rrset_detail("Saving static chunk, size=%d\n",
-			                 item);
 			/* Fixed length chunk. */
 			if (size + rdlength + item > max_size) {
 				return KNOT_ESPACE;
@@ -388,13 +232,7 @@ dbg_rrset_exec_detail(
 			size_t remainder_size =
 				rrset_rdata_remainder_size(rrset, offset,
 			                                   rdata_pos);
-			dbg_rrset_detail("Saving remaining chunk, size=%zu, "
-			                 "size with remainder=%zu\n",
-			                 remainder_size,
-			                 size + rdlength + remainder_size);
 			if (size + rdlength + remainder_size > max_size) {
-				dbg_rrset("rr: to_wire: Remainder does not fit "
-				          "to wire.\n");
 				return KNOT_ESPACE;
 			}
 			memcpy(*pos, rdata + offset, remainder_size);
@@ -410,8 +248,6 @@ dbg_rrset_exec_detail(
 			uint16_t chunk_size =
 			    rrset_rdata_naptr_bin_chunk_size(rrset, rdata_pos);
 			if (size + rdlength + chunk_size > max_size) {
-				dbg_rrset("rr: to_wire: NAPTR chunk does not "
-				          "fit to wire.\n");
 				return KNOT_ESPACE;
 			}
 			memcpy(*pos, rdata + offset, chunk_size);
@@ -427,8 +263,7 @@ dbg_rrset_exec_detail(
 	knot_wire_write_u16(rdlength_pos, rdlength);
 	size += rdlength;
 
-	*rr_size = size;
-	dbg_packet("%s: written rrset %zu bytes\n", __func__, *rr_size);
+	*knot_rr_size = size;
 	assert(size <= max_size);
 	return KNOT_EOK;
 }
@@ -463,19 +298,19 @@ static int knot_rrset_to_wire_aux(const knot_rrset_t *rrset, uint8_t **pos,
 	for (uint16_t i = 0; i < knot_rrset_rr_count(rrset); ++i) {
 		dbg_rrset_detail("rrset: to_wire: Current max_size=%zu\n",
 			         max_size);
-		size_t rr_size = 0;
+		size_t knot_rr_size = 0;
 		int ret = knot_rrset_rdata_to_wire_one(rrset, i, pos, max_size,
-		                                       &rr_size, comp);
+		                                       &knot_rr_size, comp);
 		if (ret != KNOT_EOK) {
 			dbg_rrset("rrset: to_wire: Cannot convert RR. "
 			          "Reason: %s.\n", knot_strerror(ret));
 			return ret;
 		}
-		dbg_rrset_detail("Converted RR nr=%d, size=%zu\n", i, rr_size);
+		dbg_rrset_detail("Converted RR nr=%d, size=%zu\n", i, knot_rr_size);
 		/* Change size of whole RRSet. */
-		size += rr_size;
+		size += knot_rr_size;
 		/* Change max size. */
-		max_size -= rr_size;
+		max_size -= knot_rr_size;
 	}
 
 	dbg_rrset_detail("Max size: %zu, size: %zu\n", max_size, size);
@@ -483,12 +318,9 @@ static int knot_rrset_to_wire_aux(const knot_rrset_t *rrset, uint8_t **pos,
 	return size;
 }
 
-static int knot_rrset_rdata_store_binary(uint8_t *rdata, size_t *offset,
-                                         size_t packet_offset,
-                                         const uint8_t *wire,
-                                         size_t *pos,
-                                         size_t rdlength,
-                                         size_t size)
+static int binary_store(uint8_t *rdata, size_t *offset, size_t packet_offset,
+                        const uint8_t *wire, size_t *pos, size_t rdlength,
+                        size_t size)
 {
 	assert(rdata);
 	assert(wire);
@@ -509,230 +341,97 @@ static int knot_rrset_rdata_store_binary(uint8_t *rdata, size_t *offset,
 	return KNOT_EOK;
 }
 
-static size_t rrset_binary_size_one(const knot_rrset_t *rrset,
-                                    size_t rdata_pos)
-{
-	rr_t *rr = get_rr(rrset, rdata_pos);
-	if (rr) {
-		// RR size + TTL
-		return rr_size(rr) + sizeof(uint32_t);
-	} else {
-		return 0;
-	}
-}
-
-static void rrset_serialize_rr(const knot_rrset_t *rrset, size_t rdata_pos,
-                               uint8_t *stream)
-{
-	rr_t *rr = get_rr(rrset, rdata_pos);
-	assert(rr);
-	uint32_t ttl = rr_ttl(rr);
-	memcpy(stream, &ttl, sizeof(uint32_t));
-	memcpy(stream + sizeof(uint32_t), rr_rdata(rr), rr_size(rr));
-}
-
-static int rrset_deserialize_rr(knot_rrset_t *rrset,
-                                const uint8_t *stream, uint32_t rdata_size)
-{
-	uint32_t ttl;
-	memcpy(&ttl, stream, sizeof(uint32_t));
-	uint8_t *rdata = knot_rrset_create_rr(rrset,
-	                                      rdata_size - sizeof(uint32_t),
-	                                      ttl, NULL);
-	if (rdata == NULL) {
-		return KNOT_ENOMEM;
-	}
-
-	memcpy(rdata, stream + sizeof(uint32_t), rdata_size - sizeof(uint32_t));
-	return KNOT_EOK;
-}
-
-int knot_rrset_remove_rdata_pos(knot_rrset_t *rrset, size_t pos, mm_ctx_t *mm)
+knot_rrset_t *knot_rrset_new(const knot_dname_t *owner, uint16_t type,
+                             uint16_t rclass, mm_ctx_t *mm)
 {
-	if (rrset == NULL || pos >= knot_rrset_rr_count(rrset)) {
-		return KNOT_EINVAL;
-	}
-
-	rr_t *old_rr = get_rr(rrset, pos);
-	rr_t *last_rr = get_rr(rrset, rrset->rr_count - 1);
-	assert(old_rr);
-	assert(last_rr);
-
-	size_t total_size = rrset_rdata_size_total(rrset);
-	uint16_t old_size = rr_size(old_rr);
-
-	void *old_threshold = old_rr + rr_size(old_rr) + RR_META_SIZE;
-	void *last_threshold = last_rr + rr_size(last_rr) + RR_META_SIZE;
-	// Move RDATA
-	memmove(old_rr, old_threshold,
-	        last_threshold - old_threshold);
-
-	if (rrset->rr_count > 1) {
-		// Realloc RDATA
-		void *tmp = mm_realloc(mm, rrset->rrs,
-		                       total_size - (old_size + RR_META_SIZE),
-		                       total_size);
-		if (tmp == NULL) {
-			ERR_ALLOC_FAILED;
-		} else {
-			rrset->rrs = tmp;
-		}
-	} else {
-		// Free RDATA
-		mm_free(mm, rrset->rrs);
-		rrset->rrs = NULL;
+	knot_dname_t *owner_cpy = knot_dname_copy(owner, mm);
+	if (owner_cpy == NULL) {
+		return NULL;
 	}
-	rrset->rr_count--;
-
-	return KNOT_EOK;
-}
 
-knot_rrset_t *knot_rrset_new(knot_dname_t *owner, uint16_t type,
-                             uint16_t rclass, mm_ctx_t *mm)
-{
 	knot_rrset_t *ret = mm_alloc(mm, sizeof(knot_rrset_t));
 	if (ret == NULL) {
 		ERR_ALLOC_FAILED;
+		knot_dname_free(&owner_cpy, mm);
 		return NULL;
 	}
 
-	ret->rrs = NULL;
-
-	ret->owner = owner;
-	ret->type = type;
-	ret->rclass = rclass;
-	ret->rr_count = 0;
-
-	ret->additional = NULL;
+	knot_rrset_init(ret, owner_cpy, type, rclass);
 
 	return ret;
 }
 
-knot_rrset_t *knot_rrset_new_from(const knot_rrset_t *tpl, mm_ctx_t *mm)
+void knot_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, uint16_t type,
+                     uint16_t rclass)
 {
-	if (!tpl) {
-		return NULL;
-	}
-
-	knot_dname_t *owner = knot_dname_copy(tpl->owner);
-	if (!owner) {
-		return NULL;
-	}
-
-	return knot_rrset_new(owner, tpl->type, tpl->rclass, mm);
+	rrset->owner = owner;
+	rrset->type = type;
+	rrset->rclass = rclass;
+	knot_rrs_init(&rrset->rrs);
+	rrset->additional = NULL;
 }
 
-int knot_rrset_add_rr(knot_rrset_t *rrset,
-                      const uint8_t *rdata, const uint16_t size,
-                      const uint32_t ttl, mm_ctx_t *mm)
+void knot_rrset_init_empty(knot_rrset_t *rrset)
 {
-	if (rrset == NULL || rdata == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	uint8_t *p = knot_rrset_create_rr(rrset, size, ttl, mm);
-	memcpy(p, rdata, size);
-
-	return KNOT_EOK;
+	knot_rrset_init(rrset, NULL, 0, KNOT_CLASS_IN);
 }
 
-static uint8_t* knot_rrset_create_rr_at_pos(knot_rrset_t *rrset,
-                                            size_t pos, uint16_t size,
-                                            uint32_t ttl, mm_ctx_t *mm)
+knot_rrset_t *knot_rrset_copy(const knot_rrset_t *src, mm_ctx_t *mm)
 {
-	if (rrset == NULL || pos > knot_rrset_rr_count(rrset)) {
+	if (src == NULL) {
 		return NULL;
 	}
-	if (pos == knot_rrset_rr_count(rrset)) {
-		// Normal RDATA addition
-		return knot_rrset_create_rr(rrset, size, ttl, mm);
-	}
-
-	size_t total_size = rrset_rdata_size_total(rrset);
 
-	// Realloc data.
-	void *tmp = mm_realloc(mm, rrset->rrs,
-	                       total_size + size + RR_META_SIZE,
-	                       total_size);
-	if (tmp) {
-		rrset->rrs = tmp;
-	} else {
-		ERR_ALLOC_FAILED;
+	knot_rrset_t *rrset = knot_rrset_new(src->owner, src->type,
+	                                     src->rclass, mm);
+	if (rrset == NULL) {
 		return NULL;
 	}
 
-	rr_t *old_rr = get_rr(rrset, pos);
-	rr_t *last_rr = get_rr(rrset, rrset->rr_count - 1);
-	assert(old_rr);
-	assert(last_rr);
-
-	// Make space for new data by moving the array
-	memmove(old_rr + size + RR_META_SIZE, old_rr,
-	        (last_rr + RR_META_SIZE + rr_size(last_rr)) - old_rr);
-
-	// Set size for new RR
-	rr_set_size(old_rr, size);
-	rr_set_ttl(old_rr, ttl);
+	int ret = knot_rrs_copy(&rrset->rrs, &src->rrs, mm);
+	if (ret != KNOT_EOK) {
+		knot_rrset_free(&rrset, mm);
+		return NULL;
+	}
 
-	rrset->rr_count++;
-	assert(rr_size(old_rr) > 0);
-	return rr_rdata(old_rr);
+	return rrset;
 }
 
-static int knot_rrset_add_rr_at_pos(knot_rrset_t *rrset, size_t pos,
-                                    const uint8_t *rdata, uint16_t size,
-                                    uint32_t ttl, mm_ctx_t *mm)
+void knot_rrset_free(knot_rrset_t **rrset, mm_ctx_t *mm)
 {
-	if (rrset == NULL || rdata == NULL) {
-		return KNOT_EINVAL;
+	if (rrset == NULL || *rrset == NULL) {
+		return;
 	}
 
-	uint8_t *p = knot_rrset_create_rr_at_pos(rrset, pos, size, ttl, mm);
-	if (p == NULL) {
-		return KNOT_ERROR;
-	}
-	memcpy(p, rdata, size);
+	knot_rrset_clear(*rrset, mm);
 
-	return KNOT_EOK;
+	mm_free(mm, *rrset);
+	*rrset = NULL;
 }
 
-/*----------------------------------------------------------------------------*/
-
-uint8_t* knot_rrset_create_rr(knot_rrset_t *rrset, const uint16_t size,
-                              const uint32_t ttl, mm_ctx_t *mm)
+void knot_rrset_clear(knot_rrset_t *rrset, mm_ctx_t *mm)
 {
-	if (rrset == NULL) {
-		return NULL;
+	if (rrset) {
+		knot_rrs_clear(&rrset->rrs, mm);
+		knot_dname_free(&rrset->owner, mm);
 	}
+}
 
-	size_t total_size = rrset_rdata_size_total(rrset);
-	/* Realloc RRs. */
-	void *tmp = mm_realloc(mm, rrset->rrs, total_size + size + RR_META_SIZE,
-	                       total_size);
-	if (tmp) {
-		rrset->rrs = tmp;
+uint8_t *knot_rrset_rr_rdata(const knot_rrset_t *rrset, size_t pos)
+{
+	knot_rr_t *rr = knot_rrs_rr(&rrset->rrs, pos);
+	if (rr) {
+		return knot_rr_rdata(rr);
 	} else {
-		ERR_ALLOC_FAILED;
 		return NULL;
 	}
-
-	rrset->rr_count++;
-	rr_t *rr = get_rr(rrset, rrset->rr_count - 1);
-	assert(rr);
-
-	rr_set_size(rr, size);
-	rr_set_ttl(rr, ttl);
-
-	return rr_rdata(rr);
 }
 
-/*----------------------------------------------------------------------------*/
-
 uint16_t knot_rrset_rr_size(const knot_rrset_t *rrset, size_t pos)
 {
-	rr_t *rr = get_rr(rrset, pos);
+	const knot_rr_t *rr = knot_rrs_rr(&rrset->rrs, pos);
 	if (rr) {
-		return rr_size(rr);
+		return knot_rr_rdata_size(rr);
 	} else {
 		return 0;
 	}
@@ -740,9 +439,9 @@ uint16_t knot_rrset_rr_size(const knot_rrset_t *rrset, size_t pos)
 
 uint32_t knot_rrset_rr_ttl(const knot_rrset_t *rrset, size_t pos)
 {
-	rr_t *rr = get_rr(rrset, pos);
+	const knot_rr_t *rr = knot_rrs_rr(&rrset->rrs, pos);
 	if (rr) {
-		return rr_ttl(rr);
+		return knot_rr_ttl(rr);
 	} else {
 		return 0;
 	}
@@ -750,71 +449,19 @@ uint32_t knot_rrset_rr_ttl(const knot_rrset_t *rrset, size_t pos)
 
 void knot_rrset_rr_set_ttl(const knot_rrset_t *rrset, size_t pos, uint32_t ttl)
 {
-	rr_t *rr = get_rr(rrset, pos);
+	knot_rr_t *rr = knot_rrs_rr(&rrset->rrs, pos);
 	if (rr) {
-		rr_set_ttl(rr, ttl);
-	}
-}
-
-bool knot_rrset_ttl_equal(const knot_rrset_t *r1, const knot_rrset_t *r2)
-{
-	if (r1 == NULL || r2 == NULL
-	    || r1->rr_count == 0 || r2->rr_count == 0) {
-		return false;
+		knot_rr_set_ttl(rr, ttl);
 	}
-
-	return (knot_rrset_rr_ttl(r1, 0) == knot_rrset_rr_ttl(r2, 0));
-}
-
-const knot_dname_t *knot_rrset_owner(const knot_rrset_t *rrset)
-{
-	return rrset->owner;
 }
 
-knot_dname_t *knot_rrset_get_owner(const knot_rrset_t *rrset)
-{
-	return rrset->owner;
-}
-
-int knot_rrset_set_owner(knot_rrset_t *rrset, const knot_dname_t *owner)
+uint16_t knot_rrset_rr_count(const knot_rrset_t *rrset)
 {
 	if (rrset == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	/* Copy the new owner. */
-	knot_dname_t *owner_copy = NULL;
-	if (owner) {
-		owner_copy = knot_dname_copy(owner);
-		if (owner_copy == NULL) {
-			return KNOT_ENOMEM;
-		}
+		return 0;
 	}
 
-	/* Free old owner and assign. */
-	knot_dname_free(&rrset->owner);
-	rrset->owner = owner_copy;
-	return KNOT_EOK;
-}
-
-uint16_t knot_rrset_type(const knot_rrset_t *rrset)
-{
-	return rrset->type;
-}
-
-uint16_t knot_rrset_class(const knot_rrset_t *rrset)
-{
-	return rrset->rclass;
-}
-
-uint8_t *knot_rrset_rr_rdata(const knot_rrset_t *rrset, size_t pos)
-{
-	rr_t *rr = get_rr(rrset, pos);
-	if (rr) {
-		return rr_rdata(rr);
-	} else {
-		return NULL;
-	}
+	return rrset->rrs.rr_count;
 }
 
 int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, size_t *size,
@@ -858,14 +505,16 @@ int knot_rrset_rdata_from_wire_one(knot_rrset_t *rrset,
 	}
 
 	if (rdlength == 0) {
-		return knot_rrset_create_rr(rrset, 0, ttl, mm) == NULL ?
-		       KNOT_ENOMEM : KNOT_EOK;
+		// Alloc data for empty RR.
+		uint8_t *empty_rdata = malloc(1);
+		if (empty_rdata == NULL) {
+			return KNOT_ENOMEM;
+		}
+		int ret = knot_rrset_add_rr(rrset, empty_rdata, 0, ttl, mm);
+		free(empty_rdata);
+		return ret;
 	}
 
-	dbg_rrset_detail("rr: parse_rdata_wire: Parsing RDATA of size=%zu,"
-	                 " wire_size=%zu, type=%d.\n", rdlength, total_size,
-	                 rrset->type);
-
 	const rdata_descriptor_t *desc = get_rdata_descriptor(rrset->type);
 
 	/* Check for obsolete record. */
@@ -873,10 +522,6 @@ int knot_rrset_rdata_from_wire_one(knot_rrset_t *rrset,
 		desc = get_obsolete_rdata_descriptor(rrset->type);
 	}
 
-	/*! \todo This estimate is very rough - just to have enough space for
-	 *        possible unpacked dname. Should be later replaced by exact
-	 *        size counting.
-	 */
 	uint8_t rdata_buffer[rdlength + KNOT_DNAME_MAXLEN];
 	memset(rdata_buffer, 0, rdlength + KNOT_DNAME_MAXLEN);
 
@@ -884,13 +529,6 @@ int knot_rrset_rdata_from_wire_one(knot_rrset_t *rrset,
 	size_t parsed = 0; // actual count of parsed octets
 	const size_t packet_offset = *pos;
 
-	/*! \todo [RRSet refactor]
-	 *        This could be A LOT simpler - copy it as a whole,
-	 *        unpack dnames and just do some format checks if necessary.
-	 *        But it's questionable, if copying the memory when unpacking
-	 *        dnames, wouldn't be too expensive.
-	 */
-
 	for (int i = 0; desc->block_types[i] != KNOT_RDATA_WF_END &&
 	     parsed < rdlength; ++i) {
 		const int item = desc->block_types[i];
@@ -910,51 +548,21 @@ int knot_rrset_rdata_from_wire_one(knot_rrset_t *rrset,
 
 			parsed += wire_size;
 
-dbg_rrset_exec_detail(
-			dbg_rrset_detail("rr: parse_rdata_wire: Parsed DNAME, "
-			                 "length=%d.\n", wire_size);
-			char *name = knot_dname_to_str(rdata_buffer + offset);
-			dbg_rrset_detail("rr: parse_rdata_wire: Parsed "
-			                 "DNAME=%s\n", name);
-			free(name);
-);
 			*pos += wire_size;
 			offset += unpacked_size;
 		} else if (descriptor_item_is_fixed(item)) {
-			dbg_rrset_detail("rr: parse_rdata_wire: Saving static "
-			                 "chunk of size=%u\n", item);
-			int ret = knot_rrset_rdata_store_binary(rdata_buffer,
-			                                        &offset,
-			                                        packet_offset,
-			                                        wire,
-			                                        pos,
-			                                        rdlength,
-			                                        item);
+			int ret = binary_store(rdata_buffer, &offset, packet_offset,
+			                       wire, pos, rdlength, item);
 			if (ret != KNOT_EOK) {
-				dbg_rrset("rrset: rdata_from_wire: "
-				          "Cannot store fixed RDATA chunk. "
-				          "Reason: %s.\n", knot_strerror(ret));
 				return ret;
 			}
 			parsed += item;
 		} else if (descriptor_item_is_remainder(item)) {
 			/* Item size has to be calculated. */
 			size_t remainder_size = rdlength - parsed;
-			dbg_rrset_detail("rr: parse_rdata_wire: Saving remaining "
-			                 "chunk of size=%zu\n", remainder_size);
-			int ret = knot_rrset_rdata_store_binary(rdata_buffer,
-			                                        &offset,
-			                                        packet_offset,
-			                                        wire,
-			                                        pos,
-			                                        rdlength,
-			                                        remainder_size);
+			int ret = binary_store(rdata_buffer, &offset, packet_offset,
+			                       wire, pos, rdlength, remainder_size);
 			if (ret != KNOT_EOK) {
-				dbg_rrset("rrset: rdata_from_wire: "
-				          "Cannot store RDATA remainder of "
-				          "size=%zu, RDLENGTH=%zu. "
-				          "Reason: %s.\n", remainder_size,
-				          rdlength, knot_strerror(ret));
 				return ret;
 			}
 			parsed += remainder_size;
@@ -962,52 +570,43 @@ dbg_rrset_exec_detail(
 			assert(rrset->type == KNOT_RRTYPE_NAPTR);
 			/* Read fixed part - 2 shorts. */
 			const size_t naptr_fixed_part_size = 4;
-			int ret = knot_rrset_rdata_store_binary(rdata_buffer,
-			                                        &offset,
-			                                        packet_offset,
-			                                        wire,
-			                                        pos,
-			                                        rdlength,
-			                                        naptr_fixed_part_size);
+			int ret = binary_store(rdata_buffer, &offset, packet_offset,
+			                       wire, pos, rdlength, naptr_fixed_part_size);
 			if (ret != KNOT_EOK) {
-				dbg_rrset("rrset: rdata_from_wire: "
-				          "Cannot store NAPTR fixed part. "
-				          "Reason: %s.\n", knot_strerror(ret));
 				return ret;
 			}
 			parsed += naptr_fixed_part_size;
 			for (int j = 0; j < 3; ++j) {
 				/* Read sizes of TXT's - one byte. */
 				uint8_t txt_size = *(wire + (*pos)) + 1;
-				dbg_rrset_detail("rrset: rdata_from_wire: "
-				                 "Read TXT nr=%d size=%d\n", j,
-				                 txt_size);
-				int ret = knot_rrset_rdata_store_binary(rdata_buffer,
-				                                        &offset,
-				                                        packet_offset,
-				                                        wire,
-				                                        pos,
-				                                        rdlength,
-				                                        txt_size);
+				int ret = binary_store(rdata_buffer, &offset,
+				                       packet_offset, wire, pos,
+				                       rdlength, txt_size);
 				if (ret != KNOT_EOK) {
-					dbg_rrset("rrset: rdata_from_wire: "
-					          "Cannot store NAPTR TXTs. "
-					          "Reason: %s.\n", knot_strerror(ret));
-					return ret;
 				}
 				parsed += txt_size;
 			}
 		}
 	}
 
-	uint8_t *rdata = knot_rrset_create_rr(rrset, offset, ttl, mm);
-	if (rdata == NULL) {
-		return KNOT_ENOMEM;
+	return knot_rrset_add_rr(rrset, rdata_buffer, offset, ttl, mm);
+}
+
+int knot_rrset_add_rr(knot_rrset_t *rrset,
+                      const uint8_t *rdata, const uint16_t size,
+                      const uint32_t ttl, mm_ctx_t *mm)
+{
+	if (rrset == NULL || rdata == NULL) {
+		return KNOT_EINVAL;
 	}
 
-	memcpy(rdata, rdata_buffer, offset);
+	// Create knot_rr_t from given data
+	knot_rr_t rr[knot_rr_array_size(size)];
+	knot_rr_set_size(rr, size);
+	knot_rr_set_ttl(rr, ttl);
+	memcpy(knot_rr_rdata(rr), rdata, size);
 
-	return KNOT_EOK;
+	return knot_rrs_add_rr(&rrset->rrs, rr, mm);
 }
 
 bool knot_rrset_equal(const knot_rrset_t *r1,
@@ -1018,544 +617,24 @@ bool knot_rrset_equal(const knot_rrset_t *r1,
 		return r1 == r2;
 	}
 
-
-	if (!knot_dname_is_equal(r1->owner, r2->owner))
-		return false;
-
-	if (r1->rclass != r2->rclass || r1->type != r2->type)
+	if (r1->type != r2->type) {
 		return false;
-
-	if (cmp == KNOT_RRSET_COMPARE_WHOLE)
-		return knot_rrset_rdata_equal(r1, r2);
-
-	return true;
-}
-
-int knot_rrset_copy(const knot_rrset_t *from, knot_rrset_t **to, mm_ctx_t *mm)
-{
-	if (from == NULL || to == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	dbg_rrset_detail("rr: deep_copy: Copying RRs of type %d\n",
-	                 from->type);
-	*to = knot_rrset_new_from(from, mm);
-	if (*to == NULL) {
-		*to = NULL;
-		return KNOT_ENOMEM;
-	}
-
-	(*to)->additional = NULL; /* Never copy. */
-	(*to)->rr_count = from->rr_count;
-
-	(*to)->rrs = mm_alloc(mm, rrset_rdata_size_total(from));
-	if ((*to)->rrs == NULL) {
-		ERR_ALLOC_FAILED;
-		knot_rrset_free(to, mm);
-		return KNOT_ENOMEM;
-	}
-	memcpy((*to)->rrs, from->rrs, rrset_rdata_size_total(from));
-
-	return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static void rrset_deep_free_content(knot_rrset_t *rrset,
-                                    mm_ctx_t *mm)
-{
-	assert(rrset);
-
-	mm_free(mm, rrset->rrs);
-	knot_dname_free(&rrset->owner);
-}
-
-void knot_rrset_free(knot_rrset_t **rrset, mm_ctx_t *mm)
-{
-	if (rrset == NULL || *rrset == NULL) {
-		return;
-	}
-
-	rrset_deep_free_content(*rrset, mm);
-
-	if (rrset_additional_needed((*rrset)->type)) {
-		mm_free(mm, (*rrset)->additional);
-	}
-
-	mm_free(mm, *rrset);
-	*rrset = NULL;
-}
-
-static int knot_rrset_add_rr_n(knot_rrset_t *rrset, const knot_rrset_t *rr,
-                               size_t pos, mm_ctx_t *mm)
-{
-	if (rrset == NULL || rr == NULL) {
-		return KNOT_EINVAL;
-	}
-	if (!knot_rrset_equal(rrset, rr, KNOT_RRSET_COMPARE_HEADER)) {
-		// Adding to a different header
-		return KNOT_EINVAL;
-	}
-
-	uint32_t ttl = knot_rrset_rr_ttl(rr, pos);
-	uint16_t size = knot_rrset_rr_size(rr, pos);
-	uint8_t *new_rdata = knot_rrset_create_rr(rrset, size, ttl, mm);
-	if (new_rdata == NULL) {
-		return KNOT_ERROR;
-	}
-
-	memcpy(new_rdata, knot_rrset_rr_rdata(rr, pos),
-	       knot_rrset_rr_size(rr, pos));
-
-	return KNOT_EOK;
-}
-
-int knot_rrset_merge(knot_rrset_t *rrset1, const knot_rrset_t *rrset2,
-                     mm_ctx_t *mm)
-{
-	if (rrset2 == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	uint16_t r2_rdata_count = knot_rrset_rr_count(rrset2);
-	for (uint16_t i = 0; i < r2_rdata_count; ++i) {
-		int ret = knot_rrset_add_rr_n(rrset1, rrset2, i, mm);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	return KNOT_EOK;
-}
-
-static int knot_rrset_add_rr_sort_n(knot_rrset_t *rrset, const knot_rrset_t *rr,
-                                    int *merged, int *deleted, size_t pos,
-                                    mm_ctx_t *mm)
-{
-	if (rrset == NULL || rr == NULL) {
-		dbg_rrset("rrset: add_rr_sort: NULL arguments.");
-		return KNOT_EINVAL;
-	}
-
-dbg_rrset_exec_detail(
-	char *name = knot_dname_to_str(rrset->owner);
-	dbg_rrset_detail("rrset: add_rr_sort: Merging %s.\n", name);
-	free(name);
-);
-
-	if ((!knot_dname_is_equal(rrset->owner, rr->owner))
-	    || rrset->rclass != rr->rclass
-	    || rrset->type != rr->type) {
-		dbg_rrset("rrset: add_rr_sort: Trying to merge "
-		          "different RRs.\n");
-		return KNOT_EINVAL;
-	}
-
-	int found = 0;
-	int duplicated = 0;
-	// Compare RR with all RRs in the first RRSet.
-	size_t insert_to = 0;
-	uint16_t rdata_count = knot_rrset_rr_count(rrset);
-	for (uint16_t j = 0; j < rdata_count && (!duplicated && !found); ++j) {
-		int cmp = rrset_rdata_compare_one(rrset, rr, j, pos);
-		if (cmp == 0) {
-			// Duplication - no need to merge this RR
-			duplicated = 1;
-		} else if (cmp > 0) {
-			// Found position to insert
-			found = 1;
-		} else {
-			// Not yet - it might be next position
-			insert_to = j + 1;
-		}
-	}
-
-	if (!duplicated) {
-		*merged += 1; // = need to shallow free rrset2
-		// Insert RR to RRSet
-		int ret = knot_rrset_add_rr_at_pos(rrset, insert_to,
-		                                   knot_rrset_rr_rdata(rr, pos),
-		                                   knot_rrset_rr_size(rr, pos),
-		                                   knot_rrset_rr_ttl(rr, pos),
-		                                   mm);
-		if (ret != KNOT_EOK) {
-			dbg_rrset("rrset: add_rr: Could not "
-			          "add RDATA to RRSet. (%s)\n",
-			          knot_strerror(ret));
-			return ret;
-		}
-	} else {
-		assert(!found);
-		*deleted += 1; // = need to shallow free rr
-	}
-
-	return KNOT_EOK;
-}
-
-int knot_rrset_merge_sort(knot_rrset_t *rrset1, const knot_rrset_t *rrset2,
-                          int *merged_rrs, int *deleted_rrs, mm_ctx_t *mm)
-{
-	if (rrset2 == NULL) {
-		return KNOT_EINVAL;
-	}
-	int result = KNOT_EOK;
-	int merged = 0;
-	int deleted = 0;
-
-	uint16_t r2_rdata_count = knot_rrset_rr_count(rrset2);
-	for (uint16_t i = 0; i < r2_rdata_count; i++) {
-		result = knot_rrset_add_rr_sort_n(rrset1, rrset2, &merged,
-		                                  &deleted, i, mm);
-		if (result != KNOT_EOK) {
-			break;
-		}
-	}
-
-	if (merged_rrs) {
-		*merged_rrs = merged;
-	}
-
-	if (deleted_rrs) {
-		*deleted_rrs = deleted;
-	}
-
-	return result;
-}
-
-/*!
- * \todo Not optimal, rewrite!
- */
-int knot_rrset_sort_rdata(knot_rrset_t *rrset)
-{
-	if (!rrset) {
-		return KNOT_EINVAL;
-	}
-
-	// 1. create temporary rrset
-	// 2. sort-merge given rrset into temporary rrset
-	// 3. swap the contents, free the temporary
-
-	knot_rrset_t *sorted = knot_rrset_new_from(rrset, NULL);
-	if (!sorted) {
-		return KNOT_ENOMEM;
-	}
-
-	int result = knot_rrset_merge_sort(sorted, rrset, NULL, NULL, NULL);
-	if (result != KNOT_EOK) {
-		knot_rrset_free(&sorted, NULL);
-		return result;
-	}
-
-	rrset_deep_free_content(rrset, NULL);
-	*rrset = *sorted;
-	free(sorted);
-
-	return KNOT_EOK;
-}
-
-bool knot_rrset_is_nsec3rel(const knot_rrset_t *rr)
-{
-	assert(rr != NULL);
-
-	/* Is NSEC3 or non-empty RRSIG covering NSEC3. */
-	return ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
-	        || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
-	            && knot_rdata_rrsig_type_covered(rr, 0)
-	            == KNOT_RRTYPE_NSEC3));
-}
-
-uint64_t rrset_binary_size(const knot_rrset_t *rrset)
-{
-	if (rrset == NULL || knot_rrset_rr_count(rrset) == 0) {
-		return 0;
-	}
-	uint64_t size = sizeof(uint64_t) + // size at the beginning
-	              knot_dname_size(knot_rrset_owner(rrset)) + // owner data
-	              sizeof(uint16_t) + // type
-	              sizeof(uint16_t) + // class
-	              sizeof(uint16_t);  //RR count
-	uint16_t rdata_count = knot_rrset_rr_count(rrset);
-	for (uint16_t i = 0; i < rdata_count; i++) {
-		/* Space to store length of one RR. */
-		size += sizeof(uint32_t);
-		/* Actual data. */
-		size += rrset_binary_size_one(rrset, i);
-	}
-
-	return size;
-}
-
-int rrset_serialize(const knot_rrset_t *rrset, uint8_t *stream, size_t *size)
-{
-	if (rrset == NULL || rrset->rr_count == 0) {
-		return KNOT_EINVAL;
-	}
-
-	uint64_t rrset_length = rrset_binary_size(rrset);
-	dbg_rrset_detail("rr: serialize: Binary size=%"PRIu64"\n", rrset_length);
-	memcpy(stream, &rrset_length, sizeof(uint64_t));
-
-	size_t offset = sizeof(uint64_t);
-	/* Save RR count. */
-	memcpy(stream + offset, &rrset->rr_count, sizeof(uint16_t));
-	offset += sizeof(uint16_t);
-	/* Save owner. */
-	offset += knot_dname_to_wire(stream + offset, rrset->owner, rrset_length - offset);
-
-	/* Save static data. */
-	memcpy(stream + offset, &rrset->type, sizeof(uint16_t));
-	offset += sizeof(uint16_t);
-	memcpy(stream + offset, &rrset->rclass, sizeof(uint16_t));
-	offset += sizeof(uint16_t);
-
-	/* Copy RDATA. */
-	for (uint16_t i = 0; i < rrset->rr_count; i++) {
-		uint32_t rr_size = rrset_binary_size_one(rrset, i);
-		dbg_rrset_detail("rr: serialize: RR index=%d size=%d\n",
-		                 i, rr_size);
-		memcpy(stream + offset, &rr_size, sizeof(uint32_t));
-		offset += sizeof(uint32_t);
-		rrset_serialize_rr(rrset, i, stream + offset);
-		offset += rr_size;
-	}
-
-	*size = offset;
-	assert(*size == rrset_length);
-	dbg_rrset_detail("rr: serialize: RRSet serialized, size=%zu\n", *size);
-	return KNOT_EOK;
-}
-
-int rrset_deserialize(const uint8_t *stream, size_t *stream_size,
-                      knot_rrset_t **rrset)
-{
-	if (sizeof(uint64_t) > *stream_size) {
-		dbg_rrset("rr: deserialize: No space for length.\n");
-		return KNOT_ESPACE;
-	}
-	uint64_t rrset_length = 0;
-	memcpy(&rrset_length, stream, sizeof(uint64_t));
-	if (rrset_length > *stream_size) {
-		dbg_rrset("rr: deserialize: No space for whole RRSet. "
-		          "(given=%zu needed=%"PRIu64")\n", *stream_size,
-		          rrset_length);
-		return KNOT_ESPACE;
-	}
-
-	size_t offset = sizeof(uint64_t);
-	uint16_t rdata_count = 0;
-	memcpy(&rdata_count, stream + offset, sizeof(uint16_t));
-	offset += sizeof(uint16_t);
-	/* Read owner from the stream. */
-	unsigned owner_size = knot_dname_size(stream + offset);
-	knot_dname_t *owner = knot_dname_copy_part(stream + offset, owner_size);
-	assert(owner);
-	offset += owner_size;
-	/* Read type. */
-	uint16_t type = 0;
-	memcpy(&type, stream + offset, sizeof(uint16_t));
-	offset += sizeof(uint16_t);
-	/* Read class. */
-	uint16_t rclass = 0;
-	memcpy(&rclass, stream + offset, sizeof(uint16_t));
-	offset += sizeof(uint16_t);
-
-	/* Create new RRSet. */
-	*rrset = knot_rrset_new(owner, type, rclass, NULL);
-	if (*rrset == NULL) {
-		knot_dname_free(&owner);
-		return KNOT_ENOMEM;
-	}
-
-	/* Read RRs. */
-	for (uint16_t i = 0; i < rdata_count; i++) {
-		/*
-		 * There's always size of rdata in the beginning.
-		 * Needed because of remainders.
-		 */
-		uint32_t rdata_size = 0;
-		memcpy(&rdata_size, stream + offset, sizeof(uint32_t));
-		offset += sizeof(uint32_t);
-		int ret = rrset_deserialize_rr((*rrset), stream + offset,
-		                               rdata_size);
-		if (ret != KNOT_EOK) {
-			knot_rrset_free(rrset, NULL);
-			return ret;
-		}
-		offset += rdata_size;
-	}
-
-	*stream_size = *stream_size - offset;
-
-	return KNOT_EOK;
-}
-
-int knot_rrset_find_rr_pos(const knot_rrset_t *rr_search_in,
-                           const knot_rrset_t *rr_reference, size_t pos,
-                           size_t *pos_out)
-{
-	bool found = false;
-	uint16_t rr_count = knot_rrset_rr_count(rr_search_in);
-	for (uint16_t i = 0; i < rr_count && !found; ++i) {
-		if (rrset_rdata_compare_one(rr_search_in,
-		                            rr_reference, i, pos) == 0) {
-			*pos_out = i;
-			found = true;
-		}
-	}
-
-	return found ? KNOT_EOK : KNOT_ENOENT;
-}
-
-static int knot_rrset_remove_rr(knot_rrset_t *rrset,
-                                const knot_rrset_t *rr_from, size_t rdata_pos,
-                                mm_ctx_t *mm)
-{
-	/*
-	 * Position in first and second rrset can differ, we have
-	 * to search for position first.
-	 */
-	size_t pos_to_remove = 0;
-	int ret = knot_rrset_find_rr_pos(rrset, rr_from, rdata_pos,
-	                                 &pos_to_remove);
-	if (ret == KNOT_EOK) {
-		/* Position found, can be removed. */
-		dbg_rrset_detail("rr: remove_rr: Counter position found=%zu\n",
-		                 pos_to_remove);
-		assert(pos_to_remove < knot_rrset_rr_count(rrset));
-		ret = knot_rrset_remove_rdata_pos(rrset, pos_to_remove, mm);
-		if (ret != KNOT_EOK) {
-			dbg_rrset("Cannot remove RDATA from RRSet (%s).\n",
-			          knot_strerror(ret));
-			return ret;
-		}
-	} else {
-		dbg_rrset_verb("rr: remove_rr: RDATA not found (%s).\n",
-		               knot_strerror(ret));
-		return ret;
-	}
-
-	return KNOT_EOK;
-}
-
-int knot_rrset_add_rr_from_rrset(knot_rrset_t *dest, const knot_rrset_t *source,
-                                 size_t rdata_pos, mm_ctx_t *mm)
-{
-	if (dest == NULL || source == NULL ||
-	    rdata_pos >= knot_rrset_rr_count(source)) {
-		return KNOT_EINVAL;
-	}
-
-	/* Get size and TTL of RR to be copied. */
-	uint16_t size = knot_rrset_rr_size(source, rdata_pos);
-	uint32_t ttl = knot_rrset_rr_ttl(source, rdata_pos);
-	/* Reserve space in dest RRSet. */
-	uint8_t *rdata = knot_rrset_create_rr(dest, size, ttl, mm);
-	if (rdata == NULL) {
-		dbg_rrset("rr: add_rr_from_rrset: Could not create RDATA.\n");
-		return KNOT_ERROR;
-	}
-
-	/* Copy actual data. */
-	memcpy(rdata, knot_rrset_rr_rdata(source, rdata_pos), size);
-
-	return KNOT_EOK;
-}
-
-int knot_rrset_remove_rr_using_rrset(knot_rrset_t *from,
-                                     const knot_rrset_t *what,
-                                     knot_rrset_t **rr_deleted,
-                                     mm_ctx_t *mm)
-{
-	if (from == NULL || what == NULL || rr_deleted == NULL) {
-		return KNOT_EINVAL;
 	}
 
-	knot_rrset_t *return_rr = knot_rrset_new_from(what, NULL);
-	if (return_rr == NULL) {
-		return KNOT_ENOMEM;
+	if (!knot_dname_is_equal(r1->owner, r2->owner)) {
+		return false;
 	}
 
-	uint16_t what_rdata_count = knot_rrset_rr_count(what);
-	for (uint16_t i = 0; i < what_rdata_count; ++i) {
-		int ret = knot_rrset_remove_rr(from, what, i, mm);
-		if (ret == KNOT_EOK) {
-			/* RR was removed, can be added to 'return' RRSet. */
-			ret = knot_rrset_add_rr_from_rrset(return_rr, what, i, NULL);
-			if (ret != KNOT_EOK) {
-				knot_rrset_free(&return_rr, NULL);
-				dbg_xfrin("xfr: Could not add RR (%s).\n",
-				          knot_strerror(ret));
-				return ret;
-			}
-		} else if (ret != KNOT_ENOENT) {
-			/* NOENT is OK, but other errors are not. */
-			dbg_rrset("rrset: remove_using_rrset: "
-			          "RRSet removal failed (%s).\n",
-			          knot_strerror(ret));
-			knot_rrset_free(&return_rr, NULL);
-			return ret;
-		}
+	if (cmp == KNOT_RRSET_COMPARE_WHOLE) {
+		return knot_rrs_eq(&r1->rrs, &r2->rrs);
 	}
 
-	*rr_deleted = return_rr;
-	return KNOT_EOK;
-}
-
-int rrset_additional_needed(uint16_t rrtype)
-{
-	return (rrtype == KNOT_RRTYPE_NS ||
-		rrtype == KNOT_RRTYPE_MX ||
-		rrtype == KNOT_RRTYPE_SRV);
-}
-
-static int add_rdata_to_rrsig(knot_rrset_t *new_sig, uint16_t type,
-                              const knot_rrset_t *rrsigs, mm_ctx_t *mm)
-{
-	uint16_t rrsigs_rdata_count = knot_rrset_rr_count(rrsigs);
-	for (uint16_t i = 0; i < rrsigs_rdata_count; ++i) {
-		const uint16_t type_covered =
-			knot_rdata_rrsig_type_covered(rrsigs, i);
-		if (type_covered == type) {
-			int ret = knot_rrset_add_rr_from_rrset(new_sig, rrsigs,
-			                                       i, mm);
-			if (ret != KNOT_EOK) {
-				return ret;
-			}
-		}
-	}
-
-	return knot_rrset_rr_count(new_sig) > 0 ? KNOT_EOK : KNOT_ENOENT;
+	return true;
 }
 
-int knot_rrset_synth_rrsig(const knot_dname_t *owner, uint16_t type,
-                           const knot_rrset_t *rrsigs,
-                           knot_rrset_t **out_sig, mm_ctx_t *mm)
+bool knot_rrset_empty(const knot_rrset_t *rrset)
 {
-	if (rrsigs == NULL) {
-		return KNOT_ENOENT;
-	}
-
-	if (out_sig == NULL || owner == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	knot_dname_t *owner_copy = knot_dname_copy(owner);
-	if (owner_copy == NULL) {
-		return KNOT_ENOMEM;
-	}
-	*out_sig = knot_rrset_new(owner_copy,
-	                          KNOT_RRTYPE_RRSIG, rrsigs->rclass, mm);
-	if (*out_sig == NULL) {
-		knot_dname_free(&owner_copy);
-		return KNOT_ENOMEM;
-	}
-
-	int ret = add_rdata_to_rrsig(*out_sig, type, rrsigs, mm);
-	if (ret != KNOT_EOK) {
-		knot_rrset_free(out_sig, mm);
-		return ret;
-	}
-
-	return KNOT_EOK;
+	uint16_t rr_count = knot_rrset_rr_count(rrset);
+	return rr_count == 0;
 }
 
diff --git a/src/libknot/rrset.h b/src/libknot/rrset.h
index f9fd04d426a6a7d63177c9e182b7dcc875879c10..706bc298819e9c7e3eb6a5f9ef3ee324d73129fe 100644
--- a/src/libknot/rrset.h
+++ b/src/libknot/rrset.h
@@ -25,8 +25,7 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef _KNOT_RRSET_H_
-#define _KNOT_RRSET_H_
+#pragma once
 
 #include <stdint.h>
 #include <stdbool.h>
@@ -34,48 +33,34 @@
 #include "common/mempattern.h"
 
 #include "libknot/dname.h"
+#include "libknot/rr.h"
 
 struct knot_compr;
 struct knot_node;
 
-/*----------------------------------------------------------------------------*/
 /*!
- * \brief Structure for representing an RRSet.
+ * \brief Structure for representing RRSet.
  *
- * For definition of a RRSet see RFC2181, Section 5.
+ * For RRSet definition see RFC2181, Section 5.
  */
 struct knot_rrset {
 	knot_dname_t *owner;  /*!< Domain name being the owner of the RRSet. */
 	uint16_t type;        /*!< TYPE of the RRset. */
 	uint16_t rclass;      /*!< CLASS of the RRSet. */
-	uint16_t rr_count;    /*!< Count of RRs in RRSet */
-	/*! \brief RDATA array (for all RRs). DNAMEs stored as full,
-	 *         uncompressed wire. Binary data stored in wireformat order.
-	 *         Each RR is prepended with RDATA length and TTL, respectively.
-	 *         Do not work directly with this value.
-	 */
-	uint8_t *rrs;
+	knot_rrs_t rrs;       /*!< RRSet's RRs */
 	/* Optional fields. */
 	struct knot_node **additional; /*!< Additional records. */
 };
 
 typedef struct knot_rrset knot_rrset_t;
 
-/*----------------------------------------------------------------------------*/
-
 typedef enum {
 	KNOT_RRSET_COMPARE_PTR,
 	KNOT_RRSET_COMPARE_HEADER,
 	KNOT_RRSET_COMPARE_WHOLE
 } knot_rrset_compare_type_t;
 
-typedef enum  {
-	KNOT_RRSET_DUPL_MERGE,
-	KNOT_RRSET_DUPL_REPLACE,
-	KNOT_RRSET_DUPL_SKIP
-} knot_rrset_dupl_handling_t;
-
-/*----------------------------------------------------------------------------*/
+/* -------------------- Creation / initialization --------------------------- */
 
 /*!
  * \brief Creates a new RRSet with the given properties.
@@ -88,96 +73,59 @@ typedef enum  {
  *
  * \return New RRSet structure or NULL if an error occured.
  */
-knot_rrset_t *knot_rrset_new(knot_dname_t *owner, uint16_t type,
-                             uint16_t rclass,
-                             mm_ctx_t *mm);
+knot_rrset_t *knot_rrset_new(const knot_dname_t *owner, uint16_t type,
+                             uint16_t rclass, mm_ctx_t *mm);
 
 /*!
- * \brief Creates a new RRSet according to given template RRSet.
- *
- * OWNER, TYPE, CLASS, and TTL values from template RRSet are used.
+ * \brief Initializes RRSet structure with given data.
  *
- * \param tmp  RRSet template.
- *
- * \return New RRSet, NULL if an error occured.
+ * \param rrset   RRSet to init.
+ * \param owner   RRSet owner to use.
+ * \param type    RR type to use.
+ * \param rclass  Class to use.
  */
-knot_rrset_t *knot_rrset_new_from(const knot_rrset_t *tpl, mm_ctx_t *mm);
+void knot_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner, uint16_t type,
+                     uint16_t rclass);
 
 /*!
- * \brief Adds the given RDATA to the RRSet.
- *
- * \param rrset  RRSet to add the RDATA to.
- * \param rdata  RDATA to add to the RRSet.
- * \param size   Size of RDATA.
- * \param size   TTL for RR.
- * \param mm     Memory context.
- *
- * \return KNOT_E*
- */
-int knot_rrset_add_rr(knot_rrset_t *rrset, const uint8_t *rdata,
-                      const uint16_t size, const uint32_t ttl,
-                      mm_ctx_t *mm);
-
-/*!
- * \brief Creates RDATA memory and returns a pointer to it.
- *        If the RRSet is not empty, function will return a memory
- *        pointing to a beginning of a new RR.
- *
- * \param rrset  RRSet to add the RDATA to.
- * \param size   Size of RR RDATA (Size in internal representation)
- * \param mm     Memory context.
+ * \brief Initializes given RRSet structure.
  *
- * \retval  Pointer to memory to be written to.
- * \retval  NULL if arguments are invalid / no memory.
+ * \param rrset  RRSet to init.
  */
-uint8_t* knot_rrset_create_rr(knot_rrset_t *rrset, const uint16_t size,
-                              const uint32_t ttl, mm_ctx_t *mm);
+void knot_rrset_init_empty(knot_rrset_t *rrset);
 
 /*!
- * \brief Returns the Owner of the RRSet.
+ * \brief Creates new RRSet from \a src RRSet.
  *
- * \param rrset  RRSet to get the Owner of.
+ * \param src  Source RRSet.
+ * \param mm   Memory context.
  *
- * \return Owner of the given RRSet.
+ * \retval Pointer to new RRSet if all went OK.
+ * \retval NULL on error.
  */
-const knot_dname_t *knot_rrset_owner(const knot_rrset_t *rrset);
+knot_rrset_t *knot_rrset_copy(const knot_rrset_t *src, mm_ctx_t *mm);
 
-/*!
- * \brief Returns the Owner of the RRSet.
- *
- * \param rrset  RRSet to get the Owner of.
- *
- * \return Owner of the given RRSet.
- */
-knot_dname_t *knot_rrset_get_owner(const knot_rrset_t *rrset);
+/* ---------------------------- Cleanup ------------------------------------- */
 
 /*!
- * \brief Set rrset owner to specified dname.
- *
- * Previous owner will be replaced if exist.
+ * \brief Destroys the RRSet structure and all its substructures.
+ )
+ * Also sets the given pointer to NULL.
  *
- * \param rrset  Specified RRSet.
- * \param owner  New owner dname.
+ * \param rrset  RRset to be destroyed.
+ * \param mm     Memory context.
  */
-int knot_rrset_set_owner(knot_rrset_t *rrset, const knot_dname_t *owner);
+void knot_rrset_free(knot_rrset_t **rrset, mm_ctx_t *mm);
 
 /*!
- * \brief Returns the TYPE of the RRSet.
+ * \brief Frees structures inside RRSet, but not the RRSet itself.
  *
- * \param rrset  RRSet to get the TYPE of.
- *
- * \return TYPE  of the given RRSet.
+ * \param rrset  RRSet to be cleared.
+ * \param mm     Memory context used for allocations.
  */
-uint16_t knot_rrset_type(const knot_rrset_t *rrset);
+void knot_rrset_clear(knot_rrset_t *rrset, mm_ctx_t *mm);
 
-/*!
- * \brief Returns the CLASS of the RRSet.
- *
- * \param rrset  RRSet to get the CLASS of.
- *
- * \return CLASS of the given RRSet.
- */
-uint16_t knot_rrset_class(const knot_rrset_t *rrset);
+/* ----------- Getters / Setters (legacy, functionality in rr_t) ------------ */
 
 /*!
  * \brief Returns RDATA of RR on given position.
@@ -219,17 +167,6 @@ uint32_t knot_rrset_rr_ttl(const knot_rrset_t *rrset, size_t pos);
  */
 void knot_rrset_rr_set_ttl(const knot_rrset_t *rrset, size_t pos, uint32_t ttl);
 
-/*!
- * \brief Compares TTL of first RRs of two RRSets.
- *
- * \param r1 First RRSet.
- * \param r2 Second RRSet.
- *
- * \retval True if TTLs are equal.
- * \retval False otherwise.
- */
-bool knot_rrset_ttl_equal(const knot_rrset_t *r1, const knot_rrset_t *r2);
-
 /*!
  * \brief Returns count of RRs in RRSet.
  *
@@ -239,40 +176,7 @@ bool knot_rrset_ttl_equal(const knot_rrset_t *r1, const knot_rrset_t *r2);
  */
 uint16_t knot_rrset_rr_count(const knot_rrset_t *rrset);
 
-/*!
- * \brief Compares two RRSets for equality.
- *
- * \param r1   First RRSet.
- * \param r2   Second RRSet.
- * \param cmp  Type of comparison to perform.
- *
- * \retval True   if RRSets are equal.
- * \retval False  if RRSets are not equal.
- */
-bool knot_rrset_equal(const knot_rrset_t *r1,
-                      const knot_rrset_t *r2,
-                      knot_rrset_compare_type_t cmp);
-
-/*!
- * \brief Complete copy of RRSet structure.
- *
- * \param from  Source data.
- * \param to    Destionation data.
- * \param mm    Memory context.
- *
- * \return KNOT_E*
- */
-int knot_rrset_copy(const knot_rrset_t *from, knot_rrset_t **to, mm_ctx_t *mm);
-
-/*!
- * \brief Destroys the RRSet structure and all its substructures.
- )
- * Also sets the given pointer to NULL.
- *
- * \param rrset  RRset to be destroyed.
- * \param mm     Memory context.
- */
-void knot_rrset_free(knot_rrset_t **rrset, mm_ctx_t *mm);
+/* ---------- Wire conversions (legacy, to be done in knot_pkt_t) ----------- */
 
 /*!
  * \brief Converts RRSet structure to wireformat, compression included.
@@ -289,124 +193,6 @@ void knot_rrset_free(knot_rrset_t **rrset, mm_ctx_t *mm);
 int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, size_t *size,
                        size_t max_size, uint16_t *rr_count, struct knot_compr *compr);
 
-/*!
- * \brief Merges two RRSets, no duplicate check is done, no sorting either.
- *
- * \param r1  Pointer to RRSet to be merged into.
- * \param r2  Pointer to RRSet to be merged.
- * \param mm  Memory context.
- *
- * \return KNOT_E*
- */
-int knot_rrset_merge(knot_rrset_t *rrset1, const knot_rrset_t *rrset2,
-                     mm_ctx_t *mm);
-
-/*!
- * \brief Merges two RRSets, duplicate check is done, preserves canonical ordering.
- *
- * \param r1           Pointer to RRSet to be merged into.
- * \param r2           Pointer to RRSet to be merged.
- * \param merged       Count of merged RRs.
- * \param deleted_rrs  Count of deleted duplicated RRs.
- * \param mm           Memory context.
- *
- * \return KNOT_E*
- */
-int knot_rrset_merge_sort(knot_rrset_t *rrset1, const knot_rrset_t *rrset2,
-                          int *merged, int *deleted_rrs, mm_ctx_t *mm);
-
-/*!
- * \brief Sort RDATA in RRSet to be in canonical order.
- * \todo Not optimal, rewrite!
- *
- * \param rrset  RRSet to be sorted.
- * \return Error code, KNOT_EOK when successful.
- */
-int knot_rrset_sort_rdata(knot_rrset_t *rrset);
-
-/*!
- * \brief Return true if the RRSet is an NSEC3 related type.
- *
- * \param rr RRSet.
- */
-bool knot_rrset_is_nsec3rel(const knot_rrset_t *rr);
-
-/*!
- * \brief Returns binary size of RRSet.
- *
- * \param rrset  RRSet.
- *
- * \return  Binary size.
- */
-uint64_t rrset_binary_size(const knot_rrset_t *rrset);
-
-/*!
- * \brief Serializes RRSet into given stream.
- *
- * \param rrset   RRSet to be serialized.
- * \param stream  Output stream
- * \param size    Size written.
- *
- * \return KNOT_E*
- */
-int rrset_serialize(const knot_rrset_t *rrset, uint8_t *stream, size_t *size);
-
-/*!
- * \brief Deserializes RRSet structure.
- *
- * \param stream       Input stream.
- * \param stream_size  Input stream size.
- * \param rrset        Output RRSet.
- *
- * \return KNOT_E*
- */
-int rrset_deserialize(const uint8_t *stream, size_t *stream_size,
-                      knot_rrset_t **rrset);
-
-/*!
- * \brief Adds RR on 'pos' position from 'source' to 'dest'.
- *
- * \param dest       Destination RRSet.
- * \param source     Source RRSet.
- * \param rdata_pos  RR position from 'source' to add to 'dest'.
- * \param mm         Memory context.
- *
- * \return KNOT_E*
- */
-int knot_rrset_add_rr_from_rrset(knot_rrset_t *dest, const knot_rrset_t *source,
-                                 size_t rdata_pos, mm_ctx_t *mm);
-
-/*!
- * \brief Removes RRs contained in 'what' RRSet from 'from' RRSet.
- *        Deleted RRs are returned in 'rr_deleted'.
- *
- * \param from        Delete from.
- * \param what        Delete what.
- * \param rr_deleted  Deleted RRs stored here.
- * \param mm          Memory context.
- *
- * \return KNOT_E*
- */
-int knot_rrset_remove_rr_using_rrset(knot_rrset_t *from,
-                                     const knot_rrset_t *what,
-                                     knot_rrset_t **rr_deleted, mm_ctx_t *mm);
-
-/*!
- * \brief Finds RR at 'pos' position in 'rr_reference' RRSet in
- *        'rr_search_in' RRSet. Position returned in 'pos_out'.
- *
- * \param rr_search_in  Search in this RRSet.
- * \param rr_reference  Use RR from this RRSet.
- * \param pos           Index for 'rr_reference'.
- * \param pos_out       If found, position returned here.
- *
- * \retval KNOT_EOK     if found, pos_out is set.
- * \retval KNOT_ENOENT  if not found.
- */
-int knot_rrset_find_rr_pos(const knot_rrset_t *rr_search_in,
-                           const knot_rrset_t *rr_reference, size_t pos,
-                           size_t *pos_out);
-
  /*!
  * \brief Creates one RR from wire, stores it into 'rrset'
  *
@@ -425,33 +211,46 @@ int knot_rrset_rdata_from_wire_one(knot_rrset_t *rrset,
                                    size_t total_size, uint32_t ttl, size_t rdlength,
                                    mm_ctx_t *mm);
 
+/* ---------- RR addition. (legacy, functionality in knot_rrs_t) ------------ */
+
 /*!
- * \brief Checks whether the given type requires additional processing.
- *
- * Only MX, NS and SRV types require additional processing.
+ * \brief Adds the given RDATA to the RRSet.
  *
- * \param rrtype Type to check.
+ * \param rrset  RRSet to add the RDATA to.
+ * \param rdata  RDATA to add to the RRSet.
+ * \param size   Size of RDATA.
+ * \param ttl   TTL for RR.
+ * \param mm     Memory context.
  *
- * \retval <> 0 if additional processing is needed for \a qtype.
- * \retval 0 otherwise.
+ * \return KNOT_E*
  */
-int rrset_additional_needed(uint16_t rrtype);
+int knot_rrset_add_rr(knot_rrset_t *rrset, const uint8_t *rdata,
+                      const uint16_t size, const uint32_t ttl,
+                      mm_ctx_t *mm);
+
+/* ------------------ Equality / emptines bool checks ----------------------- */
 
 /*!
- * \brief Creates RRSIG record from node RRSIGs for given RRSet.
+ * \brief Compares two RRSets for equality.
  *
- * \param owner    Owner to use for the RRSIG.
- * \param type     Type to cover.
- * \param rrsigs   Node RRSIGs.
- * \param out_sig  Output RRSIG.
- * \param mm       Memory context.
+ * \param r1   First RRSet.
+ * \param r2   Second RRSet.
+ * \param cmp  Type of comparison to perform.
  *
- * \return KNOT_E*
+ * \retval True   if RRSets are equal.
+ * \retval False  if RRSets are not equal.
  */
-int knot_rrset_synth_rrsig(const knot_dname_t *owner, uint16_t type,
-                           const knot_rrset_t *rrsigs,
-                           knot_rrset_t **out_sig, mm_ctx_t *mm);
+bool knot_rrset_equal(const knot_rrset_t *r1, const knot_rrset_t *r2,
+                      knot_rrset_compare_type_t cmp);
 
-#endif /* _KNOT_RRSET_H_ */
+/*!
+ * \brief Checks whether RRSet is empty.
+ *
+ * \param rrset  RRSet to check.
+ *
+ * \retval True if RRSet is empty.
+ * \retval False if RRSet is not empty.
+ */
+bool knot_rrset_empty(const knot_rrset_t *rrset);
 
 /*! @} */
diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c
index 1fddda11cb024a579e8b349b35479a06242638a7..6ff78c9eb86d5dbd4911b568207942a5143368eb 100644
--- a/src/libknot/tsig-op.c
+++ b/src/libknot/tsig-op.c
@@ -61,7 +61,7 @@ static int knot_tsig_check_key(const knot_rrset_t *tsig_rr,
 		return KNOT_EINVAL;
 	}
 
-	const knot_dname_t *tsig_name = knot_rrset_owner(tsig_rr);
+	const knot_dname_t *tsig_name = tsig_rr->owner;
 	if (!tsig_name) {
 		return KNOT_EMALF;
 	}
@@ -195,7 +195,7 @@ static int knot_tsig_write_tsig_variables(uint8_t *wire,
 	}
 
 	/* Copy TSIG variables - starting with key name. */
-	const knot_dname_t *tsig_owner = knot_rrset_owner(tsig_rr);
+	const knot_dname_t *tsig_owner = tsig_rr->owner;
 	if (!tsig_owner) {
 		dbg_tsig("TSIG: write variables: no owner.\n");
 		return KNOT_EINVAL;
@@ -208,9 +208,9 @@ static int knot_tsig_write_tsig_variables(uint8_t *wire,
 	/*!< \todo which order? */
 
 	/* Copy class. */
-	knot_wire_write_u16(wire + offset, knot_rrset_class(tsig_rr));
+	knot_wire_write_u16(wire + offset, tsig_rr->rclass);
 	dbg_tsig_verb("TSIG: write variables: written CLASS: %u - \n",
-	               knot_rrset_class(tsig_rr));
+	               tsig_rr->rclass);
 	dbg_tsig_hex_detail((char *)(wire + offset), sizeof(uint16_t));
 	offset += sizeof(uint16_t);
 
@@ -449,18 +449,11 @@ int knot_tsig_sign(uint8_t *msg, size_t *msg_len,
 		return KNOT_EINVAL;
 	}
 
-	knot_dname_t *key_name_copy = knot_dname_copy(key->name);
-	if (!key_name_copy) {
-		dbg_tsig("TSIG: key_name_copy = NULL\n");
-		return KNOT_ENOMEM;
-	}
-
 	knot_rrset_t *tmp_tsig =
-		knot_rrset_new(key_name_copy,
-			       KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY, NULL);
+		knot_rrset_new(key->name, KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY,
+		               NULL);
 	if (!tmp_tsig) {
 		dbg_tsig("TSIG: tmp_tsig = NULL\n");
-		knot_dname_free(&key_name_copy);
 		return KNOT_ENOMEM;
 	}
 
@@ -556,15 +549,9 @@ int knot_tsig_sign_next(uint8_t *msg, size_t *msg_len, size_t msg_max_len,
 
 	uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE];
 	size_t digest_tmp_len = 0;
-
-	/* Create tmp TSIG. */
-	knot_dname_t *owner_copy = knot_dname_copy(key->name);
-	knot_rrset_t *tmp_tsig = knot_rrset_new(owner_copy,
-	                                        KNOT_RRTYPE_TSIG,
-	                                        KNOT_CLASS_ANY,
-	                                        NULL);
+	knot_rrset_t *tmp_tsig = knot_rrset_new(key->name, KNOT_RRTYPE_TSIG,
+	                                        KNOT_CLASS_ANY, NULL);
 	if (!tmp_tsig) {
-		knot_dname_free(&owner_copy);
 		return KNOT_ENOMEM;
 	}
 
@@ -711,7 +698,6 @@ static int knot_tsig_check_digest(const knot_rrset_t *tsig_rr,
 	memset(wire_to_sign, 0, sizeof(uint8_t) * size);
 	memcpy(wire_to_sign, wire, size);
 
-
 	uint8_t digest_tmp[KNOT_TSIG_MAX_DIGEST_SIZE];
 	size_t digest_tmp_len = 0;
 	assert(knot_rrset_rr_count(tsig_rr) > 0);
@@ -814,19 +800,11 @@ int knot_tsig_add(uint8_t *msg, size_t *msg_len, size_t msg_max_len,
 	}
 
 	/*! \todo What key to use, when we do not sign? Does this even work? */
-	knot_dname_t *key_name =
-			knot_dname_copy(knot_rrset_owner(tsig_rr));
-	if (key_name == NULL) {
-		dbg_tsig("TSIG: failed to copy owner\n");
-		return KNOT_ERROR;
-	}
-
 	knot_rrset_t *tmp_tsig =
-		knot_rrset_new(key_name, KNOT_RRTYPE_TSIG, KNOT_CLASS_ANY,
-		               NULL);
+		knot_rrset_new(tsig_rr->owner, KNOT_RRTYPE_TSIG,
+		               KNOT_CLASS_ANY, NULL);
 	if (!tmp_tsig) {
 		dbg_tsig("TSIG: tmp_tsig = NULL\n");
-		knot_dname_free(&key_name);
 		return KNOT_ENOMEM;
 	}
 
@@ -842,11 +820,9 @@ int knot_tsig_add(uint8_t *msg, size_t *msg_len, size_t msg_max_len,
 	/* Set original ID */
 	tsig_rdata_set_orig_id(tmp_tsig, knot_wire_get_id(msg));
 
-
 	/* Set other len. */
 	tsig_rdata_set_other_data(tmp_tsig, 0, 0);
 
-
 	/* Append TSIG RR. */
 	int ret = knot_tsig_append(msg, msg_len, msg_max_len, tsig_rr);
 
diff --git a/src/libknot/tsig.c b/src/libknot/tsig.c
index 50d4f571b9614ff9e7670bf9a790de536157c1e2..c7c8ae75162a676f9198bfab2da6be7828edda64 100644
--- a/src/libknot/tsig.c
+++ b/src/libknot/tsig.c
@@ -132,15 +132,20 @@ int tsig_create_rdata(knot_rrset_t *rr, const knot_dname_t *alg, uint16_t maclen
 	if (tsig_err != KNOT_RCODE_BADTIME) {
 		rdlen -= TSIG_OTHER_MAXLEN;
 	}
-	uint8_t *rd = knot_rrset_create_rr(rr, rdlen, 0, NULL);
+	uint8_t rd[rdlen];
 	memset(rd, 0, rdlen);
 
 	/* Copy alg name. */
 	knot_dname_to_wire(rd, alg, rdlen);
 
 	/* Set MAC variable length in advance. */
-	rd += alg_len + TSIG_OFF_MACLEN;
-	knot_wire_write_u16(rd, maclen);
+	size_t offset = alg_len + TSIG_OFF_MACLEN;
+	knot_wire_write_u16(rd + offset, maclen);
+
+	int ret = knot_rrset_add_rr(rr, rd, rdlen, 0, NULL);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
 
 	/* Set error. */
 	tsig_rdata_set_tsig_error(rr, tsig_err);
@@ -352,7 +357,7 @@ size_t tsig_rdata_tsig_variables_length(const knot_rrset_t *tsig)
 		return 0;
 	}
 	/* Key name, Algorithm name and Other data have variable lengths. */
-	const knot_dname_t *key_name = knot_rrset_owner(tsig);
+	const knot_dname_t *key_name = tsig->owner;
 	if (!key_name) {
 		return 0;
 	}
@@ -374,7 +379,6 @@ size_t tsig_rdata_tsig_timers_length()
 	return KNOT_TSIG_TIMERS_LENGTH;
 }
 
-
 int tsig_rdata_store_current_time(knot_rrset_t *tsig)
 {
 	if (!tsig) {
@@ -444,7 +448,7 @@ size_t tsig_wire_actsize(const knot_rrset_t *tsig)
 	}
 
 	/*! \todo Used fixed size as a base. */
-	return knot_dname_size(knot_rrset_owner(tsig)) +
+	return knot_dname_size(tsig->owner) +
 	sizeof(uint16_t) + /* TYPE */
 	sizeof(uint16_t) + /* CLASS */
 	sizeof(uint32_t) + /* TTL */
diff --git a/src/utils/common/exec.c b/src/utils/common/exec.c
index 46288311b7d8db3b1baef4a2a83935b11eaaed9a..a336fc489ac96a96999b59d2968bd7560aabaf21 100644
--- a/src/utils/common/exec.c
+++ b/src/utils/common/exec.c
@@ -199,10 +199,7 @@ static void print_section_question(const knot_dname_t *owner,
 {
 	size_t buflen = 8192;
 	char   *buf = calloc(buflen, 1);
-
-	knot_dname_t *owner_copy = knot_dname_copy(owner);
-	knot_rrset_t *question = knot_rrset_new(owner_copy, qtype,
-	                                        qclass, NULL);
+	knot_rrset_t *question = knot_rrset_new(owner, qtype, qclass, NULL);
 
 	if (knot_rrset_txt_dump_header(question, 0, buf, buflen,
 	    &(style->style)) < 0) {
@@ -215,7 +212,7 @@ static void print_section_question(const knot_dname_t *owner,
 	free(buf);
 }
 
-static void print_section_full(const knot_rrset_t **rrsets,
+static void print_section_full(const knot_rrset_t *rrsets,
                                const uint16_t     count,
                                const style_t      *style)
 {
@@ -223,11 +220,11 @@ static void print_section_full(const knot_rrset_t **rrsets,
 	char   *buf = calloc(buflen, 1);
 
 	for (size_t i = 0; i < count; i++) {
-		if (rrsets[i]->type == KNOT_RRTYPE_OPT) {
+		if (rrsets[i].type == KNOT_RRTYPE_OPT) {
 			continue;
 		}
 
-		while (knot_rrset_txt_dump(rrsets[i], buf, buflen,
+		while (knot_rrset_txt_dump(&rrsets[i], buf, buflen,
 		                           &(style->style)) < 0) {
 			buflen += 4096;
 			// Oversize protection.
@@ -249,7 +246,7 @@ static void print_section_full(const knot_rrset_t **rrsets,
 	free(buf);
 }
 
-static void print_section_dig(const knot_rrset_t **rrsets,
+static void print_section_dig(const knot_rrset_t *rrsets,
                               const uint16_t     count,
                               const style_t      *style)
 {
@@ -257,7 +254,7 @@ static void print_section_dig(const knot_rrset_t **rrsets,
 	char   *buf = calloc(buflen, 1);
 
 	for (size_t i = 0; i < count; i++) {
-		const knot_rrset_t *rrset = rrsets[i];
+		const knot_rrset_t *rrset = &rrsets[i];
 		uint16_t rrset_rdata_count = knot_rrset_rr_count(rrset);
 		for (uint16_t j = 0; j < rrset_rdata_count; j++) {
 			while (knot_rrset_txt_dump_data(rrset, j, buf, buflen,
@@ -283,7 +280,7 @@ static void print_section_dig(const knot_rrset_t **rrsets,
 	free(buf);
 }
 
-static void print_section_host(const knot_rrset_t **rrsets,
+static void print_section_host(const knot_rrset_t *rrsets,
                                const uint16_t     count,
                                const style_t      *style)
 {
@@ -291,7 +288,7 @@ static void print_section_host(const knot_rrset_t **rrsets,
 	char   *buf = calloc(buflen, 1);
 
 	for (size_t i = 0; i < count; i++) {
-		const knot_rrset_t  *rrset = rrsets[i];
+		const knot_rrset_t  *rrset = &rrsets[i];
 		knot_lookup_table_t *descr;
 		char                type[32] = "NULL";
 		char                *owner;
diff --git a/src/utils/dig/dig_exec.c b/src/utils/dig/dig_exec.c
index 3180f3830a4e2af53669c08364ec7d7efbfda9e4..4244c80b11c147c85d934a18ebb68e68ba1b05b3 100644
--- a/src/utils/dig/dig_exec.c
+++ b/src/utils/dig/dig_exec.c
@@ -91,7 +91,7 @@ static knot_pkt_t* create_query_packet(const query_t *query)
 	ret = knot_pkt_put_question(packet, qname, query->class_num,
 	                            query->type_num);
 	if (ret != KNOT_EOK) {
-		knot_dname_free(&qname);
+		knot_dname_free(&qname, NULL);
 		knot_pkt_free(&packet);
 		return NULL;
 	}
@@ -107,8 +107,8 @@ static knot_pkt_t* create_query_packet(const query_t *query)
 		                                   KNOT_RRTYPE_SOA,
 		                                   query->class_num,
 		                                   &packet->mm);
+		knot_dname_free(&qname, NULL);
 		if (soa == NULL) {
-			knot_dname_free(&qname);
 			knot_pkt_free(&packet);
 			return NULL;
 		}
@@ -125,7 +125,7 @@ static knot_pkt_t* create_query_packet(const query_t *query)
 		}
 
 		// Set SOA serial.
-		knot_rdata_soa_serial_set(soa, query->xfr_serial);
+		knot_rrs_soa_serial_set(&soa->rrs, query->xfr_serial);
 
 		// Add authority section.
 		knot_pkt_begin(packet, KNOT_AUTHORITY);
@@ -136,7 +136,7 @@ static knot_pkt_t* create_query_packet(const query_t *query)
 			return NULL;
 		}
 	} else {
-		knot_dname_free(&qname);
+		knot_dname_free(&qname, NULL);
 	}
 
 	// Create EDNS section if required.
@@ -227,12 +227,12 @@ static int64_t first_serial_check(const knot_pkt_t *reply)
 		return -1;
 	}
 
-	const knot_rrset_t *first = answer->rr[0];
+	const knot_rrset_t *first = &answer->rr[0];
 
 	if (first->type != KNOT_RRTYPE_SOA) {
 		return -1;
 	} else {
-		return knot_rdata_soa_serial(first);
+		return knot_rrs_soa_serial(&first->rrs);
 	}
 }
 
@@ -243,13 +243,12 @@ static bool last_serial_check(const uint32_t serial, const knot_pkt_t *reply)
 		return false;
 	}
 
-	const knot_rrset_t *last = answer->rr[answer->count - 1];
+	const knot_rrset_t *last = &answer->rr[answer->count - 1];
 
 	if (last->type != KNOT_RRTYPE_SOA) {
 		return false;
 	} else {
-		int64_t last_serial = knot_rdata_soa_serial(last);
-
+		int64_t last_serial = knot_rrs_soa_serial(&last->rrs);
 		if (last_serial == serial) {
 			return true;
 		} else {
@@ -510,7 +509,6 @@ static int process_packet_xfr(const knot_pkt_t     *query,
 	// Get stop query time and start reply time.
 	gettimeofday(&t_query, NULL);
 
-
 	// Print query packet if required.
 	if (style->show_query) {
 		print_packet(query, net,
diff --git a/src/utils/nsec3hash/nsec3hash_main.c b/src/utils/nsec3hash/nsec3hash_main.c
index 9821a4ed67cb80a9d9acea34666e26642aec99ff..f7d33d7d61a9cfb4e5d6c7f15ef83d5fcf33bbee 100644
--- a/src/utils/nsec3hash/nsec3hash_main.c
+++ b/src/utils/nsec3hash/nsec3hash_main.c
@@ -184,7 +184,7 @@ int main(int argc, char *argv[])
 
 fail:
 	knot_nsec3_params_free(&nsec3_params);
-	knot_dname_free(&dname);
+	knot_dname_free(&dname, NULL);
 	free(digest);
 	free(b32_digest);
 
diff --git a/src/utils/nsupdate/nsupdate_exec.c b/src/utils/nsupdate/nsupdate_exec.c
index 9cd29eea540bc4e35d1e28e9871e1102219e5b20..fefb721d991fd0f35b81420ef610def67f22ec34 100644
--- a/src/utils/nsupdate/nsupdate_exec.c
+++ b/src/utils/nsupdate/nsupdate_exec.c
@@ -133,7 +133,7 @@ static int dname_isvalid(const char *lp, size_t len) {
 	if (dn == NULL) {
 		return 0;
 	}
-	knot_dname_free(&dn);
+	knot_dname_free(&dn, NULL);
 	return 1;
 }
 
@@ -188,7 +188,7 @@ static int parse_partial_rr(zs_scanner_t *s, const char *lp, unsigned flags) {
 
 	/* Parse only name? */
 	if (flags & PARSE_NAMEONLY) {
-		knot_dname_free(&owner);
+		knot_dname_free(&owner, NULL);
 		return KNOT_EOK;
 	}
 
@@ -218,7 +218,7 @@ static int parse_partial_rr(zs_scanner_t *s, const char *lp, unsigned flags) {
 		char cls_s[16] = {0};
 		knot_rrclass_to_string(s->default_class, cls_s, sizeof(cls_s));
 		ERR("class mismatch: '%s'\n", cls_s);
-		knot_dname_free(&owner);
+		knot_dname_free(&owner, NULL);
 		return KNOT_EPARSEFAIL;
 	}
 
@@ -233,7 +233,7 @@ static int parse_partial_rr(zs_scanner_t *s, const char *lp, unsigned flags) {
 
 	/* Remainder */
 	if (*lp == '\0') {
-		knot_dname_free(&owner);
+		knot_dname_free(&owner, NULL);
 		return ret; /* No RDATA */
 	}
 
@@ -251,7 +251,7 @@ static int parse_partial_rr(zs_scanner_t *s, const char *lp, unsigned flags) {
 
 	free(owner_s);
 	free(rr);
-	knot_dname_free(&owner);
+	knot_dname_free(&owner, NULL);
 	return ret;
 }
 
@@ -290,16 +290,10 @@ static srv_info_t *parse_host(const char *lp, const char* default_port)
 /* Append parsed RRSet to list. */
 static int rr_list_append(zs_scanner_t *s, list_t *target_list, mm_ctx_t *mm)
 {
-	/* Form a rrset. */
-	knot_dname_t *owner = knot_dname_copy(s->r_owner);
-	if (!owner) {
-		DBG("%s: failed to create dname\n", __func__);
-		return KNOT_ENOMEM;
-	}
-	knot_rrset_t *rr = knot_rrset_new(owner, s->r_type, s->r_class, NULL);
+	knot_rrset_t *rr = knot_rrset_new(s->r_owner, s->r_type, s->r_class,
+	                                  NULL);
 	if (!rr) {
 		DBG("%s: failed to create rrset\n", __func__);
-		knot_dname_free(&owner);
 		return KNOT_ENOMEM;
 	}
 
@@ -357,7 +351,7 @@ static int build_query(nsupdate_params_t *params)
 	knot_wire_set_opcode(query->wire, KNOT_OPCODE_UPDATE);
 	knot_dname_t *qname = knot_dname_from_str(params->zone);
 	int ret = knot_pkt_put_question(query, qname, params->class_num, params->type_num);
-	knot_dname_free(&qname);
+	knot_dname_free(&qname, NULL);
 	if (ret != KNOT_EOK)
 		return ret;
 
@@ -383,7 +377,6 @@ static int build_query(nsupdate_params_t *params)
 	return rr_list_to_packet(query, &params->update_list);
 }
 
-
 static int pkt_sendrecv(nsupdate_params_t *params)
 {
 	net_t net;
@@ -524,7 +517,6 @@ int cmd_update(const char* lp, nsupdate_params_t *params)
 	return h[bp](tok_skipspace(lp + TOK_L(cmd_array[bp])), params);
 }
 
-
 int cmd_add(const char* lp, nsupdate_params_t *params)
 {
 	DBG("%s: lp='%s'\n", __func__, lp);
diff --git a/tests-extra/tests/ddns/basic/test.py b/tests-extra/tests/ddns/basic/test.py
index 3018e35701c4c2a5f85c9efc874c9cf55f530877..70c8626811625c7e673ee91e869bd4775d517b3c 100644
--- a/tests-extra/tests/ddns/basic/test.py
+++ b/tests-extra/tests/ddns/basic/test.py
@@ -158,8 +158,7 @@ def do_normal_tests(master, zone, dnssec=False):
     resp.check_record(rtype="CNAME", rdata="mail.ddns.")
     verify(master, zone, dnssec)
 
-    # add new node with CNAME + add A to the same node
-    # the A should be ignored
+    # add new node with CNAME + add A to the same node, A should be ignored
     check_log("Add new CNAME node + add A to it")
     up = master.update(zone)
     up.add("rrtest3.ddns.", "3600", "CNAME", "dont.ignore.me.ddns.")
@@ -167,8 +166,8 @@ def do_normal_tests(master, zone, dnssec=False):
     up.send("NOERROR")
     resp = master.dig("rrtest3.ddns.", "ANY")
     resp.check(rcode="NOERROR")
+    resp.check_record(rtype="TXT", nordata="ignore")
     resp.check_record(rtype="CNAME", rdata="dont.ignore.me.ddns.")
-    compare(resp.count(section="answer"), 1, "Other RRs than CNAME in node")
     verify(master, zone, dnssec)
 
     # add CNAME to CNAME node, should be replaced
@@ -238,7 +237,7 @@ def do_normal_tests(master, zone, dnssec=False):
            "dns1.ddns. hostmaster.ddns. 2010111213 10800 3600 1209600 7200")
     resp = master.dig("ddns.", "SOA")
     resp.check(rcode="NOERROR",
-               rdata="dns1.ddns. hostmaster.ddns. 2014111213 10800 3600 1209600 7200")
+               rdata="dns1.ddns. hostmaster.ddns. 2013111213 10800 3600 1209600 7200")
     verify(master, zone, dnssec)
 
     # add and remove the same record
diff --git a/tests/conf.c b/tests/conf.c
index 506e0963116a43bb9a3cd19fd5f620dbdf370529..0e237c18699987a4e7511b032258c001cc9174fa 100644
--- a/tests/conf.c
+++ b/tests/conf.c
@@ -123,7 +123,7 @@ int main(int argc, char *argv[])
 	} else {
 		ok(0, "TSIG key dname check - NO KEY FOUND");
 	}
-	knot_dname_free(&sample);
+	knot_dname_free(&sample, NULL);
 
 skip_all:
 
diff --git a/tests/dname.c b/tests/dname.c
index 338f8faf1137a8ff7abeb13141420d5428483b6a..399e52b898eef7477de600393225ca7b00490cee 100644
--- a/tests/dname.c
+++ b/tests/dname.c
@@ -77,14 +77,14 @@ int main(int argc, char *argv[])
 	d = knot_dname_from_str(t);
 	ok(d && knot_dname_size(d) == len && memcmp(d, w, len) == 0,
 	   "dname_fromstr: parsed correct non-FQDN name");
-	knot_dname_free(&d);
+	knot_dname_free(&d, NULL);
 
 	/* 12. parse FQDN from string (correct) .*/
 	t = "abcd.efg.";
 	d = knot_dname_from_str(t);
 	ok(d && knot_dname_size(d) == len && memcmp(d, w, len) == 0,
 	   "dname_fromstr: parsed correct FQDN name");
-	knot_dname_free(&d);
+	knot_dname_free(&d, NULL);
 
 	/* 13. parse name from string (incorrect) .*/
 	t = "..";
@@ -97,30 +97,30 @@ int main(int argc, char *argv[])
 	t = "ab.cd.ef";
 	d = knot_dname_from_str(t);
 	ok(!knot_dname_is_sub(d, d2), "dname_subdomain: equal name");
-	knot_dname_free(&d);
+	knot_dname_free(&d, NULL);
 
 	/* 15. true subdomain */
 	t = "0.ab.cd.ef";
 	d = knot_dname_from_str(t);
 	ok(knot_dname_is_sub(d, d2), "dname_subdomain: true subdomain");
-	knot_dname_free(&d);
+	knot_dname_free(&d, NULL);
 
 	/* 16. not subdomain */
 	t = "cd.ef";
 	d = knot_dname_from_str(t);
 	ok(!knot_dname_is_sub(d, d2), "dname_subdomain: not subdomain");
-	knot_dname_free(&d);
+	knot_dname_free(&d, NULL);
 
 	/* 17. root subdomain */
 	t = ".";
 	d = knot_dname_from_str(t);
 	ok(knot_dname_is_sub(d2, d), "dname_subdomain: root subdomain");
-	knot_dname_free(&d);
-	knot_dname_free(&d2);
+	knot_dname_free(&d, NULL);
+	knot_dname_free(&d2, NULL);
 
 	/* 18-19. dname cat (valid) */
 	w = "\x03""cat";
-	d = knot_dname_copy((const uint8_t *)w);
+	d = knot_dname_copy((const uint8_t *)w, NULL);
 	t = "*";
 	d2 = knot_dname_from_str(t);
 	d2 = knot_dname_cat(d2, d);
@@ -128,23 +128,23 @@ int main(int argc, char *argv[])
 	len = 2 + 4 + 1;
 	ok (d2 && len == knot_dname_size(d2), "dname_cat: valid concatenation size");
 	ok(memcmp(d2, t, len) == 0, "dname_cat: valid concatenation");
-	knot_dname_free(&d);
-	knot_dname_free(&d2);
+	knot_dname_free(&d, NULL);
+	knot_dname_free(&d2, NULL);
 
 	/* 20-21. parse from wire (valid) */
 	t = "\x04""abcd""\x03""efg";
 	len = 10;
 	pos = 0;
-	d = knot_dname_parse((const uint8_t *)t, &pos, len);
+	d = knot_dname_parse((const uint8_t *)t, &pos, len, NULL);
 	ok(d != NULL, "dname_parse: valid name");
 	ok(pos == len, "dname_parse: valid name (parsed length)");
-	knot_dname_free(&d);
+	knot_dname_free(&d, NULL);
 
 	/* 22-23. parse from wire (invalid) */
 	t = "\x08""dddd";
 	len = 5;
 	pos = 0;
-	d = knot_dname_parse((const uint8_t *)t, &pos, len);
+	d = knot_dname_parse((const uint8_t *)t, &pos, len, NULL);
 	ok(d == NULL, "dname_parse: bad name");
 	ok(pos == 0, "dname_parse: bad name (parsed length)");
 
@@ -155,23 +155,23 @@ int main(int argc, char *argv[])
 	t = "ab.cd.fe";
 	d2 = knot_dname_from_str(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: same label count");
-	knot_dname_free(&d2);
+	knot_dname_free(&d2, NULL);
 	t = "ab.cd";
 	d2 = knot_dname_from_str(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: len(d1) < len(d2)");
-	knot_dname_free(&d2);
+	knot_dname_free(&d2, NULL);
 	t = "ab.cd.ef.gh";
 	d2 = knot_dname_from_str(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: len(d1) > len(d2)");
-	knot_dname_free(&d2);
+	knot_dname_free(&d2, NULL);
 	t = "ab.cd.efe";
 	d2 = knot_dname_from_str(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: last label longer");
-	knot_dname_free(&d2);
+	knot_dname_free(&d2, NULL);
 	t = "ab.cd.e";
 	d2 = knot_dname_from_str(t);
 	ok(!knot_dname_is_equal(d, d2), "dname_is_equal: last label shorter");
-	knot_dname_free(&d2);
+	knot_dname_free(&d2, NULL);
 
 	return 0;
 }
diff --git a/tests/dnssec_nsec3.c b/tests/dnssec_nsec3.c
index ba57c6c3d04368bae0ecb784cf041dbb54aeb986..8db2af9f3ce581343330c766089aeac946b3f685 100644
--- a/tests/dnssec_nsec3.c
+++ b/tests/dnssec_nsec3.c
@@ -55,10 +55,13 @@ int main(int argc, char *argv[])
 		'a', 'b', 'c', 'd'     // salt
 	};
 
-	rrset = knot_rrset_new(NULL, KNOT_RRTYPE_NSEC3PARAM, KNOT_CLASS_IN, NULL);
+	knot_dname_t *owner = knot_dname_from_str("test.");
+	rrset = knot_rrset_new(owner, KNOT_RRTYPE_NSEC3PARAM, KNOT_CLASS_IN, NULL);
+	knot_dname_free(&owner, NULL);
+
 	result = knot_rrset_add_rr(rrset, rdata, sizeof(rdata), 0, NULL);
 	if (result == KNOT_EOK) {
-		result = knot_nsec3_params_from_wire(&params, rrset);
+		result = knot_nsec3_params_from_wire(&params, &rrset->rrs);
 	}
 
 	is_int(1, params.algorithm, "parse algorithm from wire");
@@ -96,7 +99,7 @@ int main(int argc, char *argv[])
 
 	free(digest);
 	free(params.salt);
-	knot_dname_free(&dname);
+	knot_dname_free(&dname, NULL);
 
 	return 0;
 }
diff --git a/tests/dnssec_zone_nsec.c b/tests/dnssec_zone_nsec.c
index b0361bd7aad4f5c8746215000416f4cfc31c5b32..30d3e2370bf94ea91e3dc27ae186c4ca946d6171 100644
--- a/tests/dnssec_zone_nsec.c
+++ b/tests/dnssec_zone_nsec.c
@@ -36,10 +36,10 @@ int main(int argc, char *argv[])
 	knot_dname_t *result = knot_create_nsec3_owner(owner, apex, &params);
 	is_int(0, knot_dname_cmp(result, expect), "create_nsec3_owner()");
 
-	knot_dname_free(&result);
-	knot_dname_free(&owner);
-	knot_dname_free(&apex);
-	knot_dname_free(&expect);
+	knot_dname_free(&result, NULL);
+	knot_dname_free(&owner, NULL);
+	knot_dname_free(&apex, NULL);
+	knot_dname_free(&expect, NULL);
 
 	return 0;
 }
diff --git a/tests/pkt.c b/tests/pkt.c
index a6a92fb9ce7cf19b0f35922ef2a77cc0376e16c9..7f6857174a53ef359187cc7e67c79dace26e0b6c 100644
--- a/tests/pkt.c
+++ b/tests/pkt.c
@@ -19,7 +19,6 @@
 
 #include "common/errcode.h"
 #include "common/mempool.h"
-#include "libknot/rrset.h"
 #include "libknot/rdata.h"
 #include "libknot/packet/pkt.h"
 #include "libknot/tsig.h"
@@ -104,6 +103,7 @@ int main(int argc, char *argv[])
 
 	/* Write ANSWER section. */
 	rrsets[0] = knot_rrset_new(dnames[0], KNOT_RRTYPE_A, KNOT_CLASS_IN, NULL);
+	knot_dname_free(&dnames[0], NULL);
 	knot_rrset_add_rr(rrsets[0], RDVAL(0), RDLEN(0), TTL, NULL);
 	ret = knot_pkt_put(out, COMPR_HINT_QNAME, rrsets[0], 0);
 	ok(ret == KNOT_EOK, "pkt: write ANSWER");
@@ -116,6 +116,7 @@ int main(int argc, char *argv[])
 	ret = KNOT_EOK;
 	for (unsigned i = 1; i < NAMECOUNT; ++i) {
 		rrsets[i] = knot_rrset_new(dnames[i], KNOT_RRTYPE_NS, KNOT_CLASS_IN, NULL);
+		knot_dname_free(&dnames[i], NULL);
 		knot_rrset_add_rr(rrsets[i], RDVAL(i), RDLEN(i), TTL, NULL);
 		ret |= knot_pkt_put(out, COMPR_HINT_NONE, rrsets[i], 0);
 	}
@@ -162,7 +163,7 @@ int main(int argc, char *argv[])
 	/* Check RRs */
 	int rr_matched = 0;
 	for (unsigned i = 0; i < NAMECOUNT; ++i) {
-		if (knot_rrset_equal(out->rr[i], in->rr[i], KNOT_RRSET_COMPARE_WHOLE) > 0) {
+		if (knot_rrset_equal(&out->rr[i], &in->rr[i], KNOT_RRSET_COMPARE_WHOLE) > 0) {
 			++rr_matched;
 		}
 	}
diff --git a/tests/process_query.c b/tests/process_query.c
index 437119dc7793c4d79de88329e62fe8e04fe286ba..2e2ea6049e6b262b65f4eb5a4bb348e228d1a371 100644
--- a/tests/process_query.c
+++ b/tests/process_query.c
@@ -48,8 +48,7 @@ void create_root_zone(server_t *server, mm_ctx_t *mm)
 	zone_t *root = zone_new(conf);
 	root->contents = knot_zone_contents_new(root->name);
 
-	knot_dname_t *root_name = knot_dname_copy(root->name);
-	knot_rrset_t *soa_rrset = knot_rrset_new(root_name,
+	knot_rrset_t *soa_rrset = knot_rrset_new(root->name,
 	                                         KNOT_RRTYPE_SOA, KNOT_CLASS_IN,
 	                                         NULL);
 	knot_rrset_add_rr(soa_rrset, SOA_RDATA, SOA_RDLEN, 7200, NULL);
@@ -194,9 +193,9 @@ int main(int argc, char *argv[])
 	/* Forge IXFR query (well formed). */
 	knot_process_reset(&query_ctx);
 	/* Append SOA RR. */
-	knot_rrset_t *soa_rr = knot_node_get_rrset(zone->contents->apex, KNOT_RRTYPE_SOA);
+	knot_rrset_t soa_rr = knot_node_rrset(zone->contents->apex, KNOT_RRTYPE_SOA);
 	knot_pkt_begin(query, KNOT_AUTHORITY);
-	knot_pkt_put(query, COMPR_HINT_NONE, soa_rr, 0);
+	knot_pkt_put(query, COMPR_HINT_NONE, &soa_rr, 0);
 	exec_query(&query_ctx, "IN/ixfr", query->wire, query->size, KNOT_RCODE_NOTAUTH);
 
 	/* \note Tests below are not possible without proper zone and zone data. */
diff --git a/tests/rrl.c b/tests/rrl.c
index dd412ea3644b06889b5069d543aeabc888a9592f..e7e3b6648190bfe3648e5cb66455b1ed7a085316 100644
--- a/tests/rrl.c
+++ b/tests/rrl.c
@@ -101,7 +101,7 @@ int main(int argc, char *argv[])
 
 	knot_dname_t *qname = knot_dname_from_str("beef.");
 	int ret = knot_pkt_put_question(query, qname, KNOT_CLASS_IN, KNOT_RRTYPE_A);
-	knot_dname_free(&qname);
+	knot_dname_free(&qname, NULL);
 	if (ret != KNOT_EOK) {
 		knot_pkt_free(&query);
 		return KNOT_ERROR; /* Fatal */
diff --git a/tests/zonedb.c b/tests/zonedb.c
index 0edb94cddccde243ac763bf457f4ea9b79611b6a..e89e6836a85b9d190b08c628d02f4b1ae60d6297 100644
--- a/tests/zonedb.c
+++ b/tests/zonedb.c
@@ -77,7 +77,7 @@ int main(int argc, char *argv[])
 		} else {
 			diag("knot_zonedb_find(%s) failed", zone_list[i]);
 		}
-		knot_dname_free(&dname);
+		knot_dname_free(&dname, NULL);
 	}
 	ok(nr_passed == ZONE_COUNT, "zonedb: find exact zones");
 
@@ -94,7 +94,7 @@ int main(int argc, char *argv[])
 		} else {
 			diag("knot_zonedb_find_suffix(%s) failed", buf);
 		}
-		knot_dname_free(&dname);
+		knot_dname_free(&dname, NULL);
 	}
 	ok(nr_passed == ZONE_COUNT, "zonedb: find zones for subnames");
 
@@ -108,7 +108,7 @@ int main(int argc, char *argv[])
 		} else {
 			diag("knot_zonedb_remove_zone(%s) failed", zone_list[i]);
 		}
-		knot_dname_free(&dname);
+		knot_dname_free(&dname, NULL);
 	}
 	ok(nr_passed == ZONE_COUNT, "zonedb: removed all zones");
 
diff --git a/tests/ztree.c b/tests/ztree.c
index f646a268275952ff61e39d278c0cbd1ffeae5da3..56caa7401968fb956434388f361c42cdc6646aaa 100644
--- a/tests/ztree.c
+++ b/tests/ztree.c
@@ -46,7 +46,7 @@ static void ztree_init_data()
 static void ztree_free_data()
 {
 	for (unsigned i = 0; i < NCOUNT; ++i)
-		knot_dname_free(NAME + i);
+		knot_dname_free(NAME + i, NULL);
 }
 
 static int ztree_iter_data(knot_node_t **node, void *data)
@@ -107,7 +107,7 @@ int main(int argc, char *argv[])
 	const knot_node_t *prev = NULL;
 	knot_dname_t *tmp_dn = knot_dname_from_str("z.ac.");
 	knot_zone_tree_find_less_or_equal(t, tmp_dn, &node, &prev);
-	knot_dname_free(&tmp_dn);
+	knot_dname_free(&tmp_dn, NULL);
 	ok(prev == NODE + 1, "ztree: ordered lookup");
 
 	/* 5. ordered traversal */