diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index ad645ddfb10531f4d2c627ae825239b9f7a86a2f..ad0fb223e0fe77e67e9db09977a4e0747bc73e0c 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -38,29 +38,22 @@ import org.briarproject.api.transport.TemporarySecret;
  * terminated by calling either {@link #abortTransaction(T)} or
  * {@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 (alphabetical) order:
- * <ul>
- * <li> contact
- * <li> identity
- * <li> message
- * <li> retention
- * <li> setting
- * <li> subscription
- * <li> transport
- * <li> window
- * </ul>
- * If table A has a foreign key pointing to table B, we get a read lock on A to
- * read A, a write lock on A to write A, and write locks on A and B to write B.
+ * Read-write locking is provided by the DatabaseComponent implementation.
  */
 interface Database<T> {
 
-	/** Opens the database and returns true if the database already existed. */
+	/**
+	 * Opens the database and returns true if the database already existed.
+	 * <p>
+	 * Locking: write.
+	 */
 	boolean open() throws DbException, IOException;
 
 	/**
 	 * Prevents new transactions from starting, waits for all current
 	 * transactions to finish, and closes the database.
+	 * <p>
+	 * Locking: write.
 	 */
 	void close() throws DbException, IOException;
 
@@ -92,8 +85,7 @@ interface Database<T> {
 	 * Stores a contact associated with the given local and remote pseudonyms,
 	 * and returns an ID for the contact.
 	 * <p>
-	 * Locking: contact write, message write, retention write,
-	 * subscription write, transport write, window write.
+	 * Locking: write.
 	 */
 	ContactId addContact(T txn, Author remote, AuthorId local)
 			throws DbException;
@@ -101,7 +93,7 @@ interface Database<T> {
 	/**
 	 * Stores an endpoint.
 	 * <p>
-	 * Locking: window write.
+	 * Locking: write.
 	 */
 	void addEndpoint(T txn, Endpoint ep) throws DbException;
 
@@ -109,29 +101,28 @@ interface Database<T> {
 	 * Subscribes to a group, or returns false if the user already has the
 	 * maximum number of subscriptions.
 	 * <p>
-	 * Locking: message write, subscription write.
+	 * Locking: write.
 	 */
 	boolean addGroup(T txn, Group g) throws DbException;
 
 	/**
 	 * Stores a local pseudonym.
 	 * <p>
-	 * Locking: contact write, identity write, message write, retention write,
-	 * subscription write, transport write, window write.
+	 * Locking: write.
 	 */
 	void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
 
 	/**
 	 * Stores a message.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void addMessage(T txn, Message m, boolean local) throws DbException;
 
 	/**
 	 * Records that a message has been offered by the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
 
@@ -139,7 +130,7 @@ interface Database<T> {
 	 * Stores the given temporary secrets and deletes any secrets that have
 	 * been made obsolete.
 	 * <p>
-	 * Locking: window write.
+	 * Locking: write.
 	 */
 	void addSecrets(T txn, Collection<TemporarySecret> secrets)
 			throws DbException;
@@ -147,10 +138,10 @@ interface Database<T> {
 	/**
 	 * Initialises the status of the given message with respect to the given
 	 * contact.
+	 * <p>
+	 * Locking: write.
 	 * @param ack whether the message needs to be acknowledged.
 	 * @param seen whether the contact has seen the message.
-	 * <p>
-	 * Locking: message write.
 	 */
 	void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
 			throws DbException;
@@ -159,7 +150,7 @@ interface Database<T> {
 	 * Stores a transport and returns true if the transport was not previously
 	 * in the database.
 	 * <p>
-	 * Locking: transport write, window write.
+	 * Locking: write.
 	 */
 	boolean addTransport(T txn, TransportId t, long maxLatency)
 			throws DbException;
@@ -167,49 +158,49 @@ interface Database<T> {
 	/**
 	 * Makes a group visible to the given contact.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given contact.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	boolean containsContact(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given contact.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	boolean containsContact(T txn, ContactId c) throws DbException;
 
 	/**
 	 * Returns true if the user subscribes to the given group.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	boolean containsGroup(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given local pseudonym.
 	 * <p>
-	 * Locking: identity read.
+	 * Locking: read.
 	 */
 	boolean containsLocalAuthor(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given message.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	boolean containsMessage(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given transport.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	boolean containsTransport(T txn, TransportId t) throws DbException;
 
@@ -217,7 +208,7 @@ interface Database<T> {
 	 * Returns true if the user subscribes to the given group and the group is
 	 * visible to the given contact.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	boolean containsVisibleGroup(T txn, ContactId c, GroupId g)
 			throws DbException;
@@ -226,7 +217,7 @@ interface Database<T> {
 	 * Returns true if the database contains the given message and the message
 	 * is visible to the given contact.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: read.
 	 */
 	boolean containsVisibleMessage(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -234,7 +225,7 @@ interface Database<T> {
 	/**
 	 * Returns the number of messages offered by the given contact.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	int countOfferedMessages(T txn, ContactId c) throws DbException;
 
@@ -242,49 +233,49 @@ interface Database<T> {
 	 * Returns the status of all groups to which the user subscribes or can
 	 * subscribe, excluding inbox groups.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Collection<GroupStatus> getAvailableGroups(T txn) throws DbException;
 
 	/**
 	 * Returns the configuration for the given transport.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	TransportConfig getConfig(T txn, TransportId t) throws DbException;
 
 	/**
 	 * Returns the contact with the given ID.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	Contact getContact(T txn, ContactId c) throws DbException;
 
 	/**
 	 * Returns the IDs of all contacts.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	Collection<ContactId> getContactIds(T txn) throws DbException;
 
 	/**
 	 * Returns all contacts.
 	 * <p>
-	 * Locking: contact read, window read.
+	 * Locking: read.
 	 */
 	Collection<Contact> getContacts(T txn) throws DbException;
 
 	/**
 	 * Returns all contacts associated with the given local pseudonym.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns all endpoints.
 	 * <p>
-	 * Locking: window read.
+	 * Locking: read.
 	 */
 	Collection<Endpoint> getEndpoints(T txn) throws DbException;
 
@@ -298,14 +289,14 @@ interface Database<T> {
 	/**
 	 * Returns the group with the given ID, if the user subscribes to it.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Group getGroup(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns all groups to which the user subscribes.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Collection<Group> getGroups(T txn) throws DbException;
 
@@ -313,7 +304,7 @@ interface Database<T> {
 	 * Returns the ID of the inbox group for the given contact, or null if no
 	 * inbox group has been set.
 	 * <p>
-	 * Locking: contact read, subscription read.
+	 * Locking: read.
 	 */
 	GroupId getInboxGroupId(T txn, ContactId c) throws DbException;
 
@@ -321,7 +312,7 @@ interface Database<T> {
 	 * Returns the headers of all messages in the inbox group for the given
 	 * contact, or null if no inbox group has been set.
 	 * <p>
-	 * Locking: contact read, identity read, message read, subscription read.
+	 * Locking: read.
 	 */
 	Collection<MessageHeader> getInboxMessageHeaders(T txn, ContactId c)
 			throws DbException;
@@ -329,21 +320,21 @@ interface Database<T> {
 	/**
 	 * Returns the local pseudonym with the given ID.
 	 * <p>
-	 * Locking: identity read.
+	 * Locking: read.
 	 */
 	LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns all local pseudonyms.
 	 * <p>
-	 * Locking: identity read.
+	 * Locking: read.
 	 */
 	Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
 
 	/**
 	 * Returns the local transport properties for all transports.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	Map<TransportId, TransportProperties> getLocalProperties(T txn)
 			throws DbException;
@@ -351,7 +342,7 @@ interface Database<T> {
 	/**
 	 * Returns the local transport properties for the given transport.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	TransportProperties getLocalProperties(T txn, TransportId t)
 			throws DbException;
@@ -359,14 +350,14 @@ interface Database<T> {
 	/**
 	 * Returns the body of the message identified by the given ID.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	byte[] getMessageBody(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the headers of all messages in the given group.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Collection<MessageHeader> getMessageHeaders(T txn, GroupId g)
 			throws DbException;
@@ -375,7 +366,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: message read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
 			throws DbException;
@@ -384,7 +375,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be offered to the
 	 * given contact, up to the given number of messages.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
 			int maxMessages) throws DbException;
@@ -393,7 +384,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be sent to the
 	 * given contact, up to the given total length.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength)
 			throws DbException;
@@ -402,7 +393,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be requested from
 	 * the given contact, up to the given number of messages.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToRequest(T txn, ContactId c,
 			int maxMessages) throws DbException;
@@ -411,7 +402,7 @@ interface Database<T> {
 	 * Returns the IDs of the oldest messages in the database, with a total
 	 * size less than or equal to the given size.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getOldMessages(T txn, int size) throws DbException;
 
@@ -420,28 +411,28 @@ interface Database<T> {
 	 * has no parent, or the parent is absent from the database, or the parent
 	 * belongs to a different group.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	MessageId getParent(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the message identified by the given ID, in serialised form.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	byte[] getRawMessage(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns true if the given message is marked as read.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	boolean getReadFlag(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns all remote properties for the given transport.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	Map<ContactId, TransportProperties> getRemoteProperties(T txn,
 			TransportId t) throws DbException;
@@ -451,7 +442,7 @@ interface Database<T> {
 	 * given contact and have been requested by the contact, up to the given
 	 * total length.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
 			int maxLength) throws DbException;
@@ -459,7 +450,7 @@ interface Database<T> {
 	/**
 	 * Returns a retention ack for the given contact, or null if no ack is due.
 	 * <p>
-	 * Locking: retention write.
+	 * Locking: write.
 	 */
 	RetentionAck getRetentionAck(T txn, ContactId c) throws DbException;
 
@@ -467,7 +458,7 @@ interface Database<T> {
 	 * Returns a retention update for the given contact and updates its expiry
 	 * time using the given latency, or returns null if no update is due.
 	 * <p>
-	 * Locking: message read, retention write.
+	 * Locking: write.
 	 */
 	RetentionUpdate getRetentionUpdate(T txn, ContactId c, long maxLatency)
 			throws DbException;
@@ -475,21 +466,21 @@ interface Database<T> {
 	/**
 	 * Returns all temporary secrets.
 	 * <p>
-	 * Locking: window read.
+	 * Locking: read.
 	 */
 	Collection<TemporarySecret> getSecrets(T txn) throws DbException;
 
 	/**
 	 * Returns all settings.
 	 * <p>
-	 * Locking: setting read.
+	 * Locking: read.
 	 */
 	Settings getSettings(T txn) throws DbException;
 
 	/**
 	 * Returns all contacts who subscribe to the given group.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Collection<Contact> getSubscribers(T txn, GroupId g) throws DbException;
 
@@ -497,7 +488,7 @@ interface Database<T> {
 	 * Returns a subscription ack for the given contact, or null if no ack is
 	 * due.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	SubscriptionAck getSubscriptionAck(T txn, ContactId c) throws DbException;
 
@@ -505,7 +496,7 @@ interface Database<T> {
 	 * Returns a subscription update for the given contact and updates its
 	 * expiry time using the given latency, or returns null if no update is due.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c,
 			long maxLatency) throws DbException;
@@ -514,7 +505,7 @@ interface Database<T> {
 	 * Returns a collection of transport acks for the given contact, or null if
 	 * no acks are due.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	Collection<TransportAck> getTransportAcks(T txn, ContactId c)
 			throws DbException;
@@ -522,7 +513,7 @@ interface Database<T> {
 	/**
 	 * Returns the maximum latencies of all local transports.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	Map<TransportId, Long> getTransportLatencies(T txn) throws DbException;
 
@@ -531,7 +522,7 @@ interface Database<T> {
 	 * updates their expiry times using the given latency, or returns null if
 	 * no updates are due.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c,
 			long maxLatency) throws DbException;
@@ -539,14 +530,14 @@ interface Database<T> {
 	/**
 	 * Returns the number of unread messages in each subscribed group.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException;
 
 	/**
 	 * Returns the IDs of all contacts to which the given group is visible.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
 
@@ -555,7 +546,7 @@ interface Database<T> {
 	 * in the given rotation period and returns the old value, or -1 if the
 	 * counter does not exist.
 	 * <p>
-	 * Locking: window write.
+	 * Locking: write.
 	 */
 	long incrementConnectionCounter(T txn, ContactId c, TransportId t,
 			long period) throws DbException;
@@ -564,7 +555,7 @@ interface Database<T> {
 	 * Increments the retention time versions for all contacts to indicate that
 	 * the database's retention time has changed and updates should be sent.
 	 * <p>
-	 * Locking: retention write.
+	 * Locking: write.
 	 */
 	void incrementRetentionVersions(T txn) throws DbException;
 
@@ -572,7 +563,7 @@ interface Database<T> {
 	 * Marks the given messages as not needing to be acknowledged to the
 	 * given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void lowerAckFlag(T txn, ContactId c, Collection<MessageId> acked)
 			throws DbException;
@@ -581,7 +572,7 @@ interface Database<T> {
 	 * Marks the given messages as not having been requested by the given
 	 * contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void lowerRequestedFlag(T txn, ContactId c, Collection<MessageId> requested)
 			throws DbException;
@@ -590,7 +581,7 @@ interface Database<T> {
 	 * Merges the given configuration with the existing configuration for the
 	 * given transport.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	void mergeConfig(T txn, TransportId t, TransportConfig config)
 			throws DbException;
@@ -599,7 +590,7 @@ interface Database<T> {
 	 * Merges the given properties with the existing local properties for the
 	 * given transport.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	void mergeLocalProperties(T txn, TransportId t, TransportProperties p)
 			throws DbException;
@@ -607,36 +598,35 @@ interface Database<T> {
 	/**
 	 * Merges the given settings with the existing settings.
 	 * <p>
-	 * Locking: setting write.
+	 * Locking: write.
 	 */
 	void mergeSettings(T txn, Settings s) throws DbException;
 
 	/**
 	 * Marks a message as needing to be acknowledged to the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void raiseAckFlag(T txn, ContactId c, MessageId m) throws DbException;
 
 	/**
 	 * Marks a message as having been requested by the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void raiseRequestedFlag(T txn, ContactId c, MessageId m) throws DbException;
 
 	/**
 	 * Marks a message as having been seen by the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException;
 
 	/**
 	 * Removes a contact from the database.
 	 * <p>
-	 * Locking: contact write, message write, retention write,
-	 * subscription write, transport write, window write.
+	 * Locking: write.
 	 */
 	void removeContact(T txn, ContactId c) throws DbException;
 
@@ -644,7 +634,7 @@ interface Database<T> {
 	 * Unsubscribes from a group. Any messages belonging to the group are
 	 * deleted from the database.
 	 * <p>
-	 * Locking: message write, subscription write.
+	 * Locking: write.
 	 */
 	void removeGroup(T txn, GroupId g) throws DbException;
 
@@ -652,15 +642,14 @@ interface Database<T> {
 	 * Removes a local pseudonym (and all associated contacts) from the
 	 * database.
 	 * <p>
-	 * Locking: contact write, identity write, message write, retention write,
-	 * subscription write, transport write, window write.
+	 * Locking: write.
 	 */
 	void removeLocalAuthor(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Removes a message (and all associated state) from the database.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void removeMessage(T txn, MessageId m) throws DbException;
 
@@ -668,7 +657,7 @@ interface Database<T> {
 	 * Removes an offered message that was offered by the given contact, or
 	 * returns false if there is no such message.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -677,7 +666,7 @@ interface Database<T> {
 	 * Removes the given offered messages that were offered by the given
 	 * contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void removeOfferedMessages(T txn, ContactId c,
 			Collection<MessageId> requested) throws DbException;
@@ -685,14 +674,14 @@ interface Database<T> {
 	/**
 	 * Removes a transport (and all associated state) from the database.
 	 * <p>
-	 * Locking: transport write, window write.
+	 * Locking: write.
 	 */
 	void removeTransport(T txn, TransportId t) throws DbException;
 
 	/**
 	 * Makes a group invisible to the given contact.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
 
@@ -700,7 +689,7 @@ interface Database<T> {
 	 * Resets the transmission count and expiry time of the given message with
 	 * respect to the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
 
@@ -708,7 +697,7 @@ interface Database<T> {
 	 * Sets the connection reordering window for the given endpoint in the
 	 * given rotation period.
 	 * <p>
-	 * Locking: window write.
+	 * Locking: write.
 	 */
 	void setConnectionWindow(T txn, ContactId c, TransportId t, long period,
 			long centre, byte[] bitmap) throws DbException;
@@ -718,7 +707,7 @@ interface Database<T> {
 	 * true, unless an update with an equal or higher version number has
 	 * already been received from the contact.
 	 * <p>
-	 * Locking: message write, subscription write.
+	 * Locking: write.
 	 */
 	boolean setGroups(T txn, ContactId c, Collection<Group> groups,
 			long version) throws DbException;
@@ -727,14 +716,14 @@ interface Database<T> {
 	 * Makes a group visible to the given contact, adds it to the contact's
 	 * subscriptions, and sets it as the inbox group for the contact.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	public void setInboxGroup(T txn, ContactId c, Group g) throws DbException;
 
 	/**
 	 * Marks a message as read or unread.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void setReadFlag(T txn, MessageId m, boolean read) throws DbException;
 
@@ -742,7 +731,7 @@ interface Database<T> {
 	 * Sets the remote transport properties for the given contact, replacing
 	 * any existing properties.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	void setRemoteProperties(T txn, ContactId c,
 			Map<TransportId, TransportProperties> p) throws DbException;
@@ -753,7 +742,7 @@ interface Database<T> {
 	 * unless an update with an equal or higher version number has already been
 	 * received from the contact.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	boolean setRemoteProperties(T txn, ContactId c, TransportId t,
 			TransportProperties p, long version) throws DbException;
@@ -763,7 +752,7 @@ interface Database<T> {
 	 * true, unless an update with an equal or higher version number has
 	 * already been received from the contact.
 	 * <p>
-	 * Locking: retention write.
+	 * Locking: write.
 	 */
 	boolean setRetentionTime(T txn, ContactId c, long retention, long version)
 			throws DbException;
@@ -772,7 +761,7 @@ interface Database<T> {
 	 * Records a retention ack from the given contact for the given version,
 	 * unless the contact has already acked an equal or higher version.
 	 * <p>
-	 * Locking: retention write.
+	 * Locking: write.
 	 */
 	void setRetentionUpdateAcked(T txn, ContactId c, long version)
 			throws DbException;
@@ -781,7 +770,7 @@ interface Database<T> {
 	 * Records a subscription ack from the given contact for the given version,
 	 * unless the contact has already acked an equal or higher version.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	void setSubscriptionUpdateAcked(T txn, ContactId c, long version)
 			throws DbException;
@@ -790,7 +779,7 @@ interface Database<T> {
 	 * Records a transport ack from the give contact for the given version,
 	 * unless the contact has already acked an equal or higher version.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	void setTransportUpdateAcked(T txn, ContactId c, TransportId t,
 			long version) throws DbException;
@@ -798,7 +787,7 @@ interface Database<T> {
 	/**
 	 * Makes a group visible or invisible to future contacts by default.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	void setVisibleToAll(T txn, GroupId g, boolean all) throws DbException;
 
@@ -807,7 +796,7 @@ interface Database<T> {
 	 * with respect to the given contact, using the latency of the transport
 	 * over which it was sent.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void updateExpiryTime(T txn, ContactId c, MessageId m, long maxLatency)
 			throws DbException;
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index ed245931e0a26690485c8dc1197bad27e30f0f99..ffc1061c56d1ec4dc26b9011f0ec8517a97275e3 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -93,38 +93,17 @@ DatabaseCleaner.Callback {
 			Logger.getLogger(DatabaseComponentImpl.class.getName());
 	private static final int MS_BETWEEN_SWEEPS = 10 * 1000; // 10 seconds
 
-	/*
-	 * Locks must always be acquired in alphabetical order. See the Database
-	 * interface to find out which calls require which locks.
-	 */
-
-	private final ReentrantReadWriteLock contactLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock identityLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock messageLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock retentionLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock settingLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock subscriptionLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock transportLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock windowLock =
-			new ReentrantReadWriteLock(true);
-
 	private final Database<T> db;
 	private final DatabaseCleaner cleaner;
 	private final ShutdownManager shutdown;
 
+	private final ReentrantReadWriteLock lock =
+			new ReentrantReadWriteLock(true);
 	private final Collection<EventListener> listeners =
 			new CopyOnWriteArrayList<EventListener>();
 
-	private final Object openCloseLock = new Object();
-	private boolean open = false; // Locking: openCloseLock;
-	private int shutdownHandle = -1; // Locking: openCloseLock;
+	private boolean open = false; // Locking: lock.writeLock
+	private int shutdownHandle = -1; // Locking: lock.writeLock
 
 	@Inject
 	DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner,
@@ -135,39 +114,47 @@ DatabaseCleaner.Callback {
 	}
 
 	public boolean open() throws DbException, IOException {
-		synchronized(openCloseLock) {
+		Runnable shutdownHook = new Runnable() {
+			public void run() {
+				lock.writeLock().lock();
+				try {
+					shutdownHandle = -1;
+					close();
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(IOException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} finally {
+					lock.writeLock().unlock();
+				}
+			}
+		};
+		lock.writeLock().lock();
+		try {
 			if(open) throw new IllegalStateException();
 			open = true;
 			boolean reopened = db.open();
 			cleaner.startCleaning(this, MS_BETWEEN_SWEEPS);
-			shutdownHandle = shutdown.addShutdownHook(new Runnable() {
-				public void run() {
-					try {
-						synchronized(openCloseLock) {
-							shutdownHandle = -1;
-							close();
-						}
-					} catch(DbException e) {
-						if(LOG.isLoggable(WARNING))
-							LOG.log(WARNING, e.toString(), e);
-					} catch(IOException e) {
-						if(LOG.isLoggable(WARNING))
-							LOG.log(WARNING, e.toString(), e);
-					}
-				}
-			});
+			shutdownHandle = shutdown.addShutdownHook(shutdownHook);
 			return reopened;
+		} finally {
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void close() throws DbException, IOException {
-		synchronized(openCloseLock) {
+		lock.writeLock().lock();
+		try {
 			if(!open) return;
 			open = false;
 			if(shutdownHandle != -1)
 				shutdown.removeShutdownHook(shutdownHandle);
 			cleaner.stopCleaning();
 			db.close();
+		} finally {
+			lock.writeLock().unlock();
 		}
 	}
 
@@ -179,208 +166,119 @@ DatabaseCleaner.Callback {
 		listeners.remove(l);
 	}
 
+	/** Notifies all listeners of a database event. */
+	private void callListeners(Event e) {
+		for(EventListener l : listeners) l.eventOccurred(e);
+	}
+
 	public ContactId addContact(Author remote, AuthorId local)
 			throws DbException {
 		ContactId c;
-		contactLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			identityLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				messageLock.writeLock().lock();
-				try {
-					retentionLock.writeLock().lock();
-					try {
-						subscriptionLock.writeLock().lock();
-						try {
-							transportLock.writeLock().lock();
-							try {
-								windowLock.writeLock().lock();
-								try {
-									T txn = db.startTransaction();
-									try {
-										if(db.containsContact(txn, remote.getId()))
-											throw new ContactExistsException();
-										if(!db.containsLocalAuthor(txn, local))
-											throw new NoSuchLocalAuthorException();
-										c = db.addContact(txn, remote, local);
-										db.commitTransaction(txn);
-									} catch(DbException e) {
-										db.abortTransaction(txn);
-										throw e;
-									}
-								} finally {
-									windowLock.writeLock().unlock();
-								}
-							} finally {
-								transportLock.writeLock().unlock();
-							}
-						} finally {
-							subscriptionLock.writeLock().unlock();
-						}
-					} finally {
-						retentionLock.writeLock().unlock();
-					}
-				} finally {
-					messageLock.writeLock().unlock();
-				}
-			} finally {
-				identityLock.readLock().unlock();
+				if(db.containsContact(txn, remote.getId()))
+					throw new ContactExistsException();
+				if(!db.containsLocalAuthor(txn, local))
+					throw new NoSuchLocalAuthorException();
+				c = db.addContact(txn, remote, local);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new ContactAddedEvent(c));
 		return c;
 	}
 
-	/** Notifies all listeners of a database event. */
-	private void callListeners(Event e) {
-		for(EventListener l : listeners) l.eventOccurred(e);
-	}
-
 	public void addEndpoint(Endpoint ep) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, ep.getContactId()))
-							throw new NoSuchContactException();
-						if(!db.containsTransport(txn, ep.getTransportId()))
-							throw new NoSuchTransportException();
-						db.addEndpoint(txn, ep);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					windowLock.writeLock().unlock();
-				}
-			} finally {
-				transportLock.readLock().unlock();
+				if(!db.containsContact(txn, ep.getContactId()))
+					throw new NoSuchContactException();
+				if(!db.containsTransport(txn, ep.getTransportId()))
+					throw new NoSuchTransportException();
+				db.addEndpoint(txn, ep);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public boolean addGroup(Group g) throws DbException {
 		boolean added = false;
-		messageLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsGroup(txn, g.getId()))
-						added = db.addGroup(txn, g);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsGroup(txn, g.getId()))
+					added = db.addGroup(txn, g);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			messageLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(added) callListeners(new SubscriptionAddedEvent(g));
 		return added;
 	}
 
 	public void addLocalAuthor(LocalAuthor a) throws DbException {
-		contactLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			identityLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				messageLock.writeLock().lock();
-				try {
-					retentionLock.writeLock().lock();
-					try {
-						subscriptionLock.writeLock().lock();
-						try {
-							transportLock.writeLock().lock();
-							try {
-								windowLock.writeLock().lock();
-								try {
-									T txn = db.startTransaction();
-									try {
-										if(db.containsLocalAuthor(txn, a.getId()))
-											throw new LocalAuthorExistsException();
-										db.addLocalAuthor(txn, a);
-										db.commitTransaction(txn);
-									} catch(DbException e) {
-										db.abortTransaction(txn);
-										throw e;
-									}
-								} finally {
-									windowLock.writeLock().unlock();
-								}
-							} finally {
-								transportLock.writeLock().unlock();
-							}
-						} finally {
-							subscriptionLock.writeLock().unlock();
-						}
-					} finally {
-						retentionLock.writeLock().unlock();
-					}
-				} finally {
-					messageLock.writeLock().unlock();
-				}
-			} finally {
-				identityLock.writeLock().unlock();
+				if(db.containsLocalAuthor(txn, a.getId()))
+					throw new LocalAuthorExistsException();
+				db.addLocalAuthor(txn, a);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new LocalAuthorAddedEvent(a.getId()));
 	}
 
 	public void addLocalMessage(Message m) throws DbException {
 		boolean duplicate;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						duplicate = db.containsMessage(txn, m.getId());
-						if(!duplicate) {
-							GroupId g = m.getGroup().getId();
-							if(db.containsGroup(txn, g))
-								addMessage(txn, m, null);
-						}
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				duplicate = db.containsMessage(txn, m.getId());
+				if(!duplicate && db.containsGroup(txn, m.getGroup().getId()))
+					addMessage(txn, m, null);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
-		if(!duplicate)
-			callListeners(new MessageAddedEvent(m.getGroup(), null));
+		if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), null));
 	}
 
 	/**
 	 * Stores a message, initialises its status with respect to each contact,
 	 * and marks it as read if it was locally generated.
 	 * <p>
-	 * Locking: contact read, message write, subscription read.
+	 * Locking: write.
 	 * @param sender null for a locally generated message.
 	 */
 	private void addMessage(T txn, Message m, ContactId sender)
@@ -408,60 +306,43 @@ DatabaseCleaner.Callback {
 
 	public void addSecrets(Collection<TemporarySecret> secrets)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						Collection<TemporarySecret> relevant =
-								new ArrayList<TemporarySecret>();
-						for(TemporarySecret s : secrets) {
-							ContactId c = s.getContactId();
-							if(!db.containsContact(txn, c)) continue;
-							TransportId t = s.getTransportId();
-							if(!db.containsTransport(txn, t)) continue;
+				Collection<TemporarySecret> relevant =
+						new ArrayList<TemporarySecret>();
+				for(TemporarySecret s : secrets) {
+					if(db.containsContact(txn, s.getContactId()))
+						if(db.containsTransport(txn, s.getTransportId()))
 							relevant.add(s);
-						}
-						if(!secrets.isEmpty()) db.addSecrets(txn, relevant);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					windowLock.writeLock().unlock();
 				}
-			} finally {
-				transportLock.readLock().unlock();
+				if(!secrets.isEmpty()) db.addSecrets(txn, relevant);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public boolean addTransport(TransportId t, long maxLatency)
 			throws DbException {
 		boolean added;
-		transportLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			windowLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					added = db.addTransport(txn, t, maxLatency);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				windowLock.writeLock().unlock();
+				added = db.addTransport(txn, t, maxLatency);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			transportLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(added) callListeners(new TransportAddedEvent(t, maxLatency));
 		return added;
@@ -469,26 +350,21 @@ DatabaseCleaner.Callback {
 
 	public Ack generateAck(ContactId c, int maxMessages) throws DbException {
 		Collection<MessageId> ids;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					ids = db.getMessagesToAck(txn, c, maxMessages);
-					if(!ids.isEmpty()) db.lowerAckFlag(txn, c, ids);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getMessagesToAck(txn, c, maxMessages);
+				if(!ids.isEmpty()) db.lowerAckFlag(txn, c, ids);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(ids.isEmpty()) return null;
 		return new Ack(ids);
@@ -498,35 +374,25 @@ DatabaseCleaner.Callback {
 			long maxLatency) throws DbException {
 		Collection<MessageId> ids;
 		List<byte[]> messages = new ArrayList<byte[]>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						ids = db.getMessagesToSend(txn, c, maxLength);
-						for(MessageId m : ids) {
-							messages.add(db.getRawMessage(txn, m));
-							db.updateExpiryTime(txn, c, m, maxLatency);
-						}
-						if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getMessagesToSend(txn, c, maxLength);
+				for(MessageId m : ids) {
+					messages.add(db.getRawMessage(txn, m));
+					db.updateExpiryTime(txn, c, m, maxLatency);
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(messages.isEmpty()) return null;
 		return Collections.unmodifiableList(messages);
@@ -535,32 +401,22 @@ DatabaseCleaner.Callback {
 	public Offer generateOffer(ContactId c, int maxMessages, long maxLatency)
 			throws DbException {
 		Collection<MessageId> ids;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						ids = db.getMessagesToOffer(txn, c, maxMessages);
-						for(MessageId m : ids)
-							db.updateExpiryTime(txn, c, m, maxLatency);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getMessagesToOffer(txn, c, maxMessages);
+				for(MessageId m : ids)
+					db.updateExpiryTime(txn, c, m, maxLatency);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(ids.isEmpty()) return null;
 		return new Offer(ids);
@@ -569,26 +425,21 @@ DatabaseCleaner.Callback {
 	public Request generateRequest(ContactId c, int maxMessages)
 			throws DbException {
 		Collection<MessageId> ids;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					ids = db.getMessagesToRequest(txn, c, maxMessages);
-					if(!ids.isEmpty()) db.removeOfferedMessages(txn, c, ids);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getMessagesToRequest(txn, c, maxMessages);
+				if(!ids.isEmpty()) db.removeOfferedMessages(txn, c, ids);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(ids.isEmpty()) return null;
 		return new Request(ids);
@@ -598,199 +449,153 @@ DatabaseCleaner.Callback {
 			long maxLatency) throws DbException {
 		Collection<MessageId> ids;
 		List<byte[]> messages = new ArrayList<byte[]>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						ids = db.getRequestedMessagesToSend(txn, c, maxLength);
-						for(MessageId m : ids) {
-							messages.add(db.getRawMessage(txn, m));
-							db.updateExpiryTime(txn, c, m, maxLatency);
-						}
-						if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getRequestedMessagesToSend(txn, c, maxLength);
+				for(MessageId m : ids) {
+					messages.add(db.getRawMessage(txn, m));
+					db.updateExpiryTime(txn, c, m, maxLatency);
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(messages.isEmpty()) return null;
 		return Collections.unmodifiableList(messages);
 	}
 
 	public RetentionAck generateRetentionAck(ContactId c) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					RetentionAck a = db.getRetentionAck(txn, c);
-					db.commitTransaction(txn);
-					return a;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				retentionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				RetentionAck a = db.getRetentionAck(txn, c);
+				db.commitTransaction(txn);
+				return a;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public RetentionUpdate generateRetentionUpdate(ContactId c, long maxLatency)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				retentionLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						RetentionUpdate u =
-								db.getRetentionUpdate(txn, c, maxLatency);
-						db.commitTransaction(txn);
-						return u;
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					retentionLock.writeLock().unlock();
-				}
-			} finally {
-				messageLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				RetentionUpdate u = db.getRetentionUpdate(txn, c, maxLatency);
+				db.commitTransaction(txn);
+				return u;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public SubscriptionAck generateSubscriptionAck(ContactId c)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					SubscriptionAck a = db.getSubscriptionAck(txn, c);
-					db.commitTransaction(txn);
-					return a;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				SubscriptionAck a = db.getSubscriptionAck(txn, c);
+				db.commitTransaction(txn);
+				return a;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public SubscriptionUpdate generateSubscriptionUpdate(ContactId c,
 			long maxLatency) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					SubscriptionUpdate u =
-							db.getSubscriptionUpdate(txn, c, maxLatency);
-					db.commitTransaction(txn);
-					return u;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				SubscriptionUpdate u =
+						db.getSubscriptionUpdate(txn, c, maxLatency);
+				db.commitTransaction(txn);
+				return u;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public Collection<TransportAck> generateTransportAcks(ContactId c)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					Collection<TransportAck> acks = db.getTransportAcks(txn, c);
-					db.commitTransaction(txn);
-					return acks;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				Collection<TransportAck> acks = db.getTransportAcks(txn, c);
+				db.commitTransaction(txn);
+				return acks;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public Collection<TransportUpdate> generateTransportUpdates(ContactId c,
 			long maxLatency) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					Collection<TransportUpdate> updates =
-							db.getTransportUpdates(txn, c, maxLatency);
-					db.commitTransaction(txn);
-					return updates;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				Collection<TransportUpdate> updates =
+						db.getTransportUpdates(txn, c, maxLatency);
+				db.commitTransaction(txn);
+				return updates;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public Collection<GroupStatus> getAvailableGroups() throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -802,12 +607,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public TransportConfig getConfig(TransportId t) throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -821,12 +626,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Contact getContact(ContactId c) throws DbException {
-		contactLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -840,34 +645,29 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<Contact> getContacts() throws DbException {
-		contactLock.readLock().lock();
+		lock.readLock().lock();
 		try {
-			windowLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					Collection<Contact> contacts = db.getContacts(txn);
-					db.commitTransaction(txn);
-					return contacts;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				windowLock.readLock().unlock();
+				Collection<Contact> contacts = db.getContacts(txn);
+				db.commitTransaction(txn);
+				return contacts;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Group getGroup(GroupId g) throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -881,12 +681,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<Group> getGroups() throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -898,72 +698,52 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public GroupId getInboxGroupId(ContactId c) throws DbException {
-		contactLock.readLock().lock();
+		lock.readLock().lock();
 		try {
-			subscriptionLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					GroupId inbox = db.getInboxGroupId(txn, c);
-					db.commitTransaction(txn);
-					return inbox;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				GroupId inbox = db.getInboxGroupId(txn, c);
+				db.commitTransaction(txn);
+				return inbox;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<MessageHeader> getInboxMessageHeaders(ContactId c)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.readLock().lock();
 		try {
-			identityLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				messageLock.readLock().lock();
-				try {
-					subscriptionLock.readLock().lock();
-					try {
-						T txn = db.startTransaction();
-						try {
-							if(!db.containsContact(txn, c))
-								throw new NoSuchContactException();
-							Collection<MessageHeader> headers =
-									db.getInboxMessageHeaders(txn, c);
-							db.commitTransaction(txn);
-							return headers;
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
-						}
-					} finally {
-						subscriptionLock.readLock().unlock();
-					}
-				} finally {
-					messageLock.readLock().unlock();
-				}
-			} finally {
-				identityLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				Collection<MessageHeader> headers =
+						db.getInboxMessageHeaders(txn, c);
+				db.commitTransaction(txn);
+				return headers;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
-		identityLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -977,12 +757,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			identityLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<LocalAuthor> getLocalAuthors() throws DbException {
-		identityLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -994,13 +774,13 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			identityLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Map<TransportId, TransportProperties> getLocalProperties()
 			throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1013,13 +793,13 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public TransportProperties getLocalProperties(TransportId t)
 			throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1033,12 +813,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public byte[] getMessageBody(MessageId m) throws DbException {
-		messageLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1052,38 +832,33 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			messageLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<MessageHeader> getMessageHeaders(GroupId g)
 			throws DbException {
-		messageLock.readLock().lock();
+		lock.readLock().lock();
 		try {
-			subscriptionLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsGroup(txn, g))
-						throw new NoSuchSubscriptionException();
-					Collection<MessageHeader> headers =
-							db.getMessageHeaders(txn, g);
-					db.commitTransaction(txn);
-					return headers;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.readLock().unlock();
+				if(!db.containsGroup(txn, g))
+					throw new NoSuchSubscriptionException();
+				Collection<MessageHeader> headers =
+						db.getMessageHeaders(txn, g);
+				db.commitTransaction(txn);
+				return headers;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			messageLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public boolean getReadFlag(MessageId m) throws DbException {
-		messageLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1097,13 +872,13 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			messageLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Map<ContactId, TransportProperties> getRemoteProperties(
 			TransportId t) throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1116,12 +891,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<TemporarySecret> getSecrets() throws DbException {
-		windowLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1133,12 +908,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			windowLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Settings getSettings() throws DbException {
-		settingLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1150,12 +925,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			settingLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<Contact> getSubscribers(GroupId g) throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1167,12 +942,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Map<TransportId, Long> getTransportLatencies() throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1185,12 +960,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Map<GroupId, Integer> getUnreadMessageCounts() throws DbException {
-		messageLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1202,12 +977,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			messageLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<ContactId> getVisibility(GroupId g) throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1221,46 +996,35 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public long incrementConnectionCounter(ContactId c, TransportId t,
 			long period) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						if(!db.containsTransport(txn, t))
-							throw new NoSuchTransportException();
-						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 {
-				transportLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				if(!db.containsTransport(txn, t))
+					throw new NoSuchTransportException();
+				long counter = db.incrementConnectionCounter(txn, c, t, period);
+				db.commitTransaction(txn);
+				return counter;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void mergeConfig(TransportId t, TransportConfig c)
 			throws DbException {
-		transportLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1273,14 +1037,14 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void mergeLocalProperties(TransportId t, TransportProperties p)
 			throws DbException {
 		boolean changed = false;
-		transportLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1296,14 +1060,14 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(changed) callListeners(new LocalTransportsUpdatedEvent());
 	}
 
 	public void mergeSettings(Settings s) throws DbException {
 		boolean changed = false;
-		settingLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1317,113 +1081,90 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			settingLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(changed) callListeners(new SettingsUpdatedEvent());
 	}
 
 	public void receiveAck(ContactId c, Ack a) throws DbException {
 		Collection<MessageId> acked = new ArrayList<MessageId>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					for(MessageId m : a.getMessageIds()) {
-						if(db.containsVisibleMessage(txn, c, m)) {
-							db.raiseSeenFlag(txn, c, m);
-							acked.add(m);
-						}
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				for(MessageId m : a.getMessageIds()) {
+					if(db.containsVisibleMessage(txn, c, m)) {
+						db.raiseSeenFlag(txn, c, m);
+						acked.add(m);
 					}
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new MessagesAckedEvent(c, acked));
 	}
 
 	public void receiveMessage(ContactId c, Message m) throws DbException {
 		boolean duplicate, visible;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						duplicate = db.containsMessage(txn, m.getId());
-						GroupId g = m.getGroup().getId();
-						visible = db.containsVisibleGroup(txn, c, g);
-						if(!duplicate && visible) addMessage(txn, m, c);
-						if(visible) db.raiseAckFlag(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();
+				duplicate = db.containsMessage(txn, m.getId());
+				visible = db.containsVisibleGroup(txn, c, m.getGroup().getId());
+				if(visible) {
+					if(!duplicate) addMessage(txn, m, c);
+					db.raiseAckFlag(txn, c, m.getId());
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
+		// FIXME: MessageAddedEvent should only be broadcast if msg is visible
 		if(visible) callListeners(new MessageToAckEvent(c));
 		if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), c));
 	}
 
 	public void receiveOffer(ContactId c, Offer o) throws DbException {
 		boolean ack = false, request = false;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						int count = db.countOfferedMessages(txn, c);
-						for(MessageId m : o.getMessageIds()) {
-							if(db.containsVisibleMessage(txn, c, m)) {
-								db.raiseSeenFlag(txn, c, m);
-								db.raiseAckFlag(txn, c, m);
-								ack = true;
-							} else if(count < MAX_OFFERED_MESSAGES) {
-								db.addOfferedMessage(txn, c, m);
-								request = true;
-								count++;
-							}
-						}
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				int count = db.countOfferedMessages(txn, c);
+				for(MessageId m : o.getMessageIds()) {
+					if(db.containsVisibleMessage(txn, c, m)) {
+						db.raiseSeenFlag(txn, c, m);
+						db.raiseAckFlag(txn, c, m);
+						ack = true;
+					} else if(count < MAX_OFFERED_MESSAGES) {
+						db.addOfferedMessage(txn, c, m);
+						request = true;
+						count++;
 					}
-				} finally {
-					subscriptionLock.readLock().unlock();
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(ack) callListeners(new MessageToAckEvent(c));
 		if(request) callListeners(new MessageToRequestEvent(c));
@@ -1431,269 +1172,196 @@ DatabaseCleaner.Callback {
 
 	public void receiveRequest(ContactId c, Request r) throws DbException {
 		boolean requested = false;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					for(MessageId m : r.getMessageIds()) {
-						if(db.containsVisibleMessage(txn, c, m)) {
-							db.raiseRequestedFlag(txn, c, m);
-							db.resetExpiryTime(txn, c, m);
-							requested = true;
-						}
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				for(MessageId m : r.getMessageIds()) {
+					if(db.containsVisibleMessage(txn, c, m)) {
+						db.raiseRequestedFlag(txn, c, m);
+						db.resetExpiryTime(txn, c, m);
+						requested = true;
 					}
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(requested) callListeners(new MessageRequestedEvent(c));
 	}
 
 	public void receiveRetentionAck(ContactId c, RetentionAck a)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setRetentionUpdateAcked(txn, c, a.getVersion());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				retentionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setRetentionUpdateAcked(txn, c, a.getVersion());
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void receiveRetentionUpdate(ContactId c, RetentionUpdate u)
 			throws DbException {
 		boolean updated;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					updated = db.setRetentionTime(txn, c, u.getRetentionTime(),
-							u.getVersion());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				retentionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				long retention = u.getRetentionTime(), version = u.getVersion();
+				updated = db.setRetentionTime(txn, c, retention, version);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(updated) callListeners(new RemoteRetentionTimeUpdatedEvent(c));
 	}
 
 	public void receiveSubscriptionAck(ContactId c, SubscriptionAck a)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setSubscriptionUpdateAcked(txn, c, a.getVersion());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setSubscriptionUpdateAcked(txn, c, a.getVersion());
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate u)
 			throws DbException {
 		boolean updated;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						Collection<Group> groups = u.getGroups();
-						long version = u.getVersion();
-						updated = db.setGroups(txn, c, groups, version);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.writeLock().unlock();
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				updated = db.setGroups(txn, c, u.getGroups(), u.getVersion());
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(updated) callListeners(new RemoteSubscriptionsUpdatedEvent(c));
 	}
 
 	public void receiveTransportAck(ContactId c, TransportAck a)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					TransportId t = a.getId();
-					if(!db.containsTransport(txn, t))
-						throw new NoSuchTransportException();
-					db.setTransportUpdateAcked(txn, c, t, a.getVersion());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				if(!db.containsTransport(txn, a.getId()))
+					throw new NoSuchTransportException();
+				db.setTransportUpdateAcked(txn, c, a.getId(), a.getVersion());
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void receiveTransportUpdate(ContactId c, TransportUpdate u)
 			throws DbException {
 		boolean updated;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					TransportId t = u.getId();
-					TransportProperties p = u.getProperties();
-					long version = u.getVersion();
-					updated = db.setRemoteProperties(txn, c, t, p, version);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				TransportId t = u.getId();
+				TransportProperties p = u.getProperties();
+				long version = u.getVersion();
+				updated = db.setRemoteProperties(txn, c, t, p, version);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(updated)
 			callListeners(new RemoteTransportsUpdatedEvent(c, u.getId()));
 	}
 
 	public void removeContact(ContactId c) throws DbException {
-		contactLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				retentionLock.writeLock().lock();
-				try {
-					subscriptionLock.writeLock().lock();
-					try {
-						transportLock.writeLock().lock();
-						try {
-							windowLock.writeLock().lock();
-							try {
-								T txn = db.startTransaction();
-								try {
-									if(!db.containsContact(txn, c))
-										throw new NoSuchContactException();
-									GroupId g = db.getInboxGroupId(txn, c);
-									if(g != null) db.removeGroup(txn, g);
-									db.removeContact(txn, c);
-									db.commitTransaction(txn);
-								} catch(DbException e) {
-									db.abortTransaction(txn);
-									throw e;
-								}
-							} finally {
-								windowLock.writeLock().unlock();
-							}
-						} finally {
-							transportLock.writeLock().unlock();
-						}
-					} finally {
-						subscriptionLock.writeLock().unlock();
-					}
-				} finally {
-					retentionLock.writeLock().unlock();
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				GroupId g = db.getInboxGroupId(txn, c);
+				if(g != null) db.removeGroup(txn, g);
+				db.removeContact(txn, c);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new ContactRemovedEvent(c));
 	}
 
 	public void removeGroup(Group g) throws DbException {
 		Collection<ContactId> affected;
-		messageLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					GroupId id = g.getId();
-					if(!db.containsGroup(txn, id))
-						throw new NoSuchSubscriptionException();
-					affected = db.getVisibility(txn, id);
-					db.removeGroup(txn, id);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				GroupId id = g.getId();
+				if(!db.containsGroup(txn, id))
+					throw new NoSuchSubscriptionException();
+				affected = db.getVisibility(txn, id);
+				db.removeGroup(txn, id);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			messageLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new SubscriptionRemovedEvent(g));
 		callListeners(new LocalSubscriptionsUpdatedEvent(affected));
@@ -1701,141 +1369,90 @@ DatabaseCleaner.Callback {
 
 	public void removeLocalAuthor(AuthorId a) throws DbException {
 		Collection<ContactId> affected;
-		contactLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			identityLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				messageLock.writeLock().lock();
-				try {
-					retentionLock.writeLock().lock();
-					try {
-						subscriptionLock.writeLock().lock();
-						try {
-							transportLock.writeLock().lock();
-							try {
-								windowLock.writeLock().lock();
-								try {
-									T txn = db.startTransaction();
-									try {
-										if(!db.containsLocalAuthor(txn, a))
-											throw new NoSuchLocalAuthorException();
-										affected = db.getContacts(txn, a);
-										for(ContactId c : affected) {
-											GroupId g = db.getInboxGroupId(txn, c);
-											if(g != null) db.removeGroup(txn, g);
-										}
-										db.removeLocalAuthor(txn, a);
-										db.commitTransaction(txn);
-									} catch(DbException e) {
-										db.abortTransaction(txn);
-										throw e;
-									}
-								} finally {
-									windowLock.writeLock().unlock();
-								}
-							} finally {
-								transportLock.writeLock().unlock();
-							}
-						} finally {
-							subscriptionLock.writeLock().unlock();
-						}
-					} finally {
-						retentionLock.writeLock().unlock();
-					}
-				} finally {
-					messageLock.writeLock().unlock();
+				if(!db.containsLocalAuthor(txn, a))
+					throw new NoSuchLocalAuthorException();
+				affected = db.getContacts(txn, a);
+				for(ContactId c : affected) {
+					GroupId g = db.getInboxGroupId(txn, c);
+					if(g != null) db.removeGroup(txn, g);
 				}
-			} finally {
-				identityLock.writeLock().unlock();
+				db.removeLocalAuthor(txn, a);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		for(ContactId c : affected) callListeners(new ContactRemovedEvent(c));
 		callListeners(new LocalAuthorRemovedEvent(a));
 	}
 
 	public void removeTransport(TransportId t) throws DbException {
-		transportLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			windowLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsTransport(txn, t))
-						throw new NoSuchTransportException();
-					db.removeTransport(txn, t);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				windowLock.writeLock().unlock();
+				if(!db.containsTransport(txn, t))
+					throw new NoSuchTransportException();
+				db.removeTransport(txn, t);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			transportLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new TransportRemovedEvent(t));
 	}
 
 	public void setConnectionWindow(ContactId c, TransportId t, long period,
 			long centre, byte[] bitmap) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						if(!db.containsTransport(txn, t))
-							throw new NoSuchTransportException();
-						db.setConnectionWindow(txn, c, t, period, centre,
-								bitmap);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					windowLock.writeLock().unlock();
-				}
-			} finally {
-				transportLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				if(!db.containsTransport(txn, t))
+					throw new NoSuchTransportException();
+				db.setConnectionWindow(txn, c, t, period, centre, bitmap);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void setInboxGroup(ContactId c, Group g) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setInboxGroup(txn, c, g);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setInboxGroup(txn, c, g);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void setReadFlag(MessageId m, boolean read) throws DbException {
-		messageLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1848,73 +1465,63 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			messageLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void setRemoteProperties(ContactId c,
 			Map<TransportId, TransportProperties> p) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setRemoteProperties(txn, c, p);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setRemoteProperties(txn, c, p);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException {
 		Collection<ContactId> affected = new ArrayList<ContactId>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsGroup(txn, g))
-						throw new NoSuchSubscriptionException();
-					// Use HashSets for O(1) lookups, O(n) overall running time
-					HashSet<ContactId> now = new HashSet<ContactId>(visible);
-					Collection<ContactId> before = db.getVisibility(txn, g);
-					before = new HashSet<ContactId>(before);
-					// Set the group's visibility for each current contact
-					for(ContactId c : db.getContactIds(txn)) {
-						boolean wasBefore = before.contains(c);
-						boolean isNow = now.contains(c);
-						if(!wasBefore && isNow) {
-							db.addVisibility(txn, c, g);
-							affected.add(c);
-						} else if(wasBefore && !isNow) {
-							db.removeVisibility(txn, c, g);
-							affected.add(c);
-						}
+				if(!db.containsGroup(txn, g))
+					throw new NoSuchSubscriptionException();
+				// Use HashSets for O(1) lookups, O(n) overall running time
+				HashSet<ContactId> now = new HashSet<ContactId>(visible);
+				Collection<ContactId> before = db.getVisibility(txn, g);
+				before = new HashSet<ContactId>(before);
+				// Set the group's visibility for each current contact
+				for(ContactId c : db.getContactIds(txn)) {
+					boolean wasBefore = before.contains(c);
+					boolean isNow = now.contains(c);
+					if(!wasBefore && isNow) {
+						db.addVisibility(txn, c, g);
+						affected.add(c);
+					} else if(wasBefore && !isNow) {
+						db.removeVisibility(txn, c, g);
+						affected.add(c);
 					}
-					// Make the group invisible to future contacts
-					db.setVisibleToAll(txn, g, false);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
 				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				// Make the group invisible to future contacts
+				db.setVisibleToAll(txn, g, false);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(!affected.isEmpty())
 			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
@@ -1922,37 +1529,32 @@ DatabaseCleaner.Callback {
 
 	public void setVisibleToAll(GroupId g, boolean all) throws DbException {
 		Collection<ContactId> affected = new ArrayList<ContactId>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsGroup(txn, g))
-						throw new NoSuchSubscriptionException();
-					// Make the group visible or invisible to future contacts
-					db.setVisibleToAll(txn, g, all);
-					if(all) {
-						// Make the group visible to all current contacts
-						Collection<ContactId> before = db.getVisibility(txn, g);
-						before = new HashSet<ContactId>(before);
-						for(ContactId c : db.getContactIds(txn)) {
-							if(!before.contains(c)) {
-								db.addVisibility(txn, c, g);
-								affected.add(c);
-							}
+				if(!db.containsGroup(txn, g))
+					throw new NoSuchSubscriptionException();
+				// Make the group visible or invisible to future contacts
+				db.setVisibleToAll(txn, g, all);
+				if(all) {
+					// Make the group visible to all current contacts
+					Collection<ContactId> before = db.getVisibility(txn, g);
+					before = new HashSet<ContactId>(before);
+					for(ContactId c : db.getContactIds(txn)) {
+						if(!before.contains(c)) {
+							db.addVisibility(txn, c, g);
+							affected.add(c);
 						}
 					}
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
 				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(!affected.isEmpty())
 			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
@@ -1979,29 +1581,24 @@ DatabaseCleaner.Callback {
 	 */
 	private boolean expireMessages(int size) throws DbException {
 		Collection<MessageId> expired;
-		messageLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					expired = db.getOldMessages(txn, size);
-					if(!expired.isEmpty()) {
-						for(MessageId m : expired) db.removeMessage(txn, m);
-						db.incrementRetentionVersions(txn);
-						if(LOG.isLoggable(INFO))
-							LOG.info("Expired " + expired.size() + " messages");
-					}
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+				expired = db.getOldMessages(txn, size);
+				if(!expired.isEmpty()) {
+					for(MessageId m : expired) db.removeMessage(txn, m);
+					db.incrementRetentionVersions(txn);
+					if(LOG.isLoggable(INFO))
+						LOG.info("Expired " + expired.size() + " messages");
 				}
-			} finally {
-				retentionLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			messageLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(expired.isEmpty()) return false;
 		callListeners(new MessageExpiredEvent());
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 42214788e855005b03aae50fb4eddb0f58212ca9..b3988605631e036362019bfa05ebe466f17a67c2 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -71,8 +71,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " value VARCHAR NOT NULL,"
 					+ " PRIMARY KEY (key))";
 
-	// Locking: identity
-	// Dependents: contact, message, retention, subscription, transport, window
 	private static final String CREATE_LOCAL_AUTHORS =
 			"CREATE TABLE localAuthors"
 					+ " (authorId HASH NOT NULL,"
@@ -82,8 +80,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " created BIGINT NOT NULL,"
 					+ " PRIMARY KEY (authorId))";
 
-	// Locking: contact
-	// Dependents: message, retention, subscription, transport, window
 	private static final String CREATE_CONTACTS =
 			"CREATE TABLE contacts"
 					+ " (contactId COUNTER,"
@@ -97,8 +93,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES localAuthors (authorId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: subscription
-	// Dependents: message
 	private static final String CREATE_GROUPS =
 			"CREATE TABLE groups"
 					+ " (groupId HASH NOT NULL,"
@@ -107,7 +101,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " visibleToAll BOOLEAN NOT NULL,"
 					+ " PRIMARY KEY (groupId))";
 
-	// Locking: subscription
 	private static final String CREATE_GROUP_VISIBILITIES =
 			"CREATE TABLE groupVisibilities"
 					+ " (contactId INT NOT NULL,"
@@ -121,7 +114,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES groups (groupId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: subscription
 	private static final String CREATE_CONTACT_GROUPS =
 			"CREATE TABLE contactGroups"
 					+ " (contactId INT NOT NULL,"
@@ -133,7 +125,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: subscription
 	private static final String CREATE_GROUP_VERSIONS =
 			"CREATE TABLE groupVersions"
 					+ " (contactId INT NOT NULL,"
@@ -148,7 +139,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: message
 	private static final String CREATE_MESSAGES =
 			"CREATE TABLE messages"
 					+ " (messageId HASH NOT NULL,"
@@ -182,7 +172,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: message
 	private static final String CREATE_STATUSES =
 			"CREATE TABLE statuses"
 					+ " (messageId HASH NOT NULL,"
@@ -206,7 +195,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String INDEX_STATUSES_BY_CONTACT =
 			"CREATE INDEX statusesByContact ON statuses (contactId)";
 
-	// Locking: retention
 	private static final String CREATE_RETENTION_VERSIONS =
 			"CREATE TABLE retentionVersions"
 					+ " (contactId INT NOT NULL,"
@@ -222,15 +210,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
-	// Dependents: window
 	private static final String CREATE_TRANSPORTS =
 			"CREATE TABLE transports"
 					+ " (transportId VARCHAR NOT NULL,"
 					+ " maxLatency BIGINT NOT NULL,"
 					+ " PRIMARY KEY (transportId))";
 
-	// Locking: transport
 	private static final String CREATE_TRANSPORT_CONFIGS =
 			"CREATE TABLE transportConfigs"
 					+ " (transportId VARCHAR NOT NULL,"
@@ -241,7 +226,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
 	private static final String CREATE_TRANSPORT_PROPS =
 			"CREATE TABLE transportProperties"
 					+ " (transportId VARCHAR NOT NULL,"
@@ -252,7 +236,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
 	private static final String CREATE_TRANSPORT_VERSIONS =
 			"CREATE TABLE transportVersions"
 					+ " (contactId INT NOT NULL,"
@@ -269,7 +252,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
 	private static final String CREATE_CONTACT_TRANSPORT_PROPS =
 			"CREATE TABLE contactTransportProperties"
 					+ " (contactId INT NOT NULL,"
@@ -281,7 +263,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
 	private static final String CREATE_CONTACT_TRANSPORT_VERSIONS =
 			"CREATE TABLE contactTransportVersions"
 					+ " (contactId INT NOT NULL,"
@@ -293,7 +274,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: window
 	private static final String CREATE_ENDPOINTS =
 			"CREATE TABLE endpoints"
 					+ " (contactId INT NOT NULL,"
@@ -308,7 +288,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: window
 	private static final String CREATE_SECRETS =
 			"CREATE TABLE secrets"
 					+ " (contactId INT NOT NULL,"