diff --git a/briar-api/src/net/sf/briar/api/db/event/SubscriptionsUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/LocalSubscriptionsUpdatedEvent.java
similarity index 51%
rename from briar-api/src/net/sf/briar/api/db/event/SubscriptionsUpdatedEvent.java
rename to briar-api/src/net/sf/briar/api/db/event/LocalSubscriptionsUpdatedEvent.java
index 2f878438b655a0d3adff422a67789671b3edf30d..a0114cee024118ea410c294a84259b1e913177ca 100644
--- a/briar-api/src/net/sf/briar/api/db/event/SubscriptionsUpdatedEvent.java
+++ b/briar-api/src/net/sf/briar/api/db/event/LocalSubscriptionsUpdatedEvent.java
@@ -9,20 +9,20 @@ import net.sf.briar.api.ContactId;
  * An event that is broadcast when the set of subscriptions visible to one or
  * more contacts is updated.
  */
-public class SubscriptionsUpdatedEvent extends DatabaseEvent {
+public class LocalSubscriptionsUpdatedEvent extends DatabaseEvent {
 
-	private final Collection<ContactId> affectedContacts;
+	private final Collection<ContactId> affected;
 
-	public SubscriptionsUpdatedEvent() {
-		affectedContacts = Collections.emptyList();
+	public LocalSubscriptionsUpdatedEvent() {
+		affected = Collections.emptyList();
 	}
 
-	public SubscriptionsUpdatedEvent(Collection<ContactId> affectedContacts) {
-		this.affectedContacts = affectedContacts;
+	public LocalSubscriptionsUpdatedEvent(Collection<ContactId> affected) {
+		this.affected = affected;
 	}
 
 	/** Returns the contacts affected by the update. */
 	public Collection<ContactId> getAffectedContacts() {
-		return affectedContacts;
+		return affected;
 	}
 }
diff --git a/briar-api/src/net/sf/briar/api/db/event/TransportsUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/LocalTransportsUpdatedEvent.java
similarity index 66%
rename from briar-api/src/net/sf/briar/api/db/event/TransportsUpdatedEvent.java
rename to briar-api/src/net/sf/briar/api/db/event/LocalTransportsUpdatedEvent.java
index 782b11cfe7f2cbbadd68cf61dc25a3106e069cf0..c60c23b83b1000df6d6b7d933bbb8f2822abfb2e 100644
--- a/briar-api/src/net/sf/briar/api/db/event/TransportsUpdatedEvent.java
+++ b/briar-api/src/net/sf/briar/api/db/event/LocalTransportsUpdatedEvent.java
@@ -4,6 +4,6 @@ package net.sf.briar.api.db.event;
  * An event that is broadcast when the local transport properties are
  * updated.
  */
-public class TransportsUpdatedEvent extends DatabaseEvent {
+public class LocalTransportsUpdatedEvent extends DatabaseEvent {
 
 }
diff --git a/briar-api/src/net/sf/briar/api/db/event/RemoteSubscriptionsUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/RemoteSubscriptionsUpdatedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..54436e52e4d0e15a3daf07b4a78bea4bf6c15a1a
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/event/RemoteSubscriptionsUpdatedEvent.java
@@ -0,0 +1,17 @@
+package net.sf.briar.api.db.event;
+
+import net.sf.briar.api.ContactId;
+
+/**  An event that is broadcast when a contact's subscriptions are updated. */
+public class RemoteSubscriptionsUpdatedEvent extends DatabaseEvent {
+
+	private final ContactId contactId;
+
+	public RemoteSubscriptionsUpdatedEvent(ContactId contactId) {
+		this.contactId = contactId;
+	}
+
+	public ContactId getContactId() {
+		return contactId;
+	}
+}
diff --git a/briar-api/src/net/sf/briar/api/db/event/RemoteTransportsUpdatedEvent.java b/briar-api/src/net/sf/briar/api/db/event/RemoteTransportsUpdatedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..6458ad54bd869f1763b919eb8e3dc18087455111
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/event/RemoteTransportsUpdatedEvent.java
@@ -0,0 +1,28 @@
+package net.sf.briar.api.db.event;
+
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.protocol.TransportId;
+
+/**
+ * An event that is broadcast when a contact's remote transport properties
+ * are updated.
+ */
+public class RemoteTransportsUpdatedEvent extends DatabaseEvent {
+
+	private final ContactId contactId;
+	private final TransportId transportId;
+
+	public RemoteTransportsUpdatedEvent(ContactId contactId,
+			TransportId transportId) {
+		this.contactId = contactId;
+		this.transportId = transportId;
+	}
+
+	public ContactId getContactId() {
+		return contactId;
+	}
+
+	public TransportId getTransportId() {
+		return transportId;
+	}
+}
diff --git a/briar-api/src/net/sf/briar/api/db/event/TransportAddedEvent.java b/briar-api/src/net/sf/briar/api/db/event/TransportAddedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..75156882292478155b0ef2b3c29e30511c5e6f8b
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/event/TransportAddedEvent.java
@@ -0,0 +1,17 @@
+package net.sf.briar.api.db.event;
+
+import net.sf.briar.api.protocol.TransportId;
+
+/** An event that is broadcast when a transport is added. */
+public class TransportAddedEvent extends DatabaseEvent {
+
+	private final TransportId transportId;
+
+	public TransportAddedEvent(TransportId transportId) {
+		this.transportId = transportId;
+	}
+
+	public TransportId getTransportId() {
+		return transportId;
+	}
+}
diff --git a/briar-api/src/net/sf/briar/api/db/event/TransportRemovedEvent.java b/briar-api/src/net/sf/briar/api/db/event/TransportRemovedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b7dd98a267a578282ed6ff6244cf22ccdc5e490
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/event/TransportRemovedEvent.java
@@ -0,0 +1,17 @@
+package net.sf.briar.api.db.event;
+
+import net.sf.briar.api.protocol.TransportId;
+
+/** An event that is broadcast when a transport is removed. */
+public class TransportRemovedEvent extends DatabaseEvent {
+
+	private final TransportId transportId;
+
+	public TransportRemovedEvent(TransportId transportId) {
+		this.transportId = transportId;
+	}
+
+	public TransportId getTransportId() {
+		return transportId;
+	}
+}
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index 229e912b63d6059df561e6dac5340c504d309e5a..98f4b0adb45d575c7514f1511021072e2c103867 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -31,12 +31,10 @@ import net.sf.briar.api.transport.TemporarySecret;
  * {@link #commitTransaction(T)}, even if an exception is thrown.
  * <p>
  * Locking is provided by the DatabaseComponent implementation. To prevent
- * deadlock, locks must be acquired in the following order:
+ * deadlock, locks must be acquired in the following (alphabetical) order:
  * <ul>
  * <li> contact
  * <li> message
- * <li> messageFlag
- * <li> messageStatus
  * <li> rating
  * <li> subscription
  * <li> transport
@@ -83,7 +81,7 @@ interface Database<T> {
 	/**
 	 * Adds a contact transport to the database.
 	 * <p>
-	 * Locking: contact read, window write.
+	 * Locking: contact read, transport read, window write.
 	 */
 	void addContactTransport(T txn, ContactTransport ct) throws DbException;
 
@@ -98,14 +96,14 @@ interface Database<T> {
 	/**
 	 * Records a received message as needing to be acknowledged.
 	 * <p>
-	 * Locking: contact read, messageStatus write.
+	 * Locking: contact read, message write.
 	 */
 	void addMessageToAck(T txn, ContactId c, MessageId m) throws DbException;
 
 	/**
 	 * Records a collection of sent messages as needing to be acknowledged.
 	 * <p>
-	 * Locking: contact read, message read, messageStatus write.
+	 * Locking: contact read, message write.
 	 */
 	void addOutstandingMessages(T txn, ContactId c, Collection<MessageId> sent)
 			throws DbException;
@@ -122,7 +120,7 @@ interface Database<T> {
 	 * Stores the given temporary secrets and deletes any secrets that have
 	 * been made obsolete.
 	 * <p>
-	 * Locking: contact read, window write.
+	 * Locking: contact read, transport read, window write.
 	 */
 	void addSecrets(T txn, Collection<TemporarySecret> secrets)
 			throws DbException;
@@ -158,7 +156,7 @@ interface Database<T> {
 	/**
 	 * Returns true if the database contains the given contact transport.
 	 * <p>
-	 * Locking: contact read, window read.
+	 * Locking: contact read, transport read, window read.
 	 */
 	boolean containsContactTransport(T txn, ContactId c, TransportId t)
 			throws DbException;
@@ -203,7 +201,7 @@ interface Database<T> {
 	/**
 	 * Returns all contact transports.
 	 * <p>
-	 * Locking: contact read, window read.
+	 * Locking: contact read, transport read, window read.
 	 */
 	Collection<ContactTransport> getContactTransports(T txn) throws DbException;
 
@@ -257,7 +255,7 @@ interface Database<T> {
 	/**
 	 * Returns the headers of all messages in the given group.
 	 * <p>
-	 * Locking: message read, messageFlag read.
+	 * Locking: message read.
 	 */
 	Collection<MessageHeader> getMessageHeaders(T txn, GroupId g)
 			throws DbException;
@@ -267,8 +265,7 @@ interface Database<T> {
 	 * if the message is not present in the database or is not sendable to the
 	 * given contact.
 	 * <p>
-	 * Locking: contact read, message read, messageStatus read,
-	 * subscription read.
+	 * Locking: contact read, message read, subscription read.
 	 */
 	byte[] getMessageIfSendable(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -285,7 +282,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages received from the given contact that
 	 * need to be acknowledged, up to the given number of messages.
 	 * <p>
-	 * Locking: contact read, messageStatus read.
+	 * Locking: contact read, message read.
 	 */
 	Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
 			throws DbException;
@@ -294,8 +291,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be sent to the
 	 * given contact, up to the given number of messages.
 	 * <p>
-	 * Locking: contact read, message read, messageStatus read,
-	 * subscription read.
+	 * Locking: contact read, message read, subscription read.
 	 */
 	Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
 			int maxMessages) throws DbException;
@@ -327,7 +323,7 @@ interface Database<T> {
 	/**
 	 * Returns true if the given message has been read.
 	 * <p>
-	 * Locking: message read, messageFlag read.
+	 * Locking: message read.
 	 */
 	boolean getReadFlag(T txn, MessageId m) throws DbException;
 
@@ -342,7 +338,7 @@ interface Database<T> {
 	/**
 	 * Returns all temporary secrets.
 	 * <p>
-	 * Locking: contact read, window read.
+	 * Locking: contact read, transport read, window read.
 	 */
 	Collection<TemporarySecret> getSecrets(T txn) throws DbException;
 
@@ -358,8 +354,7 @@ interface Database<T> {
 	 * given contact, with a total length less than or equal to the given
 	 * length.
 	 * <p>
-	 * Locking: contact read, message read, messageStatus read,
-	 * subscription read.
+	 * Locking: contact read, message read, subscription read.
 	 */
 	Collection<MessageId> getSendableMessages(T txn, ContactId c, int maxLength)
 			throws DbException;
@@ -367,7 +362,7 @@ interface Database<T> {
 	/**
 	 * Returns true if the given message has been starred.
 	 * <p>
-	 * Locking: message read, messageFlag read.
+	 * Locking: message read.
 	 */
 	boolean getStarredFlag(T txn, MessageId m) throws DbException;
 
@@ -425,7 +420,7 @@ interface Database<T> {
 	/**
 	 * Returns the number of unread messages in each subscribed group.
 	 * <p>
-	 * Locking: message read, messageFlag read, subscription read.
+	 * Locking: message read, subscription read.
 	 */
 	Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException;
 
@@ -439,7 +434,7 @@ interface Database<T> {
 	/**
 	 * Returns true if any messages are sendable to the given contact.
 	 * <p>
-	 * Locking: contact read, message read, messageStatus read.
+	 * Locking: contact read, message read.
 	 */
 	boolean hasSendableMessages(T txn, ContactId c) throws DbException;
 
@@ -447,7 +442,7 @@ interface Database<T> {
 	 * Increments the outgoing connection counter for the given contact
 	 * transport in the given rotation period and returns the old value;
 	 * <p>
-	 * Locking: contact read, window write.
+	 * Locking: contact read, transport read, window write.
 	 */
 	long incrementConnectionCounter(T txn, ContactId c, TransportId t,
 			long period) throws DbException;
@@ -471,53 +466,51 @@ interface Database<T> {
 			throws DbException;
 
 	/**
-	 * Removes outstanding messages that have been acknowledged. Any of the
-	 * messages that are still considered outstanding (Status.SENT) with
-	 * respect to the given contact are now considered seen (Status.SEEN).
+	 * Removes a contact (and all associated state) from the database.
 	 * <p>
-	 * Locking: contact read, message read, messageStatus write.
+	 * Locking: contact write, message write, subscription write,
+	 * transport write, window write.
 	 */
-	void removeOutstandingMessages(T txn, ContactId c,
-			Collection<MessageId> acked) throws DbException;
+	void removeContact(T txn, ContactId c) throws DbException;
 
 	/**
-	 * Marks the given messages received from the given contact as having been
-	 * acknowledged.
+	 * Removes a message (and all associated state) from the database.
 	 * <p>
-	 * Locking: contact read, messageStatus write.
+	 * Locking: contact read, message write.
 	 */
-	void removeMessagesToAck(T txn, ContactId c, Collection<MessageId> acked)
-			throws DbException;
+	void removeMessage(T txn, MessageId m) throws DbException;
 
 	/**
-	 * Removes a contact (and all associated state) from the database.
+	 * Marks the given messages received from the given contact as having been
+	 * acknowledged.
 	 * <p>
-	 * Locking: contact write, message write, messageFlag write,
-	 * messageStatus write, subscription write, transport write, window write.
+	 * Locking: contact read, message write.
 	 */
-	void removeContact(T txn, ContactId c) throws DbException;
+	void removeMessagesToAck(T txn, ContactId c, Collection<MessageId> acked)
+			throws DbException;
 
 	/**
-	 * Removes a message (and all associated state) from the database.
+	 * Removes outstanding messages that have been acknowledged. Any of the
+	 * messages that are still considered outstanding (Status.SENT) with
+	 * respect to the given contact are now considered seen (Status.SEEN).
 	 * <p>
-	 * Locking: contact read, message write, messageFlag write,
-	 * messageStatus write.
+	 * Locking: contact read, message write.
 	 */
-	void removeMessage(T txn, MessageId m) throws DbException;
+	void removeOutstandingMessages(T txn, ContactId c,
+			Collection<MessageId> acked) throws DbException;
 
 	/**
 	 * Unsubscribes from the given group. Any messages belonging to the group
 	 * are deleted from the database.
 	 * <p>
-	 * Locking: contact write, message write, messageFlag write,
-	 * messageStatus write, subscription write.
+	 * Locking: contact write, message write, subscription write.
 	 */
 	void removeSubscription(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Removes a transport (and all associated state) from the database.
 	 * <p>
-	 * Locking: contact read, transport write.
+	 * Locking: transport write.
 	 */
 	void removeTransport(T txn, TransportId t) throws DbException;
 
@@ -532,7 +525,7 @@ interface Database<T> {
 	 * Sets the connection reordering window for the given contact transport in
 	 * the given rotation period.
 	 * <p>
-	 * Locking: contact read, window write.
+	 * Locking: contact read, transport read, window write.
 	 */
 	void setConnectionWindow(T txn, ContactId c, TransportId t, long period,
 			long centre, byte[] bitmap) throws DbException;
@@ -555,7 +548,7 @@ interface Database<T> {
 	 * Marks the given message read or unread and returns true if it was
 	 * previously read.
 	 * <p>
-	 * Locking: message read, messageFlag write.
+	 * Locking: message write.
 	 */
 	boolean setRead(T txn, MessageId m, boolean read) throws DbException;
 
@@ -581,14 +574,14 @@ interface Database<T> {
 	 * Marks the given message starred or unstarred and returns true if it was
 	 * previously starred.
 	 * <p>
-	 * Locking: message read, messageFlag write.
+	 * Locking: message write.
 	 */
 	boolean setStarred(T txn, MessageId m, boolean starred) throws DbException;
 
 	/**
 	 * Sets the status of the given message with respect to the given contact.
 	 * <p>
-	 * Locking: contact read, message read, messageStatus write.
+	 * Locking: contact read, message write.
 	 */
 	void setStatus(T txn, ContactId c, MessageId m, Status s)
 			throws DbException;
@@ -599,8 +592,7 @@ interface Database<T> {
 	 * with respect to the contact to Status.SEEN and returns true; otherwise
 	 * returns false.
 	 * <p>
-	 * Locking: contact read, message read, messageStatus write,
-	 * subscription read.
+	 * Locking: contact read, message write, subscription read.
 	 */
 	boolean setStatusSeenIfVisible(T txn, ContactId c, MessageId m)
 			throws DbException;
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index d1308b3b1acb7bfd6ab746c8cf50a48c120a76b6..0ab4bfc60cddb3a94ce1dbabb0343b325d8a80aa 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -34,11 +34,15 @@ import net.sf.briar.api.db.event.ContactAddedEvent;
 import net.sf.briar.api.db.event.ContactRemovedEvent;
 import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
+import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
+import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
 import net.sf.briar.api.db.event.MessageAddedEvent;
 import net.sf.briar.api.db.event.MessageReceivedEvent;
 import net.sf.briar.api.db.event.RatingChangedEvent;
-import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent;
-import net.sf.briar.api.db.event.TransportsUpdatedEvent;
+import net.sf.briar.api.db.event.RemoteSubscriptionsUpdatedEvent;
+import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
+import net.sf.briar.api.db.event.TransportAddedEvent;
+import net.sf.briar.api.db.event.TransportRemovedEvent;
 import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
@@ -79,10 +83,6 @@ DatabaseCleaner.Callback {
 			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock messageLock =
 			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock messageFlagLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock messageStatusLock =
-			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock ratingLock =
 			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock subscriptionLock =
@@ -194,20 +194,25 @@ DatabaseCleaner.Callback {
 	public void addContactTransport(ContactTransport ct) throws DbException {
 		contactLock.readLock().lock();
 		try {
-			windowLock.writeLock().lock();
+			transportLock.readLock().lock();
 			try {
-				T txn = db.startTransaction();
+				windowLock.writeLock().lock();
 				try {
-					if(!db.containsContact(txn, ct.getContactId()))
-						throw new NoSuchContactException();
-					db.addContactTransport(txn, ct);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+					T txn = db.startTransaction();
+					try {
+						if(!db.containsContact(txn, ct.getContactId()))
+							throw new NoSuchContactException();
+						db.addContactTransport(txn, ct);
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
+					}
+				} finally {
+					windowLock.writeLock().unlock();
 				}
 			} finally {
-				windowLock.writeLock().unlock();
+				transportLock.readLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -220,26 +225,21 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.writeLock().lock();
 			try {
-				messageStatusLock.writeLock().lock();
+				subscriptionLock.readLock().lock();
 				try {
-					subscriptionLock.readLock().lock();
+					T txn = db.startTransaction();
 					try {
-						T txn = db.startTransaction();
-						try {
-							// Don't store the message if the user has
-							// unsubscribed from the group
-							if(db.containsSubscription(txn, m.getGroup()))
-								added = storeGroupMessage(txn, m, null);
-							db.commitTransaction(txn);
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
-						}
-					} finally {
-						subscriptionLock.readLock().unlock();
+						// Don't store the message if the user has
+						// unsubscribed from the group
+						if(db.containsSubscription(txn, m.getGroup()))
+							added = storeGroupMessage(txn, m, null);
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
 					}
 				} finally {
-					messageStatusLock.writeLock().unlock();
+					subscriptionLock.readLock().unlock();
 				}
 			} finally {
 				messageLock.writeLock().unlock();
@@ -257,7 +257,7 @@ DatabaseCleaner.Callback {
 	 * sendability of its ancestors if necessary, marks the message as seen by
 	 * the sender and unseen by all other contacts, and returns true.
 	 * <p>
-	 * Locking: contact read, message write, messageStatus write.
+	 * Locking: contact read, message write.
 	 * @param sender may be null for a locally generated message.
 	 */
 	private boolean storeGroupMessage(T txn, Message m, ContactId sender)
@@ -344,20 +344,15 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.writeLock().lock();
 			try {
-				messageStatusLock.writeLock().lock();
+				T txn = db.startTransaction();
 				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						added = storePrivateMessage(txn, m, c, false);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					messageStatusLock.writeLock().unlock();
+					if(!db.containsContact(txn, c))
+						throw new NoSuchContactException();
+					added = storePrivateMessage(txn, m, c, false);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
 				}
 			} finally {
 				messageLock.writeLock().unlock();
@@ -373,26 +368,31 @@ DatabaseCleaner.Callback {
 			throws DbException {
 		contactLock.readLock().lock();
 		try {
-			windowLock.writeLock().lock();
+			transportLock.readLock().lock();
 			try {
-				T txn = db.startTransaction();
+				windowLock.writeLock().lock();
 				try {
-					Collection<TemporarySecret> relevant =
-							new ArrayList<TemporarySecret>();
-					for(TemporarySecret s : secrets) {
-						ContactId c = s.getContactId();
-						TransportId t = s.getTransportId();
-						if(db.containsContactTransport(txn, c, t))
-							relevant.add(s);
+					T txn = db.startTransaction();
+					try {
+						Collection<TemporarySecret> relevant =
+								new ArrayList<TemporarySecret>();
+						for(TemporarySecret s : secrets) {
+							ContactId c = s.getContactId();
+							TransportId t = s.getTransportId();
+							if(db.containsContactTransport(txn, c, t))
+								relevant.add(s);
+						}
+						if(!secrets.isEmpty()) db.addSecrets(txn, relevant);
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
 					}
-					if(!secrets.isEmpty()) db.addSecrets(txn, relevant);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+				} finally {
+					windowLock.writeLock().unlock();
 				}
 			} finally {
-				windowLock.writeLock().unlock();
+				transportLock.readLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -413,6 +413,8 @@ DatabaseCleaner.Callback {
 		} finally {
 			transportLock.writeLock().unlock();
 		}
+		// Call the listeners outside the lock
+		callListeners(new TransportAddedEvent(t));
 	}
 
 	/**
@@ -421,7 +423,7 @@ DatabaseCleaner.Callback {
 	 * incoming, respectively; or returns false if the message is already in
 	 * the database.
 	 * <p>
-	 * Locking: contact read, message write, messageStatus write.
+	 * Locking: contact read, message write.
 	 */
 	private boolean storePrivateMessage(T txn, Message m, ContactId c,
 			boolean incoming) throws DbException {
@@ -442,7 +444,7 @@ DatabaseCleaner.Callback {
 		Collection<MessageId> acked;
 		contactLock.readLock().lock();
 		try {
-			messageStatusLock.readLock().lock();
+			messageLock.readLock().lock();
 			try {
 				T txn = db.startTransaction();
 				try {
@@ -455,11 +457,11 @@ DatabaseCleaner.Callback {
 					throw e;
 				}
 			} finally {
-				messageStatusLock.readLock().unlock();
+				messageLock.readLock().unlock();
 			}
 			if(acked.isEmpty()) return null;
 			// Record the contents of the ack
-			messageStatusLock.writeLock().lock();
+			messageLock.writeLock().lock();
 			try {
 				T txn = db.startTransaction();
 				try {
@@ -470,7 +472,7 @@ DatabaseCleaner.Callback {
 					throw e;
 				}
 			} finally {
-				messageStatusLock.writeLock().unlock();
+				messageLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -487,45 +489,41 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.readLock().lock();
 			try {
-				messageStatusLock.readLock().lock();
-				try {
-					subscriptionLock.readLock().lock();
-					try {
-						T txn = db.startTransaction();
-						try {
-							if(!db.containsContact(txn, c))
-								throw new NoSuchContactException();
-							ids = db.getSendableMessages(txn, c, maxLength);
-							for(MessageId m : ids) {
-								messages.add(db.getMessage(txn, m));
-							}
-							db.commitTransaction(txn);
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
-						}
-					} finally {
-						subscriptionLock.readLock().unlock();
-					}
-				} finally {
-					messageStatusLock.readLock().unlock();
-				}
-				if(messages.isEmpty()) return null;
-				messageStatusLock.writeLock().lock();
+				subscriptionLock.readLock().lock();
 				try {
 					T txn = db.startTransaction();
 					try {
-						db.addOutstandingMessages(txn, c, ids);
+						if(!db.containsContact(txn, c))
+							throw new NoSuchContactException();
+						ids = db.getSendableMessages(txn, c, maxLength);
+						for(MessageId m : ids) {
+							messages.add(db.getMessage(txn, m));
+						}
 						db.commitTransaction(txn);
 					} catch(DbException e) {
 						db.abortTransaction(txn);
 						throw e;
 					}
 				} finally {
-					messageStatusLock.writeLock().unlock();
+					subscriptionLock.readLock().unlock();
 				}
 			} finally {
-				messageLock.readLock().unlock();
+				messageLock.readLock().lock();
+			}
+			if(messages.isEmpty()) return null;
+			// Record the message as sent
+			messageLock.writeLock().lock();
+			try {
+				T txn = db.startTransaction();
+				try {
+					db.addOutstandingMessages(txn, c, ids);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				messageLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -542,54 +540,50 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.readLock().lock();
 			try {
-				messageStatusLock.readLock().lock();
-				try{
-					subscriptionLock.readLock().lock();
-					try {
-						T txn = db.startTransaction();
-						try {
-							if(!db.containsContact(txn, c))
-								throw new NoSuchContactException();
-							Iterator<MessageId> it = requested.iterator();
-							while(it.hasNext()) {
-								MessageId m = it.next();
-								byte[] raw = db.getMessageIfSendable(txn, c, m);
-								if(raw != null) {
-									if(raw.length > maxLength) break;
-									messages.add(raw);
-									ids.add(m);
-									maxLength -= raw.length;
-								}
-								it.remove();
-							}
-							db.commitTransaction(txn);
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
-						}
-					} finally {
-						subscriptionLock.readLock().unlock();
-					}
-				} finally {
-					messageStatusLock.readLock().unlock();
-				}
-				if(messages.isEmpty()) return null;
-				messageStatusLock.writeLock().lock();
+				subscriptionLock.readLock().lock();
 				try {
 					T txn = db.startTransaction();
 					try {
-						db.addOutstandingMessages(txn, c, ids);
+						if(!db.containsContact(txn, c))
+							throw new NoSuchContactException();
+						Iterator<MessageId> it = requested.iterator();
+						while(it.hasNext()) {
+							MessageId m = it.next();
+							byte[] raw = db.getMessageIfSendable(txn, c, m);
+							if(raw != null) {
+								if(raw.length > maxLength) break;
+								messages.add(raw);
+								ids.add(m);
+								maxLength -= raw.length;
+							}
+							it.remove();
+						}
 						db.commitTransaction(txn);
 					} catch(DbException e) {
 						db.abortTransaction(txn);
 						throw e;
 					}
 				} finally {
-					messageStatusLock.writeLock().unlock();
+					subscriptionLock.readLock().unlock();
 				}
 			} finally {
 				messageLock.readLock().unlock();
 			}
+			if(messages.isEmpty()) return null;
+			// Record the messages as sent
+			messageLock.writeLock().lock();
+			try {
+				T txn = db.startTransaction();
+				try {
+					db.addOutstandingMessages(txn, c, ids);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				messageLock.writeLock().unlock();
+			}
 		} finally {
 			contactLock.readLock().unlock();
 		}
@@ -603,20 +597,15 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.readLock().lock();
 			try {
-				messageStatusLock.readLock().lock();
+				T txn = db.startTransaction();
 				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						offered = db.getMessagesToOffer(txn, c, maxMessages);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					messageStatusLock.readLock().unlock();
+					if(!db.containsContact(txn, c))
+						throw new NoSuchContactException();
+					offered = db.getMessagesToOffer(txn, c, maxMessages);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
 				}
 			} finally {
 				messageLock.readLock().unlock();
@@ -780,20 +769,15 @@ DatabaseCleaner.Callback {
 			throws DbException {
 		messageLock.readLock().lock();
 		try {
-			messageFlagLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					Collection<MessageHeader> headers =
-							db.getMessageHeaders(txn, g);
-					db.commitTransaction(txn);
-					return headers;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				messageFlagLock.readLock().unlock();
+				Collection<MessageHeader> headers =
+						db.getMessageHeaders(txn, g);
+				db.commitTransaction(txn);
+				return headers;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
 			messageLock.readLock().unlock();
@@ -844,19 +828,25 @@ DatabaseCleaner.Callback {
 	public Collection<TemporarySecret> getSecrets() throws DbException {
 		contactLock.readLock().lock();
 		try {
-			windowLock.readLock().lock();
+			transportLock.readLock().lock();
 			try {
-				T txn = db.startTransaction();
+				windowLock.readLock().lock();
 				try {
-					Collection<TemporarySecret> secrets = db.getSecrets(txn);
-					db.commitTransaction(txn);
-					return secrets;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+					T txn = db.startTransaction();
+					try {
+						Collection<TemporarySecret> secrets =
+								db.getSecrets(txn);
+						db.commitTransaction(txn);
+						return secrets;
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
+					}
+				} finally {
+					windowLock.readLock().unlock();
 				}
 			} finally {
-				windowLock.readLock().unlock();
+				transportLock.readLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -883,25 +873,20 @@ DatabaseCleaner.Callback {
 	public Map<GroupId, Integer> getUnreadMessageCounts() throws DbException {
 		messageLock.readLock().lock();
 		try {
-			messageFlagLock.readLock().lock();
+			subscriptionLock.readLock().lock();
 			try {
-				subscriptionLock.readLock().lock();
+				T txn = db.startTransaction();
 				try {
-					T txn = db.startTransaction();
-					try {
-						Map<GroupId, Integer> counts =
-								db.getUnreadMessageCounts(txn);
-						db.commitTransaction(txn);
-						return counts;
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
+					Map<GroupId, Integer> counts =
+							db.getUnreadMessageCounts(txn);
+					db.commitTransaction(txn);
+					return counts;
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
 				}
 			} finally {
-				messageFlagLock.readLock().unlock();
+				subscriptionLock.readLock().unlock();
 			}
 		} finally {
 			messageLock.readLock().unlock();
@@ -935,26 +920,21 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.readLock().lock();
 			try {
-				messageStatusLock.readLock().lock();
+				subscriptionLock.readLock().lock();
 				try {
-					subscriptionLock.readLock().lock();
+					T txn = db.startTransaction();
 					try {
-						T txn = db.startTransaction();
-						try {
-							if(!db.containsContact(txn, c))
-								throw new NoSuchContactException();
-							boolean has = db.hasSendableMessages(txn, c);
-							db.commitTransaction(txn);
-							return has;
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
-						}
-					} finally {
-						subscriptionLock.readLock().unlock();
+						if(!db.containsContact(txn, c))
+							throw new NoSuchContactException();
+						boolean has = db.hasSendableMessages(txn, c);
+						db.commitTransaction(txn);
+						return has;
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
 					}
 				} finally {
-					messageStatusLock.readLock().unlock();
+					subscriptionLock.readLock().unlock();
 				}
 			} finally {
 				messageLock.readLock().unlock();
@@ -968,21 +948,27 @@ DatabaseCleaner.Callback {
 			long period) throws DbException {
 		contactLock.readLock().lock();
 		try {
-			windowLock.writeLock().lock();
+			transportLock.readLock().lock();
 			try {
-				T txn = db.startTransaction();
+				windowLock.writeLock().lock();
 				try {
-					if(!db.containsContactTransport(txn, c, t))
-						throw new NoSuchContactTransportException();
-					long l = db.incrementConnectionCounter(txn, c, t, period);
-					db.commitTransaction(txn);
-					return l;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+					T txn = db.startTransaction();
+					try {
+						if(!db.containsContactTransport(txn, c, t))
+							throw new NoSuchContactTransportException();
+						long counter = db.incrementConnectionCounter(txn, c, t,
+								period);
+						db.commitTransaction(txn);
+						return counter;
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
+					}
+				} finally {
+					windowLock.writeLock().unlock();
 				}
 			} finally {
-				windowLock.writeLock().unlock();
+				transportLock.readLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -1026,31 +1012,26 @@ DatabaseCleaner.Callback {
 			transportLock.writeLock().unlock();
 		}
 		// Call the listeners outside the lock
-		if(changed) callListeners(new TransportsUpdatedEvent());
+		if(changed) callListeners(new LocalTransportsUpdatedEvent());
 	}
 
 	public void receiveAck(ContactId c, Ack a) throws DbException {
 		contactLock.readLock().lock();
 		try {
-			messageLock.readLock().lock();
+			messageLock.writeLock().lock();
 			try {
-				messageStatusLock.writeLock().lock();
+				T txn = db.startTransaction();
 				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						db.removeOutstandingMessages(txn, c, a.getMessageIds());
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					messageStatusLock.writeLock().unlock();
+					if(!db.containsContact(txn, c))
+						throw new NoSuchContactException();
+					db.removeOutstandingMessages(txn, c, a.getMessageIds());
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
 				}
 			} finally {
-				messageLock.readLock().unlock();
+				messageLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -1063,26 +1044,21 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.writeLock().lock();
 			try {
-				messageStatusLock.writeLock().lock();
+				subscriptionLock.readLock().lock();
 				try {
-					subscriptionLock.readLock().lock();
+					T txn = db.startTransaction();
 					try {
-						T txn = db.startTransaction();
-						try {
-							if(!db.containsContact(txn, c))
-								throw new NoSuchContactException();
-							added = storeMessage(txn, c, m);
-							db.addMessageToAck(txn, c, m.getId());
-							db.commitTransaction(txn);
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
-						}
-					} finally {
-						subscriptionLock.readLock().unlock();
+						if(!db.containsContact(txn, c))
+							throw new NoSuchContactException();
+						added = storeMessage(txn, c, m);
+						db.addMessageToAck(txn, c, m.getId());
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
 					}
 				} finally {
-					messageStatusLock.writeLock().unlock();
+					subscriptionLock.readLock().unlock();
 				}
 			} finally {
 				messageLock.writeLock().unlock();
@@ -1099,8 +1075,7 @@ DatabaseCleaner.Callback {
 	 * Attempts to store a message received from the given contact, and returns
 	 * true if it was stored.
 	 * <p>
-	 * Locking: contact read, message write, messageStatus write,
-	 * subscription read.
+	 * Locking: contact read, message write, subscription read.
 	 */
 	private boolean storeMessage(T txn, ContactId c, Message m)
 			throws DbException {
@@ -1115,39 +1090,34 @@ DatabaseCleaner.Callback {
 		BitSet request;
 		contactLock.readLock().lock();
 		try {
-			messageLock.readLock().lock();
+			messageLock.writeLock().lock();
 			try {
-				messageStatusLock.writeLock().lock();
+				subscriptionLock.readLock().lock();
 				try {
-					subscriptionLock.readLock().lock();
+					T txn = db.startTransaction();
 					try {
-						T txn = db.startTransaction();
-						try {
-							if(!db.containsContact(txn, c))
-								throw new NoSuchContactException();
-							offered = o.getMessageIds();
-							request = new BitSet(offered.size());
-							Iterator<MessageId> it = offered.iterator();
-							for(int i = 0; it.hasNext(); i++) {
-								// If the message is not in the database, or if
-								// it is not visible to the contact, request it
-								MessageId m = it.next();
-								if(!db.setStatusSeenIfVisible(txn, c, m))
-									request.set(i);
-							}
-							db.commitTransaction(txn);
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
+						if(!db.containsContact(txn, c))
+							throw new NoSuchContactException();
+						offered = o.getMessageIds();
+						request = new BitSet(offered.size());
+						Iterator<MessageId> it = offered.iterator();
+						for(int i = 0; it.hasNext(); i++) {
+							// If the message is not in the database, or not
+							// visible to the contact, request it
+							MessageId m = it.next();
+							if(!db.setStatusSeenIfVisible(txn, c, m))
+								request.set(i);
 						}
-					} finally {
-						subscriptionLock.readLock().unlock();
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
 					}
 				} finally {
-					messageStatusLock.writeLock().unlock();
+					subscriptionLock.readLock().unlock();
 				}
 			} finally {
-				messageLock.readLock().unlock();
+				messageLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -1165,8 +1135,7 @@ DatabaseCleaner.Callback {
 				try {
 					if(!db.containsContact(txn, c))
 						throw new NoSuchContactException();
-					long version = a.getVersionNumber();
-					db.setSubscriptionUpdateAcked(txn, c, version);
+					db.setSubscriptionUpdateAcked(txn, c, a.getVersionNumber());
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
@@ -1202,6 +1171,8 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
+		// Call the listeners outside the lock
+		callListeners(new RemoteSubscriptionsUpdatedEvent(c));
 	}
 
 	public void receiveTransportAck(ContactId c, TransportAck a)
@@ -1215,8 +1186,7 @@ DatabaseCleaner.Callback {
 					if(!db.containsContact(txn, c))
 						throw new NoSuchContactException();
 					TransportId t = a.getId();
-					long version = a.getVersionNumber();
-					db.setTransportUpdateAcked(txn, c, t, version);
+					db.setTransportUpdateAcked(txn, c, t, a.getVersionNumber());
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
@@ -1252,6 +1222,8 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
+		// Call the listeners outside the lock
+		callListeners(new RemoteTransportsUpdatedEvent(c, t.getId()));
 	}
 
 	public void removeContact(ContactId c) throws DbException {
@@ -1259,40 +1231,30 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.writeLock().lock();
 			try {
-				messageFlagLock.writeLock().lock();
+				subscriptionLock.writeLock().lock();
 				try {
-					messageStatusLock.writeLock().lock();
+					transportLock.writeLock().lock();
 					try {
-						subscriptionLock.writeLock().lock();
+						windowLock.writeLock().lock();
 						try {
-							transportLock.writeLock().lock();
+							T txn = db.startTransaction();
 							try {
-								windowLock.writeLock().lock();
-								try {
-									T txn = db.startTransaction();
-									try {
-										if(!db.containsContact(txn, c))
-											throw new NoSuchContactException();
-										db.removeContact(txn, c);
-										db.commitTransaction(txn);
-									} catch(DbException e) {
-										db.abortTransaction(txn);
-										throw e;
-									}
-								} finally {
-									windowLock.writeLock().unlock();
-								}
-							} finally {
-								transportLock.writeLock().unlock();
+								if(!db.containsContact(txn, c))
+									throw new NoSuchContactException();
+								db.removeContact(txn, c);
+								db.commitTransaction(txn);
+							} catch(DbException e) {
+								db.abortTransaction(txn);
+								throw e;
 							}
 						} finally {
-							subscriptionLock.writeLock().unlock();
+							windowLock.writeLock().unlock();
 						}
 					} finally {
-						messageStatusLock.writeLock().unlock();
+						transportLock.writeLock().unlock();
 					}
 				} finally {
-					messageFlagLock.writeLock().unlock();
+					subscriptionLock.writeLock().unlock();
 				}
 			} finally {
 				messageLock.writeLock().unlock();
@@ -1304,24 +1266,48 @@ DatabaseCleaner.Callback {
 		callListeners(new ContactRemovedEvent(c));
 	}
 
+	public void removeTransport(TransportId t) throws DbException {
+		transportLock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				db.removeTransport(txn, t);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			transportLock.writeLock().unlock();
+		}
+		// Call the listeners outside the lock
+		callListeners(new TransportRemovedEvent(t));
+	}
+
 	public void setConnectionWindow(ContactId c, TransportId t, long period,
 			long centre, byte[] bitmap) throws DbException {
 		contactLock.readLock().lock();
 		try {
-			windowLock.writeLock().lock();
+			transportLock.readLock().lock();
 			try {
-				T txn = db.startTransaction();
+				windowLock.writeLock().lock();
 				try {
-					if(!db.containsContactTransport(txn, c, t))
-						throw new NoSuchContactTransportException();
-					db.setConnectionWindow(txn, c, t, period, centre, bitmap);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+					T txn = db.startTransaction();
+					try {
+						if(!db.containsContactTransport(txn, c, t))
+							throw new NoSuchContactTransportException();
+						db.setConnectionWindow(txn, c, t, period, centre,
+								bitmap);
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
+					}
+				} finally {
+					windowLock.writeLock().unlock();
 				}
 			} finally {
-				windowLock.writeLock().unlock();
+				transportLock.readLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -1362,32 +1348,27 @@ DatabaseCleaner.Callback {
 			throws DbException {
 		contactLock.readLock().lock();
 		try {
-			messageLock.readLock().lock();
+			messageLock.writeLock().lock();
 			try {
-				messageStatusLock.writeLock().lock();
+				subscriptionLock.readLock().lock();
 				try {
-					subscriptionLock.readLock().lock();
+					T txn = db.startTransaction();
 					try {
-						T txn = db.startTransaction();
-						try {
-							if(!db.containsContact(txn, c))
-								throw new NoSuchContactException();
-							for(MessageId m : seen) {
-								db.setStatusSeenIfVisible(txn, c, m);
-							}
-							db.commitTransaction(txn);
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
+						if(!db.containsContact(txn, c))
+							throw new NoSuchContactException();
+						for(MessageId m : seen) {
+							db.setStatusSeenIfVisible(txn, c, m);
 						}
-					} finally {
-						subscriptionLock.readLock().unlock();
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
 					}
 				} finally {
-					messageStatusLock.writeLock().unlock();
+					subscriptionLock.readLock().unlock();
 				}
 			} finally {
-				messageLock.readLock().unlock();
+				messageLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -1475,31 +1456,21 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.writeLock().lock();
 			try {
-				messageFlagLock.writeLock().lock();
+				subscriptionLock.writeLock().lock();
 				try {
-					messageStatusLock.writeLock().lock();
+					T txn = db.startTransaction();
 					try {
-						subscriptionLock.writeLock().lock();
-						try {
-							T txn = db.startTransaction();
-							try {
-								if(db.containsSubscription(txn, g)) {
-									affected = db.getVisibility(txn, g);
-									db.removeSubscription(txn, g);
-								}
-								db.commitTransaction(txn);
-							} catch(DbException e) {
-								db.abortTransaction(txn);
-								throw e;
-							}
-						} finally {
-							subscriptionLock.writeLock().unlock();
+						if(db.containsSubscription(txn, g)) {
+							affected = db.getVisibility(txn, g);
+							db.removeSubscription(txn, g);
 						}
-					} finally {
-						messageStatusLock.writeLock().unlock();
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
 					}
 				} finally {
-					messageFlagLock.writeLock().unlock();
+					subscriptionLock.writeLock().unlock();
 				}
 			} finally {
 				messageLock.writeLock().unlock();
@@ -1508,8 +1479,7 @@ DatabaseCleaner.Callback {
 			contactLock.writeLock().unlock();
 		}
 		// Call the listeners outside the lock
-		if(affected != null && !affected.isEmpty())
-			callListeners(new SubscriptionsUpdatedEvent(affected));
+		callListeners(new LocalSubscriptionsUpdatedEvent(affected));
 	}
 
 	public void checkFreeSpaceAndClean() throws DbException {
@@ -1537,24 +1507,14 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.writeLock().lock();
 			try {
-				messageFlagLock.writeLock().lock();
+				T txn = db.startTransaction();
 				try {
-					messageStatusLock.writeLock().lock();
-					try {
-						T txn = db.startTransaction();
-						try {
-							old = db.getOldMessages(txn, size);
-							for(MessageId m : old) removeMessage(txn, m);
-							db.commitTransaction(txn);
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
-						}
-					} finally {
-						messageStatusLock.writeLock().unlock();
-					}
-				} finally {
-					messageFlagLock.writeLock().unlock();
+					old = db.getOldMessages(txn, size);
+					for(MessageId m : old) removeMessage(txn, m);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
 				}
 			} finally {
 				messageLock.writeLock().unlock();
@@ -1568,7 +1528,7 @@ DatabaseCleaner.Callback {
 	/**
 	 * Removes the given message (and all associated state) from the database.
 	 * <p>
-	 * Locking: contact read, message write, messageStatus write.
+	 * Locking: contact read, message write.
 	 */
 	private void removeMessage(T txn, MessageId m) throws DbException {
 		int sendability = db.getSendability(txn, m);
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 708595a0b22adef9b0baeec6b61d7807105d7017..a4263a62a012b1a6bd084912b5e015ae5c2f3ce4 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -92,7 +92,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String INDEX_MESSAGES_BY_SENDABILITY =
 			"CREATE INDEX messagesBySendability ON messages (sendability)";
 
-	// Locking: contact read, messageStatus
+	// Locking: contact read, message
 	private static final String CREATE_MESSAGES_TO_ACK =
 			"CREATE TABLE messagesToAck"
 					+ " (messageId HASH NOT NULL,"
@@ -102,7 +102,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, message read, messageStatus
+	// Locking: contact read, message
 	private static final String CREATE_STATUSES =
 			"CREATE TABLE statuses"
 					+ " (messageId HASH NOT NULL,"
@@ -122,7 +122,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String INDEX_STATUSES_BY_CONTACT =
 			"CREATE INDEX statusesByContact ON statuses (contactId)";
 
-	// Locking: message read, messageFlag
+	// Locking: message
 	private static final String CREATE_FLAGS =
 			"CREATE TABLE flags"
 					+ " (messageId HASH NOT NULL,"
@@ -252,7 +252,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, window
+	// Locking: contact read, transport read, window
 	private static final String CREATE_CONTACT_TRANSPORTS =
 			"CREATE TABLE contactTransports"
 					+ " (contactId INT NOT NULL,"
@@ -269,7 +269,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, window
+	// Locking: contact read, transport read, window
 	private static final String CREATE_SECRETS =
 			"CREATE TABLE secrets"
 					+ " (contactId INT NOT NULL,"
diff --git a/briar-core/src/net/sf/briar/protocol/duplex/DuplexConnection.java b/briar-core/src/net/sf/briar/protocol/duplex/DuplexConnection.java
index 221dd002759075080f23e29acd729a78c64624c4..9321750e4c23da193d09c513184f22c368dc2241 100644
--- a/briar-core/src/net/sf/briar/protocol/duplex/DuplexConnection.java
+++ b/briar-core/src/net/sf/briar/protocol/duplex/DuplexConnection.java
@@ -29,8 +29,8 @@ import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessageAddedEvent;
 import net.sf.briar.api.db.event.MessageReceivedEvent;
-import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent;
-import net.sf.briar.api.db.event.TransportsUpdatedEvent;
+import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
+import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
 import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.Message;
@@ -127,13 +127,13 @@ abstract class DuplexConnection implements DatabaseListener {
 		} else if(e instanceof MessageAddedEvent) {
 			if(canSendOffer.getAndSet(false))
 				dbExecutor.execute(new GenerateOffer());
-		} else if(e instanceof SubscriptionsUpdatedEvent) {
+		} else if(e instanceof LocalSubscriptionsUpdatedEvent) {
 			Collection<ContactId> affected =
-					((SubscriptionsUpdatedEvent) e).getAffectedContacts();
+					((LocalSubscriptionsUpdatedEvent) e).getAffectedContacts();
 			if(affected.contains(contactId)) {
 				dbExecutor.execute(new GenerateSubscriptionUpdate());
 			}
-		} else if(e instanceof TransportsUpdatedEvent) {
+		} else if(e instanceof LocalTransportsUpdatedEvent) {
 			dbExecutor.execute(new GenerateTransportUpdate());
 		}
 	}
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index 298503a0c7d2af3659874806ec0ea7dea2152cbc..dfe7f39165bfb7a115e3ff46fe1b9bb6d9fc6697 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -19,7 +19,7 @@ import net.sf.briar.api.db.event.ContactRemovedEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessageAddedEvent;
 import net.sf.briar.api.db.event.RatingChangedEvent;
-import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent;
+import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
 import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
@@ -1377,7 +1377,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).removeVisibility(txn, contactId1, groupId);
 			oneOf(database).commitTransaction(txn);
 			oneOf(listener).eventOccurred(with(any(
-					SubscriptionsUpdatedEvent.class)));
+					LocalSubscriptionsUpdatedEvent.class)));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				shutdown);