diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/Bytes.java b/bramble-api/src/main/java/org/briarproject/bramble/api/Bytes.java
index 5c9b61f40873694903dfb9a393ede6af72069fcb..1a862bbd87b28d70d0d5121f08a2d1378c95a27d 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/Bytes.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/Bytes.java
@@ -1,6 +1,7 @@
 package org.briarproject.bramble.api;
 
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.util.StringUtils;
 
 import java.util.Arrays;
 import java.util.Comparator;
@@ -53,6 +54,12 @@ public class Bytes implements Comparable<Bytes> {
 		return aBytes.length - bBytes.length;
 	}
 
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() +
+				"(" + StringUtils.toHexString(getBytes()) + ")";
+	}
+
 	public static class BytesComparator implements Comparator<Bytes> {
 
 		@Override
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java
index cc7dc13fc319b466cc28437c25ebdf0695795488..bc56ac377b7139a14d31518c763a169769ac7a0c 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogManager.java
@@ -60,7 +60,7 @@ public interface BlogManager {
 	 * Adds a comment to an existing blog post or reblogs it.
 	 */
 	void addLocalComment(LocalAuthor author, GroupId groupId,
-			@Nullable String comment, BlogPostHeader wHeader)
+			@Nullable String comment, BlogPostHeader parentHeader)
 			throws DbException;
 
 	/**
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogPostFactory.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogPostFactory.java
index 4a64c27323232acff7bf2d957ecbdc4c2f4432f8..6a07a18c22f4dcee7a275e010bc598cd5d5c5a6a 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogPostFactory.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogPostFactory.java
@@ -25,7 +25,8 @@ public interface BlogPostFactory {
 			throws FormatException, GeneralSecurityException;
 
 	Message createBlogComment(GroupId groupId, LocalAuthor author,
-			@Nullable String comment, MessageId originalId, MessageId wrappedId)
+			@Nullable String comment, MessageId parentOriginalId,
+			MessageId parentCurrentId)
 			throws FormatException, GeneralSecurityException;
 
 	/**
@@ -44,11 +45,11 @@ public interface BlogPostFactory {
 	 * Wraps a blog comment
 	 */
 	Message wrapComment(GroupId groupId, byte[] descriptor, long timestamp,
-			BdfList body, MessageId currentId) throws FormatException;
+			BdfList body, MessageId parentCurrentId) throws FormatException;
 
 	/**
 	 * Re-wraps a previously wrapped comment
 	 */
 	Message rewrapWrappedComment(GroupId groupId, BdfList body,
-			MessageId currentId) throws FormatException;
+			MessageId parentCurrentId) throws FormatException;
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
index 02e4f1eae7ca22012e7cae4f336401a0ba7a94a1..cad04aa7ea64eb23156db131649029475e9e12a5 100644
--- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
@@ -232,7 +232,6 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 			addLocalPost(txn, p);
 			db.commitTransaction(txn);
 		} finally {
-			//noinspection ThrowFromFinallyBlock
 			db.endTransaction(txn);
 		}
 	}
@@ -255,8 +254,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 			MessageId postId = p.getMessage().getId();
 			BlogPostHeader h =
 					getPostHeaderFromMetadata(txn, groupId, postId, meta);
-			BlogPostAddedEvent event =
-					new BlogPostAddedEvent(groupId, h, true);
+			BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true);
 			txn.attach(event);
 		} catch (FormatException e) {
 			throw new DbException(e);
@@ -265,42 +263,39 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 
 	@Override
 	public void addLocalComment(LocalAuthor author, GroupId groupId,
-			@Nullable String comment, BlogPostHeader pOriginalHeader)
+			@Nullable String comment, BlogPostHeader parentHeader)
 			throws DbException {
 
-		MessageType type = pOriginalHeader.getType();
+		MessageType type = parentHeader.getType();
 		if (type != POST && type != COMMENT)
 			throw new IllegalArgumentException("Comment on unknown type!");
 
 		Transaction txn = db.startTransaction(false);
 		try {
 			// Wrap post that we are commenting on
-			MessageId parentId = wrapMessage(txn, groupId, pOriginalHeader);
-
-			// Get ID of new parent's original message.
-			// Assumes that pOriginalHeader is a POST or COMMENT
-			MessageId pOriginalId = pOriginalHeader.getId();
+			MessageId parentOriginalId =
+					getOriginalMessageId(txn, parentHeader);
+			MessageId parentCurrentId =
+					wrapMessage(txn, groupId, parentHeader, parentOriginalId);
 
 			// Create actual comment
-			Message message = blogPostFactory
-					.createBlogComment(groupId, author, comment, pOriginalId,
-							parentId);
+			Message message = blogPostFactory.createBlogComment(groupId, author,
+					comment, parentOriginalId, parentCurrentId);
 			BdfDictionary meta = new BdfDictionary();
 			meta.put(KEY_TYPE, COMMENT.getInt());
 			if (comment != null) meta.put(KEY_COMMENT, comment);
 			meta.put(KEY_TIMESTAMP, message.getTimestamp());
 			meta.put(KEY_ORIGINAL_MSG_ID, message.getId());
-			meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId);
-			meta.put(KEY_PARENT_MSG_ID, parentId);
+			meta.put(KEY_ORIGINAL_PARENT_MSG_ID, parentOriginalId);
+			meta.put(KEY_PARENT_MSG_ID, parentCurrentId);
 			meta.put(KEY_AUTHOR, authorToBdfDictionary(author));
 
 			// Send comment
 			clientHelper.addLocalMessage(txn, message, meta, true);
 
 			// broadcast event
-			BlogPostHeader h =
-					getPostHeaderFromMetadata(txn, groupId, message.getId(),
-							meta);
+			BlogPostHeader h = getPostHeaderFromMetadata(txn, groupId,
+					message.getId(), meta);
 			BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true);
 			txn.attach(event);
 			db.commitTransaction(txn);
@@ -309,81 +304,94 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 		} catch (GeneralSecurityException e) {
 			throw new IllegalArgumentException("Invalid key of author", e);
 		} finally {
-			//noinspection ThrowFromFinallyBlock
 			db.endTransaction(txn);
 		}
 	}
 
+	private MessageId getOriginalMessageId(Transaction txn, BlogPostHeader h)
+			throws DbException, FormatException {
+		MessageType type = h.getType();
+		if (type == POST || type == COMMENT) return h.getId();
+		BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(txn,
+				h.getId());
+		return new MessageId(meta.getRaw(KEY_ORIGINAL_MSG_ID));
+	}
+
 	private MessageId wrapMessage(Transaction txn, GroupId groupId,
-			BlogPostHeader pOriginalHeader)
+			BlogPostHeader header, MessageId originalId)
 			throws DbException, FormatException {
 
-		if (groupId.equals(pOriginalHeader.getGroupId())) {
+		if (groupId.equals(header.getGroupId())) {
 			// We are trying to wrap a post that is already in our group.
 			// This is unnecessary, so just return the post's MessageId
-			return pOriginalHeader.getId();
+			return header.getId();
 		}
 
 		// Get body of message to be wrapped
-		BdfList body =
-				clientHelper.getMessageAsList(txn, pOriginalHeader.getId());
+		BdfList body = clientHelper.getMessageAsList(txn, header.getId());
 		if (body == null) throw new DbException();
-		long wTimestamp = pOriginalHeader.getTimestamp();
-		Message wMessage;
+		long timestamp = header.getTimestamp();
+		Message wrappedMessage;
 
 		BdfDictionary meta = new BdfDictionary();
-		MessageType type = pOriginalHeader.getType();
+		MessageType type = header.getType();
 		if (type == POST) {
-			Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
-			byte[] wDescriptor = wGroup.getDescriptor();
 			// Wrap post
-			wMessage = blogPostFactory
-					.wrapPost(groupId, wDescriptor, wTimestamp, body);
+			Group group = db.getGroup(txn, header.getGroupId());
+			byte[] descriptor = group.getDescriptor();
+			wrappedMessage = blogPostFactory.wrapPost(groupId, descriptor,
+					timestamp, body);
 			meta.put(KEY_TYPE, WRAPPED_POST.getInt());
-			meta.put(KEY_RSS_FEED, pOriginalHeader.isRssFeed());
+			meta.put(KEY_RSS_FEED, header.isRssFeed());
 		} else if (type == COMMENT) {
-			Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
-			byte[] wDescriptor = wGroup.getDescriptor();
-			BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader;
-			MessageId wrappedId =
-					wrapMessage(txn, groupId, wComment.getParent());
+			// Recursively wrap parent
+			BlogCommentHeader commentHeader = (BlogCommentHeader) header;
+			BlogPostHeader parentHeader = commentHeader.getParent();
+			MessageId parentOriginalId =
+					getOriginalMessageId(txn, parentHeader);
+			MessageId parentCurrentId =
+					wrapMessage(txn, groupId, parentHeader, parentOriginalId);
 			// Wrap comment
-			wMessage = blogPostFactory
-					.wrapComment(groupId, wDescriptor, wTimestamp,
-							body, wrappedId);
+			Group group = db.getGroup(txn, header.getGroupId());
+			byte[] descriptor = group.getDescriptor();
+			wrappedMessage = blogPostFactory.wrapComment(groupId, descriptor,
+					timestamp, body, parentCurrentId);
 			meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt());
-			if (wComment.getComment() != null)
-				meta.put(KEY_COMMENT, wComment.getComment());
-			meta.put(KEY_PARENT_MSG_ID, wrappedId);
+			if (commentHeader.getComment() != null)
+				meta.put(KEY_COMMENT, commentHeader.getComment());
+			meta.put(KEY_PARENT_MSG_ID, parentCurrentId);
 		} else if (type == WRAPPED_POST) {
 			// Re-wrap wrapped post without adding another wrapping layer
-			wMessage = blogPostFactory.rewrapWrappedPost(groupId, body);
+			wrappedMessage = blogPostFactory.rewrapWrappedPost(groupId, body);
 			meta.put(KEY_TYPE, WRAPPED_POST.getInt());
-			meta.put(KEY_RSS_FEED, pOriginalHeader.isRssFeed());
+			meta.put(KEY_RSS_FEED, header.isRssFeed());
 		} else if (type == WRAPPED_COMMENT) {
-			BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader;
-			MessageId wrappedId =
-					wrapMessage(txn, groupId, wComment.getParent());
+			// Recursively wrap parent
+			BlogCommentHeader commentHeader = (BlogCommentHeader) header;
+			BlogPostHeader parentHeader = commentHeader.getParent();
+			MessageId parentOriginalId =
+					getOriginalMessageId(txn, parentHeader);
+			MessageId parentCurrentId =
+					wrapMessage(txn, groupId, parentHeader, parentOriginalId);
 			// Re-wrap wrapped comment
-			wMessage = blogPostFactory
-					.rewrapWrappedComment(groupId, body, wrappedId);
+			wrappedMessage = blogPostFactory.rewrapWrappedComment(groupId, body,
+					parentCurrentId);
 			meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt());
-			if (wComment.getComment() != null)
-				meta.put(KEY_COMMENT, wComment.getComment());
-			meta.put(KEY_PARENT_MSG_ID, wrappedId);
+			if (commentHeader.getComment() != null)
+				meta.put(KEY_COMMENT, commentHeader.getComment());
+			meta.put(KEY_PARENT_MSG_ID, parentCurrentId);
 		} else {
 			throw new IllegalArgumentException(
 					"Unknown Message Type: " + type);
 		}
-		meta.put(KEY_ORIGINAL_MSG_ID, pOriginalHeader.getId());
-		meta.put(KEY_AUTHOR,
-				authorToBdfDictionary(pOriginalHeader.getAuthor()));
-		meta.put(KEY_TIMESTAMP, pOriginalHeader.getTimestamp());
-		meta.put(KEY_TIME_RECEIVED, pOriginalHeader.getTimeReceived());
+		meta.put(KEY_ORIGINAL_MSG_ID, originalId);
+		meta.put(KEY_AUTHOR, authorToBdfDictionary(header.getAuthor()));
+		meta.put(KEY_TIMESTAMP, header.getTimestamp());
+		meta.put(KEY_TIME_RECEIVED, header.getTimeReceived());
 
 		// Send wrapped message and store metadata
-		clientHelper.addLocalMessage(txn, wMessage, meta, true);
-		return wMessage.getId();
+		clientHelper.addLocalMessage(txn, wrappedMessage, meta, true);
+		return wrappedMessage.getId();
 	}
 
 	@Override
diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostFactoryImpl.java
index b8fd8b7e32e85c816ec766921d043dcbd4b58b08..bd89a0ba81930ea31b2416f1386a642938d1e0f3 100644
--- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostFactoryImpl.java
@@ -65,7 +65,8 @@ class BlogPostFactoryImpl implements BlogPostFactory {
 
 	@Override
 	public Message createBlogComment(GroupId groupId, LocalAuthor author,
-			@Nullable String comment, MessageId pOriginalId, MessageId parentId)
+			@Nullable String comment, MessageId parentOriginalId,
+			MessageId parentCurrentId)
 			throws FormatException, GeneralSecurityException {
 
 		if (comment != null) {
@@ -78,22 +79,20 @@ class BlogPostFactoryImpl implements BlogPostFactory {
 		long timestamp = clock.currentTimeMillis();
 
 		// Generate the signature
-		BdfList signed =
-				BdfList.of(groupId, timestamp, comment, pOriginalId, parentId);
+		BdfList signed = BdfList.of(groupId, timestamp, comment,
+				parentOriginalId, parentCurrentId);
 		byte[] sig = clientHelper
 				.sign(SIGNING_LABEL_COMMENT, signed, author.getPrivateKey());
 
 		// Serialise the signed message
-		BdfList message =
-				BdfList.of(COMMENT.getInt(), comment, pOriginalId, parentId,
-						sig);
+		BdfList message = BdfList.of(COMMENT.getInt(), comment,
+				parentOriginalId, parentCurrentId, sig);
 		return clientHelper.createMessage(groupId, timestamp, message);
 	}
 
 	@Override
 	public Message wrapPost(GroupId groupId, byte[] descriptor,
-			long timestamp, BdfList body)
-			throws FormatException {
+			long timestamp, BdfList body) throws FormatException {
 
 		if (getType(body) != POST)
 			throw new IllegalArgumentException("Needs to wrap a POST");
@@ -101,9 +100,8 @@ class BlogPostFactoryImpl implements BlogPostFactory {
 		// Serialise the message
 		String content = body.getString(1);
 		byte[] signature = body.getRaw(2);
-		BdfList message =
-				BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp,
-						content, signature);
+		BdfList message = BdfList.of(WRAPPED_POST.getInt(), descriptor,
+				timestamp, content, signature);
 		return clientHelper
 				.createMessage(groupId, clock.currentTimeMillis(), message);
 	}
@@ -120,16 +118,15 @@ class BlogPostFactoryImpl implements BlogPostFactory {
 		long timestamp = body.getLong(2);
 		String content = body.getString(3);
 		byte[] signature = body.getRaw(4);
-		BdfList message =
-				BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp,
-						content, signature);
+		BdfList message = BdfList.of(WRAPPED_POST.getInt(), descriptor,
+				timestamp, content, signature);
 		return clientHelper
 				.createMessage(groupId, clock.currentTimeMillis(), message);
 	}
 
 	@Override
 	public Message wrapComment(GroupId groupId, byte[] descriptor,
-			long timestamp, BdfList body, MessageId parentId)
+			long timestamp, BdfList body, MessageId parentCurrentId)
 			throws FormatException {
 
 		if (getType(body) != COMMENT)
@@ -140,16 +137,16 @@ class BlogPostFactoryImpl implements BlogPostFactory {
 		byte[] pOriginalId = body.getRaw(2);
 		byte[] oldParentId = body.getRaw(3);
 		byte[] signature = body.getRaw(4);
-		BdfList message =
-				BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp,
-						comment, pOriginalId, oldParentId, signature, parentId);
+		BdfList message = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
+				timestamp, comment, pOriginalId, oldParentId, signature,
+				parentCurrentId);
 		return clientHelper
 				.createMessage(groupId, clock.currentTimeMillis(), message);
 	}
 
 	@Override
 	public Message rewrapWrappedComment(GroupId groupId, BdfList body,
-			MessageId parentId) throws FormatException {
+			MessageId parentCurrentId) throws FormatException {
 
 		if (getType(body) != WRAPPED_COMMENT)
 			throw new IllegalArgumentException(
@@ -162,9 +159,9 @@ class BlogPostFactoryImpl implements BlogPostFactory {
 		byte[] pOriginalId = body.getRaw(4);
 		byte[] oldParentId = body.getRaw(5);
 		byte[] signature = body.getRaw(6);
-		BdfList message =
-				BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp,
-						comment, pOriginalId, oldParentId, signature, parentId);
+		BdfList message = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
+				timestamp, comment, pOriginalId, oldParentId, signature,
+				parentCurrentId);
 		return clientHelper
 				.createMessage(groupId, clock.currentTimeMillis(), message);
 	}
diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
index 5b9cee48ccac949bf0f0a4887825a52a8a7c45e9..3678f1aba2bfa5e2a794935ceee7843f6a19a0da 100644
--- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.briar.api.blog.Blog;
+import org.briarproject.briar.api.blog.BlogCommentHeader;
 import org.briarproject.briar.api.blog.BlogFactory;
 import org.briarproject.briar.api.blog.BlogPost;
 import org.briarproject.briar.api.blog.BlogPostFactory;
@@ -33,24 +34,38 @@ import org.junit.Test;
 import java.util.Collection;
 import java.util.Collections;
 
+import static org.briarproject.bramble.api.identity.Author.Status.NONE;
+import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
 import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.test.TestUtils.getRandomString;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_RSS_FEED;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
+import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_COMMENT_LENGTH;
 import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
+import static org.briarproject.briar.api.blog.MessageType.COMMENT;
 import static org.briarproject.briar.api.blog.MessageType.POST;
+import static org.briarproject.briar.api.blog.MessageType.WRAPPED_COMMENT;
+import static org.briarproject.briar.api.blog.MessageType.WRAPPED_POST;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 public class BlogManagerImplTest extends BriarTestCase {
@@ -62,21 +77,40 @@ public class BlogManagerImplTest extends BriarTestCase {
 			context.mock(IdentityManager.class);
 	private final ClientHelper clientHelper = context.mock(ClientHelper.class);
 	private final BlogFactory blogFactory = context.mock(BlogFactory.class);
+	private final BlogPostFactory blogPostFactory =
+			context.mock(BlogPostFactory.class);
 
-	private final Blog blog1, blog2;
-	private final Message message;
-	private final MessageId messageId;
+	private final LocalAuthor localAuthor1, localAuthor2, rssLocalAuthor;
+	private final BdfDictionary authorDict1, authorDict2, rssAuthorDict;
+	private final Blog blog1, blog2, rssBlog;
+	private final long timestamp, timeReceived;
+	private final MessageId messageId, rssMessageId;
+	private final Message message, rssMessage;
+	private final String comment;
 
 	public BlogManagerImplTest() {
 		MetadataParser metadataParser = context.mock(MetadataParser.class);
-		BlogPostFactory blogPostFactory = context.mock(BlogPostFactory.class);
 		blogManager = new BlogManagerImpl(db, identityManager, clientHelper,
 				metadataParser, blogFactory, blogPostFactory);
 
-		blog1 = createBlog();
-		blog2 = createBlog();
+		localAuthor1 = createLocalAuthor();
+		localAuthor2 = createLocalAuthor();
+		rssLocalAuthor = createLocalAuthor();
+		authorDict1 = authorToBdfDictionary(localAuthor1);
+		authorDict2 = authorToBdfDictionary(localAuthor2);
+		rssAuthorDict = authorToBdfDictionary(rssLocalAuthor);
+		blog1 = createBlog(localAuthor1, false);
+		blog2 = createBlog(localAuthor2, false);
+		rssBlog = createBlog(rssLocalAuthor, true);
+		timestamp = System.currentTimeMillis();
+		timeReceived = timestamp + 1;
 		messageId = new MessageId(getRandomId());
-		message = new Message(messageId, blog1.getId(), 42, getRandomBytes(42));
+		rssMessageId = new MessageId(getRandomId());
+		message = new Message(messageId, blog1.getId(), timestamp,
+				getRandomBytes(MAX_MESSAGE_LENGTH));
+		rssMessage = new Message(rssMessageId, rssBlog.getId(), timestamp,
+				getRandomBytes(MAX_MESSAGE_LENGTH));
+		comment = getRandomString(MAX_BLOG_COMMENT_LENGTH);
 	}
 
 	@Test
@@ -135,23 +169,22 @@ public class BlogManagerImplTest extends BriarTestCase {
 	@Test
 	public void testIncomingMessage() throws DbException, FormatException {
 		final Transaction txn = new Transaction(null, false);
-		BdfList list = new BdfList();
-		BdfDictionary author = authorToBdfDictionary(blog1.getAuthor());
+		BdfList body = BdfList.of("body");
 		BdfDictionary meta = BdfDictionary.of(
 				new BdfEntry(KEY_TYPE, POST.getInt()),
-				new BdfEntry(KEY_TIMESTAMP, 0),
-				new BdfEntry(KEY_TIME_RECEIVED, 1),
-				new BdfEntry(KEY_AUTHOR, author),
-				new BdfEntry(KEY_READ, false)
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_TIME_RECEIVED, timeReceived),
+				new BdfEntry(KEY_AUTHOR, authorDict1),
+				new BdfEntry(KEY_READ, false),
+				new BdfEntry(KEY_RSS_FEED, false)
 		);
 
 		context.checking(new Expectations() {{
-			oneOf(identityManager)
-					.getAuthorStatus(txn, blog1.getAuthor().getId());
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
 			will(returnValue(VERIFIED));
 		}});
 
-		blogManager.incomingMessage(txn, message, list, meta);
+		blogManager.incomingMessage(txn, message, body, meta);
 		context.assertIsSatisfied();
 
 		assertEquals(1, txn.getEvents().size());
@@ -161,11 +194,49 @@ public class BlogManagerImplTest extends BriarTestCase {
 		assertEquals(blog1.getId(), e.getGroupId());
 
 		BlogPostHeader h = e.getHeader();
-		assertEquals(1, h.getTimeReceived());
+		assertEquals(POST, h.getType());
+		assertFalse(h.isRssFeed());
+		assertEquals(timestamp, h.getTimestamp());
+		assertEquals(timeReceived, h.getTimeReceived());
 		assertEquals(messageId, h.getId());
-		assertEquals(null, h.getParentId());
+		assertEquals(blog1.getId(), h.getGroupId());
+		assertNull(h.getParentId());
 		assertEquals(VERIFIED, h.getAuthorStatus());
-		assertEquals(blog1.getAuthor(), h.getAuthor());
+		assertEquals(localAuthor1, h.getAuthor());
+	}
+
+	@Test
+	public void testIncomingRssMessage() throws DbException, FormatException {
+		final Transaction txn = new Transaction(null, false);
+		BdfList body = BdfList.of("body");
+		BdfDictionary meta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, POST.getInt()),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_TIME_RECEIVED, timeReceived),
+				new BdfEntry(KEY_AUTHOR, rssAuthorDict),
+				new BdfEntry(KEY_READ, false),
+				new BdfEntry(KEY_RSS_FEED, true)
+		);
+
+		blogManager.incomingMessage(txn, rssMessage, body, meta);
+		context.assertIsSatisfied();
+
+		assertEquals(1, txn.getEvents().size());
+		assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
+
+		BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
+		assertEquals(rssBlog.getId(), e.getGroupId());
+
+		BlogPostHeader h = e.getHeader();
+		assertEquals(POST, h.getType());
+		assertTrue(h.isRssFeed());
+		assertEquals(timestamp, h.getTimestamp());
+		assertEquals(timeReceived, h.getTimeReceived());
+		assertEquals(rssMessageId, h.getId());
+		assertEquals(rssBlog.getId(), h.getGroupId());
+		assertNull(h.getParentId());
+		assertEquals(NONE, h.getAuthorStatus());
+		assertEquals(rssLocalAuthor, h.getAuthor());
 	}
 
 	@Test
@@ -189,13 +260,11 @@ public class BlogManagerImplTest extends BriarTestCase {
 	@Test
 	public void testAddLocalPost() throws DbException, FormatException {
 		final Transaction txn = new Transaction(null, false);
-		final BlogPost post =
-				new BlogPost(message, null, blog1.getAuthor());
-		BdfDictionary authorMeta = authorToBdfDictionary(blog1.getAuthor());
+		final BlogPost post = new BlogPost(message, null, localAuthor1);
 		final BdfDictionary meta = BdfDictionary.of(
 				new BdfEntry(KEY_TYPE, POST.getInt()),
-				new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()),
-				new BdfEntry(KEY_AUTHOR, authorMeta),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_AUTHOR, authorDict1),
 				new BdfEntry(KEY_READ, true),
 				new BdfEntry(KEY_RSS_FEED, false)
 		);
@@ -207,11 +276,9 @@ public class BlogManagerImplTest extends BriarTestCase {
 			will(returnValue(blog1.getGroup()));
 			oneOf(blogFactory).parseBlog(blog1.getGroup());
 			will(returnValue(blog1));
-			oneOf(clientHelper)
-					.addLocalMessage(txn, message, meta, true);
-			oneOf(identityManager)
-					.getAuthorStatus(txn, blog1.getAuthor().getId());
-			will(returnValue(VERIFIED));
+			oneOf(clientHelper).addLocalMessage(txn, message, meta, true);
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
+			will(returnValue(OURSELVES));
 			oneOf(db).commitTransaction(txn);
 			oneOf(db).endTransaction(txn);
 		}});
@@ -226,11 +293,504 @@ public class BlogManagerImplTest extends BriarTestCase {
 		assertEquals(blog1.getId(), e.getGroupId());
 
 		BlogPostHeader h = e.getHeader();
-		assertEquals(message.getTimestamp(), h.getTimeReceived());
+		assertEquals(POST, h.getType());
+		assertEquals(timestamp, h.getTimestamp());
+		assertEquals(timestamp, h.getTimeReceived());
 		assertEquals(messageId, h.getId());
-		assertEquals(null, h.getParentId());
-		assertEquals(VERIFIED, h.getAuthorStatus());
-		assertEquals(blog1.getAuthor(), h.getAuthor());
+		assertEquals(blog1.getId(), h.getGroupId());
+		assertNull(h.getParentId());
+		assertEquals(OURSELVES, h.getAuthorStatus());
+		assertEquals(localAuthor1, h.getAuthor());
+	}
+
+	@Test
+	public void testAddLocalRssPost() throws DbException, FormatException {
+		final Transaction txn = new Transaction(null, false);
+		final BlogPost post = new BlogPost(rssMessage, null, rssLocalAuthor);
+		final BdfDictionary meta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, POST.getInt()),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_AUTHOR, rssAuthorDict),
+				new BdfEntry(KEY_READ, true),
+				new BdfEntry(KEY_RSS_FEED, true)
+		);
+
+		context.checking(new Expectations() {{
+			oneOf(db).startTransaction(false);
+			will(returnValue(txn));
+			oneOf(db).getGroup(txn, rssBlog.getId());
+			will(returnValue(rssBlog.getGroup()));
+			oneOf(blogFactory).parseBlog(rssBlog.getGroup());
+			will(returnValue(rssBlog));
+			oneOf(clientHelper).addLocalMessage(txn, rssMessage, meta, true);
+			oneOf(db).commitTransaction(txn);
+			oneOf(db).endTransaction(txn);
+		}});
+
+		blogManager.addLocalPost(post);
+		context.assertIsSatisfied();
+
+		assertEquals(1, txn.getEvents().size());
+		assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
+
+		BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
+		assertEquals(rssBlog.getId(), e.getGroupId());
+
+		BlogPostHeader h = e.getHeader();
+		assertEquals(POST, h.getType());
+		assertTrue(h.isRssFeed());
+		assertEquals(timestamp, h.getTimestamp());
+		assertEquals(timestamp, h.getTimeReceived());
+		assertEquals(rssMessageId, h.getId());
+		assertEquals(rssBlog.getId(), h.getGroupId());
+		assertNull(h.getParentId());
+		assertEquals(NONE, h.getAuthorStatus());
+		assertEquals(rssLocalAuthor, h.getAuthor());
+	}
+
+	@Test
+	public void testAddLocalCommentToLocalPost() throws Exception {
+		final Transaction txn = new Transaction(null, false);
+		// The post was originally posted to blog 1, then reblogged to the
+		// same blog (commenting on own post)
+		final BdfDictionary postMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, POST.getInt()),
+				new BdfEntry(KEY_RSS_FEED, false),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, messageId),
+				new BdfEntry(KEY_AUTHOR, authorDict1),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
+		);
+		final MessageId commentId = new MessageId(getRandomId());
+		final Message commentMsg = new Message(commentId, blog1.getId(),
+				timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
+		final BdfDictionary commentMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, COMMENT.getInt()),
+				new BdfEntry(KEY_COMMENT, comment),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
+				new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId),
+				new BdfEntry(KEY_PARENT_MSG_ID, messageId),
+				new BdfEntry(KEY_AUTHOR, authorDict1)
+		);
+
+		context.checking(new Expectations() {{
+			oneOf(db).startTransaction(false);
+			will(returnValue(txn));
+			// Create the comment
+			oneOf(blogPostFactory).createBlogComment(blog1.getId(),
+					localAuthor1, comment, messageId, messageId);
+			will(returnValue(commentMsg));
+			// Store the comment
+			oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
+					true);
+			// Create the headers for the comment and its parent
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
+			will(returnValue(OURSELVES));
+			oneOf(clientHelper).getMessageMetadataAsDictionary(txn, messageId);
+			will(returnValue(postMeta));
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
+			will(returnValue(OURSELVES));
+			oneOf(db).commitTransaction(txn);
+			oneOf(db).endTransaction(txn);
+		}});
+
+		BlogPostHeader postHeader = new BlogPostHeader(POST, blog1.getId(),
+				messageId, null, timestamp, timeReceived, localAuthor1,
+				OURSELVES, false, true);
+		blogManager.addLocalComment(localAuthor1, blog1.getId(), comment,
+				postHeader);
+		context.assertIsSatisfied();
+
+		assertEquals(1, txn.getEvents().size());
+		assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
+
+		BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
+		assertEquals(blog1.getId(), e.getGroupId());
+
+		BlogPostHeader h = e.getHeader();
+		assertEquals(COMMENT, h.getType());
+		assertFalse(h.isRssFeed());
+		assertEquals(timestamp, h.getTimestamp());
+		assertEquals(timestamp, h.getTimeReceived());
+		assertEquals(commentId, h.getId());
+		assertEquals(blog1.getId(), h.getGroupId());
+		assertEquals(messageId, h.getParentId());
+		assertEquals(OURSELVES, h.getAuthorStatus());
+		assertEquals(localAuthor1, h.getAuthor());
+
+		assertTrue(h instanceof BlogCommentHeader);
+		BlogPostHeader h1 = ((BlogCommentHeader) h).getParent();
+		assertEquals(POST, h1.getType());
+		assertFalse(h1.isRssFeed());
+		assertEquals(timestamp, h1.getTimestamp());
+		assertEquals(timeReceived, h1.getTimeReceived());
+		assertEquals(messageId, h1.getId());
+		assertEquals(blog1.getId(), h.getGroupId());
+		assertNull(h1.getParentId());
+		assertEquals(OURSELVES, h1.getAuthorStatus());
+		assertEquals(localAuthor1, h1.getAuthor());
+
+		assertEquals(h1.getId(), ((BlogCommentHeader) h).getRootPost().getId());
+	}
+
+	@Test
+	public void testAddLocalCommentToRemotePost() throws Exception {
+		final Transaction txn = new Transaction(null, false);
+		// The post was originally posted to blog 1, then reblogged to
+		// blog 2 with a comment
+		final BdfList originalPostBody = BdfList.of("originalPostBody");
+		final MessageId wrappedPostId = new MessageId(getRandomId());
+		final Message wrappedPostMsg = new Message(wrappedPostId, blog2.getId(),
+				timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
+		final BdfDictionary wrappedPostMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, WRAPPED_POST.getInt()),
+				new BdfEntry(KEY_RSS_FEED, false),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, messageId),
+				new BdfEntry(KEY_AUTHOR, authorDict1),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
+		);
+		final MessageId commentId = new MessageId(getRandomId());
+		final Message commentMsg = new Message(commentId, blog2.getId(),
+				timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
+		final BdfDictionary commentMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, COMMENT.getInt()),
+				new BdfEntry(KEY_COMMENT, comment),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
+				new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId),
+				new BdfEntry(KEY_PARENT_MSG_ID, wrappedPostId),
+				new BdfEntry(KEY_AUTHOR, authorDict2)
+		);
+
+		context.checking(new Expectations() {{
+			oneOf(db).startTransaction(false);
+			will(returnValue(txn));
+			// Wrap the original post for blog 2
+			oneOf(clientHelper).getMessageAsList(txn, messageId);
+			will(returnValue(originalPostBody));
+			oneOf(db).getGroup(txn, blog1.getId());
+			will(returnValue(blog1.getGroup()));
+			oneOf(blogPostFactory).wrapPost(blog2.getId(),
+					blog1.getGroup().getDescriptor(), timestamp,
+					originalPostBody);
+			will(returnValue(wrappedPostMsg));
+			// Store the wrapped post
+			oneOf(clientHelper).addLocalMessage(txn, wrappedPostMsg,
+					wrappedPostMeta, true);
+			// Create the comment
+			oneOf(blogPostFactory).createBlogComment(blog2.getId(),
+					localAuthor2, comment, messageId, wrappedPostId);
+			will(returnValue(commentMsg));
+			// Store the comment
+			oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
+					true);
+			// Create the headers for the comment and the wrapped post
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor2.getId());
+			will(returnValue(OURSELVES));
+			oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
+					wrappedPostId);
+			will(returnValue(wrappedPostMeta));
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
+			will(returnValue(VERIFIED));
+			oneOf(db).commitTransaction(txn);
+			oneOf(db).endTransaction(txn);
+		}});
+
+		BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
+				blog1.getId(), messageId, null, timestamp, timeReceived,
+				localAuthor1, VERIFIED, false, true);
+		blogManager.addLocalComment(localAuthor2, blog2.getId(), comment,
+				originalPostHeader);
+		context.assertIsSatisfied();
+
+		assertEquals(1, txn.getEvents().size());
+		assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
+
+		BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
+		assertEquals(blog2.getId(), e.getGroupId());
+
+		BlogPostHeader h = e.getHeader();
+		assertEquals(COMMENT, h.getType());
+		assertFalse(h.isRssFeed());
+		assertEquals(timestamp, h.getTimestamp());
+		assertEquals(timestamp, h.getTimeReceived());
+		assertEquals(commentId, h.getId());
+		assertEquals(blog2.getId(), h.getGroupId());
+		assertEquals(wrappedPostId, h.getParentId());
+		assertEquals(OURSELVES, h.getAuthorStatus());
+		assertEquals(localAuthor2, h.getAuthor());
+
+		assertTrue(h instanceof BlogCommentHeader);
+		BlogPostHeader h1 = ((BlogCommentHeader) h).getParent();
+		assertEquals(WRAPPED_POST, h1.getType());
+		assertFalse(h1.isRssFeed());
+		assertEquals(timestamp, h1.getTimestamp());
+		assertEquals(timeReceived, h1.getTimeReceived());
+		assertEquals(wrappedPostId, h1.getId());
+		assertEquals(blog2.getId(), h1.getGroupId());
+		assertNull(h1.getParentId());
+		assertEquals(VERIFIED, h1.getAuthorStatus());
+		assertEquals(localAuthor1, h1.getAuthor());
+
+		assertEquals(h1.getId(), ((BlogCommentHeader) h).getRootPost().getId());
+	}
+
+	@Test
+	public void testAddLocalCommentToRemoteRssPost() throws Exception {
+		final Transaction txn = new Transaction(null, false);
+		// The post was originally posted to the RSS blog, then reblogged to
+		// blog 1 with a comment
+		final BdfList originalPostBody = BdfList.of("originalPostBody");
+		final MessageId wrappedPostId = new MessageId(getRandomId());
+		final Message wrappedPostMsg = new Message(wrappedPostId, blog1.getId(),
+				timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
+		final BdfDictionary wrappedPostMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, WRAPPED_POST.getInt()),
+				new BdfEntry(KEY_RSS_FEED, true),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, rssMessageId),
+				new BdfEntry(KEY_AUTHOR, rssAuthorDict),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
+		);
+		final MessageId commentId = new MessageId(getRandomId());
+		final Message commentMsg = new Message(commentId, blog1.getId(),
+				timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
+		final BdfDictionary commentMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, COMMENT.getInt()),
+				new BdfEntry(KEY_COMMENT, comment),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
+				new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, rssMessageId),
+				new BdfEntry(KEY_PARENT_MSG_ID, wrappedPostId),
+				new BdfEntry(KEY_AUTHOR, authorDict1)
+		);
+
+		context.checking(new Expectations() {{
+			oneOf(db).startTransaction(false);
+			will(returnValue(txn));
+			// Wrap the original post for blog 1
+			oneOf(clientHelper).getMessageAsList(txn, rssMessageId);
+			will(returnValue(originalPostBody));
+			oneOf(db).getGroup(txn, rssBlog.getId());
+			will(returnValue(rssBlog.getGroup()));
+			oneOf(blogPostFactory).wrapPost(blog1.getId(),
+					rssBlog.getGroup().getDescriptor(), timestamp,
+					originalPostBody);
+			will(returnValue(wrappedPostMsg));
+			// Store the wrapped post
+			oneOf(clientHelper).addLocalMessage(txn, wrappedPostMsg,
+					wrappedPostMeta, true);
+			// Create the comment
+			oneOf(blogPostFactory).createBlogComment(blog1.getId(),
+					localAuthor1, comment, rssMessageId, wrappedPostId);
+			will(returnValue(commentMsg));
+			// Store the comment
+			oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
+					true);
+			// Create the headers for the comment and the wrapped post
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
+			will(returnValue(OURSELVES));
+			oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
+					wrappedPostId);
+			will(returnValue(wrappedPostMeta));
+			oneOf(db).commitTransaction(txn);
+			oneOf(db).endTransaction(txn);
+		}});
+
+		BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
+				rssBlog.getId(), rssMessageId, null, timestamp, timeReceived,
+				rssLocalAuthor, NONE, true, true);
+		blogManager.addLocalComment(localAuthor1, blog1.getId(), comment,
+				originalPostHeader);
+		context.assertIsSatisfied();
+
+		assertEquals(1, txn.getEvents().size());
+		assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
+
+		BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
+		assertEquals(blog1.getId(), e.getGroupId());
+
+		BlogPostHeader h = e.getHeader();
+		assertEquals(COMMENT, h.getType());
+		assertFalse(h.isRssFeed());
+		assertEquals(timestamp, h.getTimestamp());
+		assertEquals(timestamp, h.getTimeReceived());
+		assertEquals(commentId, h.getId());
+		assertEquals(blog1.getId(), h.getGroupId());
+		assertEquals(wrappedPostId, h.getParentId());
+		assertEquals(OURSELVES, h.getAuthorStatus());
+		assertEquals(localAuthor1, h.getAuthor());
+
+		assertTrue(h instanceof BlogCommentHeader);
+		BlogPostHeader h1 = ((BlogCommentHeader) h).getParent();
+		assertEquals(WRAPPED_POST, h1.getType());
+		assertTrue(h1.isRssFeed());
+		assertEquals(timestamp, h1.getTimestamp());
+		assertEquals(timeReceived, h1.getTimeReceived());
+		assertEquals(wrappedPostId, h1.getId());
+		assertEquals(blog1.getId(), h1.getGroupId());
+		assertNull(h1.getParentId());
+		assertEquals(NONE, h1.getAuthorStatus());
+		assertEquals(rssLocalAuthor, h1.getAuthor());
+
+		assertEquals(h1.getId(), ((BlogCommentHeader) h).getRootPost().getId());
+	}
+
+	@Test
+	public void testAddLocalCommentToRebloggedRemoteRssPost() throws Exception {
+		final Transaction txn = new Transaction(null, false);
+		// The post was originally posted to the RSS blog, then reblogged to
+		// blog 1 with a comment
+		final MessageId wrappedPostId = new MessageId(getRandomId());
+		final BdfList wrappedPostBody = BdfList.of("wrappedPostBody");
+		final MessageId originalCommentId = new MessageId(getRandomId());
+		final BdfList originalCommentBody = BdfList.of("originalCommentBody");
+		// The post and comment were reblogged to blog 2 with another comment
+		final MessageId rewrappedPostId = new MessageId(getRandomId());
+		final Message rewrappedPostMsg = new Message(rewrappedPostId,
+				blog2.getId(), timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
+		final BdfDictionary rewrappedPostMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, WRAPPED_POST.getInt()),
+				new BdfEntry(KEY_RSS_FEED, true),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, messageId),
+				new BdfEntry(KEY_AUTHOR, rssAuthorDict),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
+		);
+		final MessageId wrappedCommentId = new MessageId(getRandomId());
+		final Message wrappedCommentMsg = new Message(wrappedCommentId,
+				blog2.getId(), timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
+		final BdfDictionary wrappedCommentMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, WRAPPED_COMMENT.getInt()),
+				new BdfEntry(KEY_COMMENT, comment),
+				new BdfEntry(KEY_PARENT_MSG_ID, rewrappedPostId),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, originalCommentId),
+				new BdfEntry(KEY_AUTHOR, authorDict1),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
+		);
+		final String localComment = getRandomString(MAX_BLOG_COMMENT_LENGTH);
+		final MessageId localCommentId = new MessageId(getRandomId());
+		final Message localCommentMsg = new Message(localCommentId,
+				blog2.getId(), timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
+		final BdfDictionary localCommentMeta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, COMMENT.getInt()),
+				new BdfEntry(KEY_COMMENT, localComment),
+				new BdfEntry(KEY_TIMESTAMP, timestamp),
+				new BdfEntry(KEY_ORIGINAL_MSG_ID, localCommentId),
+				new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, originalCommentId),
+				new BdfEntry(KEY_PARENT_MSG_ID, wrappedCommentId),
+				new BdfEntry(KEY_AUTHOR, authorDict2)
+		);
+
+		context.checking(new Expectations() {{
+			oneOf(db).startTransaction(false);
+			will(returnValue(txn));
+			// Rewrap the wrapped post for blog 2
+			oneOf(clientHelper).getMessageAsList(txn, wrappedPostId);
+			will(returnValue(wrappedPostBody));
+			oneOf(blogPostFactory).rewrapWrappedPost(blog2.getId(),
+					wrappedPostBody);
+			will(returnValue(rewrappedPostMsg));
+			// Store the rewrapped post
+			oneOf(clientHelper).addLocalMessage(txn, rewrappedPostMsg,
+					rewrappedPostMeta, true);
+			// Wrap the original comment for blog 2
+			oneOf(clientHelper).getMessageAsList(txn, originalCommentId);
+			will(returnValue(originalCommentBody));
+			oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
+					wrappedPostId);
+			will(returnValue(rewrappedPostMeta));
+			oneOf(db).getGroup(txn, blog1.getId());
+			will(returnValue(blog1.getGroup()));
+			oneOf(blogPostFactory).wrapComment(blog2.getId(),
+					blog1.getGroup().getDescriptor(), timestamp,
+					originalCommentBody, rewrappedPostId);
+			will(returnValue(wrappedCommentMsg));
+			// Store the wrapped comment
+			oneOf(clientHelper).addLocalMessage(txn, wrappedCommentMsg,
+					wrappedCommentMeta, true);
+			// Create the new comment
+			oneOf(blogPostFactory).createBlogComment(blog2.getId(),
+					localAuthor2, localComment, originalCommentId,
+					wrappedCommentId);
+			will(returnValue(localCommentMsg));
+			// Store the new comment
+			oneOf(clientHelper).addLocalMessage(txn, localCommentMsg,
+					localCommentMeta, true);
+			// Create the headers for the new comment, the wrapped comment and
+			// the rewrapped post
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor2.getId());
+			will(returnValue(OURSELVES));
+			oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
+					wrappedCommentId);
+			will(returnValue(wrappedCommentMeta));
+			oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
+			will(returnValue(VERIFIED));
+			oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
+					rewrappedPostId);
+			will(returnValue(rewrappedPostMeta));
+			oneOf(db).commitTransaction(txn);
+			oneOf(db).endTransaction(txn);
+		}});
+
+		BlogPostHeader wrappedPostHeader = new BlogPostHeader(WRAPPED_POST,
+				blog1.getId(), wrappedPostId, null, timestamp, timeReceived,
+				rssLocalAuthor, NONE, true, true);
+		BlogCommentHeader originalCommentHeader = new BlogCommentHeader(COMMENT,
+				blog1.getId(), comment, wrappedPostHeader, originalCommentId,
+				timestamp, timeReceived, localAuthor1, VERIFIED, true);
+
+		blogManager.addLocalComment(localAuthor2, blog2.getId(), localComment,
+				originalCommentHeader);
+		context.assertIsSatisfied();
+
+		assertEquals(1, txn.getEvents().size());
+		assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
+
+		BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
+		assertEquals(blog2.getId(), e.getGroupId());
+
+		BlogPostHeader h = e.getHeader();
+		assertEquals(COMMENT, h.getType());
+		assertFalse(h.isRssFeed());
+		assertEquals(timestamp, h.getTimestamp());
+		assertEquals(timestamp, h.getTimeReceived());
+		assertEquals(localCommentId, h.getId());
+		assertEquals(blog2.getId(), h.getGroupId());
+		assertEquals(wrappedCommentId, h.getParentId());
+		assertEquals(OURSELVES, h.getAuthorStatus());
+		assertEquals(localAuthor2, h.getAuthor());
+
+		assertTrue(h instanceof BlogCommentHeader);
+		BlogPostHeader h1 = ((BlogCommentHeader) h).getParent();
+		assertEquals(WRAPPED_COMMENT, h1.getType());
+		assertFalse(h1.isRssFeed());
+		assertEquals(timestamp, h1.getTimestamp());
+		assertEquals(timeReceived, h1.getTimeReceived());
+		assertEquals(wrappedCommentId, h1.getId());
+		assertEquals(blog2.getId(), h1.getGroupId());
+		assertEquals(rewrappedPostId, h1.getParentId());
+		assertEquals(VERIFIED, h1.getAuthorStatus());
+		assertEquals(localAuthor1, h1.getAuthor());
+
+		assertTrue(h1 instanceof BlogCommentHeader);
+		BlogPostHeader h2 = ((BlogCommentHeader) h1).getParent();
+		assertEquals(WRAPPED_POST, h2.getType());
+		assertTrue(h2.isRssFeed());
+		assertEquals(timestamp, h2.getTimestamp());
+		assertEquals(timeReceived, h2.getTimeReceived());
+		assertEquals(rewrappedPostId, h2.getId());
+		assertEquals(blog2.getId(), h2.getGroupId());
+		assertNull(h2.getParentId());
+		assertEquals(NONE, h2.getAuthorStatus());
+		assertEquals(rssLocalAuthor, h2.getAuthor());
+
+		assertEquals(h2.getId(), ((BlogCommentHeader) h).getRootPost().getId());
+		assertEquals(h2.getId(),
+				((BlogCommentHeader) h1).getRootPost().getId());
 	}
 
 	@Test
@@ -262,17 +822,17 @@ public class BlogManagerImplTest extends BriarTestCase {
 		context.assertIsSatisfied();
 	}
 
-	private Blog createBlog() {
-		final GroupId groupId = new GroupId(getRandomId());
-		final Group group = new Group(groupId, CLIENT_ID, getRandomBytes(42));
-		final AuthorId authorId = new AuthorId(getRandomId());
-		final byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		final byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		final long created = System.currentTimeMillis();
-		final LocalAuthor localAuthor =
-				new LocalAuthor(authorId, "Author", publicKey, privateKey,
-						created);
-		return new Blog(group, localAuthor, false);
+	private LocalAuthor createLocalAuthor() {
+		return new LocalAuthor(new AuthorId(getRandomId()),
+				getRandomString(MAX_AUTHOR_NAME_LENGTH),
+				getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
+				getRandomBytes(123), System.currentTimeMillis());
+	}
+
+	private Blog createBlog(LocalAuthor localAuthor, boolean rssFeed) {
+		GroupId groupId = new GroupId(getRandomId());
+		Group group = new Group(groupId, CLIENT_ID, getRandomBytes(42));
+		return new Blog(group, localAuthor, rssFeed);
 	}
 
 	private BdfDictionary authorToBdfDictionary(Author a) {
@@ -283,4 +843,4 @@ public class BlogManagerImplTest extends BriarTestCase {
 		);
 	}
 
-}
+}
\ No newline at end of file