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/knot/nameserver/update.c b/src/knot/nameserver/update.c
index 26a29d9aeda0de1ed11c140c5c168f999786b220..1c91e0e5f806025c4d6cff526f1b29934572002a 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -280,7 +280,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(chgsets, old_contents, &new_contents);
+			xfrin_rollback_update(chgsets, &new_contents);
 			knot_changesets_free(&chgsets);
 			free(msg);
 			return KNOT_ENOMEM;
@@ -313,7 +313,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 			log_zone_error("%s: Failed to sign incoming update (%s)"
 			               "\n", msg, knot_strerror(ret));
 			1 == 1; // TODO: rollback
-			xfrin_rollback_update(chgsets, old_contents, &new_contents);
+			xfrin_rollback_update(chgsets, &new_contents);
 			knot_changesets_free(&chgsets);
 			knot_changesets_free(&sec_chs);
 			free(msg);
@@ -328,7 +328,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(chgsets, old_contents, &new_contents);
+		xfrin_rollback_update(chgsets, &new_contents);
 		zones_free_merged_changesets(chgsets, sec_chs);
 		free(msg);
 		return ret;
@@ -337,10 +337,10 @@ 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(zone, 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));
@@ -366,7 +366,7 @@ 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, old_contents, &new_contents);
+			xfrin_rollback_update(chgsets, &new_contents);
 			zones_free_merged_changesets(chgsets, sec_chs);
 			free(msg);
 			return ret;
@@ -379,7 +379,7 @@ 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(chgsets, old_contents, &new_contents);
+			xfrin_rollback_update(chgsets, &new_contents);
 			zones_free_merged_changesets(chgsets, sec_chs);
 			free(msg);
 			return ret;
@@ -395,7 +395,7 @@ 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(chgsets, old_contents, &new_contents);
+		xfrin_rollback_update(chgsets, &new_contents);
 
 		/* Free changesets, but not the data. */
 		zones_free_merged_changesets(chgsets, sec_chs);
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index de0d814d12fae23cafe2a7d379ae3ff2dc284e2f..2cb7736d08224b12445fac41f4127d63875b20e9 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -1470,7 +1470,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(chs, zone->contents, new_contents);
+		xfrin_rollback_update(chs, new_contents);
 		log_zone_error("%s Failed to commit stored changesets.\n", msgpref);
 		knot_changesets_free(&chs);
 		return ret;
@@ -1486,7 +1486,7 @@ 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(chs, zone->contents, new_contents);
+		xfrin_rollback_update(chs, new_contents);
 
 		/* Free changesets, but not the data. */
 		knot_changesets_free(&chs);
@@ -1930,7 +1930,6 @@ int zones_journal_apply(zone_t *zone)
 
 					// Cleanup old and new contents
 					xfrin_rollback_update(chsets,
-					                      zone->contents,
 					                      &contents);
 				}
 			}
@@ -2151,7 +2150,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(diff_chs, zone->contents, &new_contents);
+			xfrin_rollback_update(diff_chs, &new_contents);
 			return ret;
 		}
 
diff --git a/src/knot/updates/xfr-in.c b/src/knot/updates/xfr-in.c
index 2dd755e3e5aed143a5ff9380b16dfa5cd2f44a90..32e13b12708b79f3b00f5a120bff95b46ca3a9c6 100644
--- a/src/knot/updates/xfr-in.c
+++ b/src/knot/updates/xfr-in.c
@@ -597,24 +597,6 @@ cleanup:
 /* Applying changesets to zone                                                */
 /*----------------------------------------------------------------------------*/
 
-void xfrin_zone_contents_free(knot_zone_contents_t **contents)
-{
-	/*! \todo This should be all in some API!! */
-
-	// free the zone tree with nodes
-	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;
-}
-
-/*----------------------------------------------------------------------------*/
-
 void xfrin_cleanup_successful_update(knot_changesets_t *chgs)
 {
 	if (chgs == NULL) {
@@ -632,46 +614,10 @@ void xfrin_cleanup_successful_update(knot_changesets_t *chgs)
 	};
 }
 
-/*----------------------------------------------------------------------------*/
-/* 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 void xfrin_zone_contents_free(knot_zone_contents_t **contents)
 {
-	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);
-	}
-
-	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");
@@ -687,44 +633,22 @@ static void xfrin_zone_contents_free2(knot_zone_contents_t **contents)
 
 /*----------------------------------------------------------------------------*/
 
-static int xfrin_cleanup_old_nodes(knot_node_t **node, void *data)
+static void xfrin_cleanup_failed_update(knot_zone_contents_t **new_contents)
 {
-	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)
-{
-	if (old_contents == NULL && new_contents == NULL) {
+	if (new_contents == NULL) {
 		return;
 	}
 
 	if (*new_contents != NULL) {
 		// destroy the shallow copy of zone
-		xfrin_zone_contents_free2(new_contents);
+		xfrin_zone_contents_free(new_contents);
 	}
 
-	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_changesets_t *chgs,
-                           knot_zone_contents_t *old_contents,
                            knot_zone_contents_t **new_contents)
 {
 	if (chgs != NULL) {
@@ -734,11 +658,11 @@ void xfrin_rollback_update(knot_changesets_t *chgs,
 			rrs_list_clear(&change->new_data, NULL);
 			// Keep old RR data
 			ptrlist_free(&change->old_data, NULL);
-				init_list(&change->new_data);
-		init_list(&change->old_data);
+			init_list(&change->new_data);
+			init_list(&change->old_data);
 		};
 	}
-	xfrin_cleanup_failed_update(old_contents, new_contents);
+	xfrin_cleanup_failed_update(new_contents);
 }
 
 /*----------------------------------------------------------------------------*/
@@ -999,7 +923,7 @@ static int xfrin_apply_changeset(list_t *old_rrs, list_t *new_rrs,
 	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, old_rrs, new_rrs);
@@ -1140,7 +1064,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;
@@ -1155,19 +1079,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;
@@ -1246,12 +1157,11 @@ int xfrin_apply_changesets_directly(knot_zone_contents_t *contents,
 
 /* Post-DDNS application, no need to shallow copy. */
 int xfrin_apply_changesets_dnssec_ddns(zone_t *zone,
-                                       knot_zone_contents_t *z_old,
                                        knot_zone_contents_t *z_new,
                                        knot_changesets_t *sec_chsets,
                                        knot_changesets_t *chsets)
 {
-	if (zone == NULL || z_old == NULL || z_new == NULL ||
+	if (zone == NULL || z_new == NULL ||
 	    sec_chsets == NULL || chsets == NULL) {
 		return KNOT_EINVAL;
 	}
@@ -1262,7 +1172,7 @@ int xfrin_apply_changesets_dnssec_ddns(zone_t *zone,
 	/* Apply changes. */
 	int ret = xfrin_apply_changesets_directly(z_new, sec_chsets);
 	if (ret != KNOT_EOK) {
-		xfrin_rollback_update(sec_chsets, z_old, &z_new);
+		xfrin_rollback_update(sec_chsets, &z_new);
 		dbg_xfrin("Failed to apply changesets to zone: "
 		          "%s\n", knot_strerror(ret));
 		return ret;
@@ -1273,7 +1183,7 @@ int xfrin_apply_changesets_dnssec_ddns(zone_t *zone,
 	if (ret != KNOT_EOK) {
 		dbg_xfrin("Failed to finalize updated zone: %s\n",
 		          knot_strerror(ret));
-		xfrin_rollback_update(sec_chsets, z_old, &z_new);
+		xfrin_rollback_update(sec_chsets, &z_new);
 		return ret;
 	}
 
@@ -1320,8 +1230,7 @@ int xfrin_apply_changesets(zone_t *zone,
 		                            &set->new_data,
 		                            contents_copy, set);
 		if (ret != KNOT_EOK) {
-			xfrin_rollback_update(chsets, old_contents,
-			                      &contents_copy);
+			xfrin_rollback_update(chsets, &contents_copy);
 			dbg_xfrin("Failed to apply changesets to zone: "
 				  "%s\n", knot_strerror(ret));
 			return ret;
@@ -1338,7 +1247,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(chsets, old_contents, &contents_copy);
+		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 ab3f95187ceca80a232483bb8ec499237d44d140..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(zone_t *zone, 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);
@@ -199,7 +198,7 @@ int xfrin_switch_zone(zone_t *zone,
                       knot_zone_contents_t *new_contents,
                       int transfer_type);
 
-void xfrin_rollback_update(knot_changesets_t *chgs, knot_zone_contents_t *old_contents,
+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,
@@ -211,7 +210,6 @@ int xfrin_replace_rrset_in_node(knot_node_t *node,
                                 knot_rrset_t *rrset_new,
                                 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/node.c b/src/knot/zone/node.c
index ae8cfda7488140ede20759f10ce069ec00bbd68e..6e26eee43383d4ecdc67b41894b259c243c1aa4d 100644
--- a/src/knot/zone/node.c
+++ b/src/knot/zone/node.c
@@ -401,63 +401,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;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static 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) {
@@ -638,18 +581,21 @@ 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));
+	// Copy owner
 	(*to)->owner = knot_dname_copy(from->owner, NULL);
+	if ((*to)->owner == NULL) {
+		free(*to);
+		return KNOT_ENOMEM;
+	}
 
 	// copy RRSets
+	(*to)->rrset_count = from->rrset_count;
 	size_t rrlen = sizeof(struct rr_data) * from->rrset_count;
 	(*to)->rrs = malloc(rrlen);
 	if ((*to)->rrs == NULL) {
-		free(*to);
-		*to = NULL;
+		knot_node_free(to);
 		return KNOT_ENOMEM;
 	}
 	memcpy((*to)->rrs, from->rrs, rrlen);
diff --git a/src/knot/zone/node.h b/src/knot/zone/node.h
index b374bdd5e182192862d36274c0906bcb9c00e80d..df636b2745982b091b99ba6c3d5b25278bc85b16 100644
--- a/src/knot/zone/node.h
+++ b/src/knot/zone/node.h
@@ -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. */
@@ -286,14 +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_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_refs(knot_node_t *node);
-
 /*!
  * \brief Mark the node as a delegation point.
  *
diff --git a/src/knot/zone/zone-contents.c b/src/knot/zone/zone-contents.c
index 467e12de19eaa35c3f5bc1a8dd3601aa05d2a7be..3645ef62dfe8ff86aa7da27efe3cac6fa5144018 100644
--- a/src/knot/zone/zone-contents.c
+++ b/src/knot/zone/zone-contents.c
@@ -566,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);
@@ -599,13 +598,9 @@ dbg_zone_exec_detail(
 
 /*----------------------------------------------------------------------------*/
 
-int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *zone,
-                                        knot_node_t *node, int create_parents,
-                                        uint8_t flags)
+static int knot_zone_contents_add_nsec3_node(knot_zone_contents_t *zone,
+                                             knot_node_t *node)
 {
-	UNUSED(create_parents);
-	UNUSED(flags);
-
 	if (zone == NULL || node == NULL) {
 		return KNOT_EINVAL;
 	}
@@ -684,7 +679,7 @@ static int insert_rr(knot_zone_contents_t *z,
 			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);
@@ -695,6 +690,97 @@ static int insert_rr(knot_zone_contents_t *z,
 	return knot_node_add_rrset(*n, rr);
 }
 
+static int recreate_normal_tree(const knot_zone_contents_t *z,
+                                knot_zone_contents_t *out)
+{
+	out->nodes = hattrie_dup(z->nodes, NULL);
+	if (out->nodes == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	// Insert APEX first.
+	knot_node_t *apex_cpy;
+	int ret = knot_node_shallow_copy(z->apex, &apex_cpy);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// 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;
+	}
+
+	out->apex = apex_cpy;
+
+	hattrie_iter_t *itt = hattrie_iter_begin(z->nodes, true);
+	if (itt == NULL) {
+		return KNOT_ENOMEM;
+	}
+	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);
+	}
+
+	hattrie_iter_free(itt);
+	hattrie_build_index(out->nodes);
+
+	return KNOT_EOK;
+}
+
+static int recreate_nsec3_tree(const knot_zone_contents_t *z,
+                               knot_zone_contents_t *out)
+{
+	out->nsec3_nodes = hattrie_dup(z->nsec3_nodes, NULL);
+	if (out->nsec3_nodes == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	hattrie_iter_t *itt = hattrie_iter_begin(z->nsec3_nodes, false);
+	if (itt == 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);
+	}
+
+	hattrie_iter_free(itt);
+	hattrie_build_index(out->nsec3_nodes);
+
+	return KNOT_EOK;
+}
+
 int knot_zone_contents_add_rr(knot_zone_contents_t *z,
                               const knot_rrset_t *rr, knot_node_t **n)
 {
@@ -1249,41 +1335,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 (contents->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;
 }
 
 /*----------------------------------------------------------------------------*/
@@ -1368,7 +1449,7 @@ knot_node_t *zone_contents_get_node_for_rr(knot_zone_contents_t *zone,
 		if (!nsec3) {
 			ret = knot_zone_contents_add_node(zone, node, 1, 0);
 		} else {
-			ret = knot_zone_contents_add_nsec3_node(zone, node, 1, 0);
+			ret = knot_zone_contents_add_nsec3_node(zone, node);
 		}
 		if (ret != KNOT_EOK) {
 			knot_node_free(&node);
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/libknot/rr.c b/src/libknot/rr.c
index eded54e763a3400b13641d39b4b103bccbc31578..328f6d7dcb4a4af3314ae8e3d777dbf1f7362eb9 100644
--- a/src/libknot/rr.c
+++ b/src/libknot/rr.c
@@ -117,7 +117,7 @@ static int add_rr_at(knot_rrs_t *rrs, const knot_rr_t *rr, size_t pos,
 		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, rdata, size);
+		memcpy(knot_rr_rdata(new_rr), rdata, size);
 		return KNOT_EOK;
 	}