diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index f735dcc7654f1e4b3f80d52024a1b3498e4be27c..9faecae8e9e114a23064196f795be65f23493d98 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -90,7 +90,7 @@ interface Database<T> {
 	/**
 	 * Adds an endpoint to the database.
 	 * <p>
-	 * Locking: contact read, transport read, window write.
+	 * Locking: window write.
 	 */
 	void addEndpoint(T txn, Endpoint ep) throws DbException;
 
@@ -105,7 +105,7 @@ interface Database<T> {
 	/**
 	 * Records a received message as needing to be acknowledged.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	void addMessageToAck(T txn, ContactId c, MessageId m) throws DbException;
 
@@ -113,7 +113,7 @@ interface Database<T> {
 	 * Stores the given message, or returns false if the message is already in
 	 * the database.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	boolean addPrivateMessage(T txn, Message m, ContactId c) throws DbException;
 
@@ -121,7 +121,7 @@ interface Database<T> {
 	 * Stores the given temporary secrets and deletes any secrets that have
 	 * been made obsolete.
 	 * <p>
-	 * Locking: contact read, transport read, window write.
+	 * Locking: window write.
 	 */
 	void addSecrets(T txn, Collection<TemporarySecret> secrets)
 			throws DbException;
@@ -130,7 +130,7 @@ interface Database<T> {
 	 * Initialises the status (seen or unseen) of the given message with
 	 * respect to the given contact.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	void addStatus(T txn, ContactId c, MessageId m, boolean seen)
 			throws DbException;
@@ -147,14 +147,14 @@ interface Database<T> {
 	 * Adds a new transport to the database and returns true if the transport
 	 * was not previously in the database.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: transport write, window write.
 	 */
 	boolean addTransport(T txn, TransportId t) throws DbException;
 
 	/**
 	 * Makes the given group visible to the given contact.
 	 * <p>
-	 * Locking: contact write, subscription write.
+	 * Locking: subscription write.
 	 */
 	void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
 
@@ -182,7 +182,7 @@ interface Database<T> {
 	/**
 	 * Returns true if the database contains the given transport.
 	 * <p>
-	 * Locking: contact read, transport read.
+	 * Locking: transport read.
 	 */
 	boolean containsTransport(T txn, TransportId t) throws DbException;
 
@@ -190,7 +190,7 @@ interface Database<T> {
 	 * Returns true if the user subscribes to the given group and the
 	 * subscription is visible to the given contact.
 	 * <p>
-	 * Locking: contact read, subscription read.
+	 * Locking: subscription read.
 	 */
 	boolean containsVisibleSubscription(T txn, ContactId c, GroupId g)
 			throws DbException;
@@ -219,7 +219,7 @@ interface Database<T> {
 	/**
 	 * Returns all endpoints.
 	 * <p>
-	 * Locking: contact read, transport read, window read.
+	 * Locking: window read.
 	 */
 	Collection<Endpoint> getEndpoints(T txn) throws DbException;
 
@@ -227,8 +227,6 @@ interface Database<T> {
 	 * Returns the amount of free storage space available to the database, in
 	 * bytes. This is based on the minimum of the space available on the device
 	 * where the database is stored and the database's configured size.
-	 * <p>
-	 * Locking: message read.
 	 */
 	long getFreeSpace() throws DbException;
 
@@ -245,7 +243,7 @@ interface Database<T> {
 	 * Returns the time at which a connection to the given contact was last
 	 * made.
 	 * <p>
-	 * Locking: contact read, window read.
+	 * Locking: window read.
 	 */
 	long getLastConnected(T txn, ContactId c) throws DbException;
 
@@ -292,7 +290,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages received from the given contact that
 	 * need to be acknowledged, up to the given number of messages.
 	 * <p>
-	 * Locking: contact read, message read.
+	 * Locking: message read.
 	 */
 	Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
 			throws DbException;
@@ -301,7 +299,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be sent to the
 	 * given contact, up to the given number of messages.
 	 * <p>
-	 * Locking: contact read, message read, subscription read.
+	 * Locking: message read, subscription read.
 	 */
 	Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
 			int maxMessages) throws DbException;
@@ -327,7 +325,7 @@ interface Database<T> {
 	 * Returns null if the message is not present in the database or is not
 	 * sendable to the given contact.
 	 * <p>
-	 * Locking: contact read, message read, subscription read.
+	 * Locking: message read, subscription read.
 	 */
 	byte[] getRawMessageIfSendable(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -357,7 +355,7 @@ interface Database<T> {
 	/**
 	 * Returns all remote properties for the given transport.
 	 * <p>
-	 * Locking: contact read, transport read.
+	 * Locking: transport read.
 	 */
 	Map<ContactId, TransportProperties> getRemoteProperties(T txn,
 			TransportId t) throws DbException;
@@ -365,7 +363,7 @@ interface Database<T> {
 	/**
 	 * Returns a retention ack for the given contact, or null if no ack is due.
 	 * <p>
-	 * Locking: contact read, retention write.
+	 * Locking: retention write.
 	 */
 	RetentionAck getRetentionAck(T txn, ContactId c) throws DbException;
 
@@ -373,7 +371,7 @@ interface Database<T> {
 	 * Returns a retention update for the given contact and updates its expiry
 	 * time using the given latency. Returns null if no update is due.
 	 * <p>
-	 * Locking: contact read, retention write.
+	 * Locking: message read, retention write.
 	 */
 	RetentionUpdate getRetentionUpdate(T txn, ContactId c, long maxLatency)
 			throws DbException;
@@ -381,7 +379,7 @@ interface Database<T> {
 	/**
 	 * Returns all temporary secrets.
 	 * <p>
-	 * Locking: contact read, transport read, window read.
+	 * Locking: window read.
 	 */
 	Collection<TemporarySecret> getSecrets(T txn) throws DbException;
 
@@ -397,7 +395,7 @@ interface Database<T> {
 	 * given contact, with a total length less than or equal to the given
 	 * length.
 	 * <p>
-	 * Locking: contact read, message read, subscription read.
+	 * Locking: message read, subscription read.
 	 */
 	Collection<MessageId> getSendableMessages(T txn, ContactId c, int maxLength)
 			throws DbException;
@@ -419,7 +417,7 @@ interface Database<T> {
 	/**
 	 * Returns the groups to which the given contact subscribes.
 	 * <p>
-	 * Locking: contact read, subscription read.
+	 * Locking: subscription read.
 	 */
 	Collection<Group> getSubscriptions(T txn, ContactId c) throws DbException;
 
@@ -427,7 +425,7 @@ interface Database<T> {
 	 * Returns a subscription ack for the given contact, or null if no ack is
 	 * due.
 	 * <p>
-	 * Locking: contact read, subscription write.
+	 * Locking: subscription write.
 	 */
 	SubscriptionAck getSubscriptionAck(T txn, ContactId c) throws DbException;
 
@@ -435,7 +433,7 @@ interface Database<T> {
 	 * Returns a subscription update for the given contact and updates its
 	 * expiry time using the given latency. Returns null if no update is due.
 	 * <p>
-	 * Locking: contact read, subscription write.
+	 * Locking: subscription write.
 	 */
 	SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c,
 			long maxLatency) throws DbException;
@@ -444,7 +442,7 @@ interface Database<T> {
 	 * Returns the transmission count of the given message with respect to the
 	 * given contact.
 	 * <p>
-	 * Locking: contact read, message read.
+	 * Locking: message read.
 	 */
 	int getTransmissionCount(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -453,7 +451,7 @@ interface Database<T> {
 	 * Returns a collection of transport acks for the given contact, or null if
 	 * no acks are due.
 	 * <p>
-	 * Locking: contact read, transport write.
+	 * Locking: transport write.
 	 */
 	Collection<TransportAck> getTransportAcks(T txn, ContactId c)
 			throws DbException;
@@ -463,31 +461,29 @@ interface Database<T> {
 	 * updates their expiry times using the given latency. Returns null if no
 	 * updates are due.
 	 * <p>
-	 * Locking: contact read, transport write.
+	 * Locking: transport write.
 	 */
 	Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c,
 			long maxLatency) throws DbException;
 
-	/**
-	 * Returns the version number of the 
 	/**
 	 * Returns the number of unread messages in each subscribed group.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: message read.
 	 */
 	Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException;
 
 	/**
 	 * Returns the contacts to which the given group is visible.
 	 * <p>
-	 * Locking: contact read, subscription read.
+	 * Locking: subscription read.
 	 */
 	Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns the subscriptions that are visible to the given contact.
 	 * <p>
-	 * Locking: contact read, subscription read.
+	 * Locking: subscription read.
 	 */
 	Collection<GroupId> getVisibleSubscriptions(T txn, ContactId c)
 			throws DbException;
@@ -495,7 +491,7 @@ interface Database<T> {
 	/**
 	 * Returns true if any messages are sendable to the given contact.
 	 * <p>
-	 * Locking: contact read, message read.
+	 * Locking: message read, subscription read.
 	 */
 	boolean hasSendableMessages(T txn, ContactId c) throws DbException;
 
@@ -503,7 +499,7 @@ interface Database<T> {
 	 * Increments the outgoing connection counter for the given contact
 	 * transport in the given rotation period and returns the old value;
 	 * <p>
-	 * Locking: contact read, transport read, window write.
+	 * Locking: window write.
 	 */
 	long incrementConnectionCounter(T txn, ContactId c, TransportId t,
 			long period) throws DbException;
@@ -512,7 +508,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: contact read, retention write.
+	 * Locking: retention write.
 	 */
 	void incrementRetentionVersions(T txn) throws DbException;
 
@@ -545,7 +541,7 @@ interface Database<T> {
 	/**
 	 * Removes a message (and all associated state) from the database.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	void removeMessage(T txn, MessageId m) throws DbException;
 
@@ -553,7 +549,7 @@ interface Database<T> {
 	 * Marks the given messages received from the given contact as having been
 	 * acknowledged.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	void removeMessagesToAck(T txn, ContactId c, Collection<MessageId> acked)
 			throws DbException;
@@ -562,7 +558,7 @@ interface Database<T> {
 	 * Marks any of the given messages that are considered outstanding with
 	 * respect to the given contact as seen by the contact.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	void removeOutstandingMessages(T txn, ContactId c,
 			Collection<MessageId> acked) throws DbException;
@@ -571,21 +567,21 @@ interface Database<T> {
 	 * Unsubscribes from the given group. Any messages belonging to the group
 	 * are deleted from the database.
 	 * <p>
-	 * Locking: contact write, message write, subscription write.
+	 * Locking: message write, subscription write.
 	 */
 	void removeSubscription(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Removes a transport (and all associated state) from the database.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: transport write, window write.
 	 */
 	void removeTransport(T txn, TransportId t) throws DbException;
 
 	/**
 	 * Makes the given group invisible to the given contact.
 	 * <p>
-	 * Locking: contact write, subscription write.
+	 * Locking: subscription write.
 	 */
 	void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
 
@@ -593,7 +589,7 @@ interface Database<T> {
 	 * Sets the connection reordering window for the given endpoint in the
 	 * given rotation period.
 	 * <p>
-	 * Locking: contact read, transport read, window write.
+	 * Locking: window write.
 	 */
 	void setConnectionWindow(T txn, ContactId c, TransportId t, long period,
 			long centre, byte[] bitmap) throws DbException;
@@ -601,7 +597,7 @@ interface Database<T> {
 	/**
 	 * Sets the time at which a connection to the given contact was last made.
 	 * <p>
-	 * Locking: contact read, window write.
+	 * Locking: window write.
 	 */
 	void setLastConnected(T txn, ContactId c, long now) throws DbException;
 
@@ -626,7 +622,7 @@ interface Database<T> {
 	 * with an equal or higher version number has already been received from
 	 * the contact.
 	 * <p>
-	 * Locking: contact read, transport write.
+	 * Locking: transport write.
 	 */
 	void setRemoteProperties(T txn, ContactId c, TransportId t,
 			TransportProperties p, long version) throws DbException;
@@ -636,7 +632,7 @@ interface Database<T> {
 	 * update with an equal or higher version number has already been received
 	 * from the contact.
 	 * <p>
-	 * Locking: contact read, retention write.
+	 * Locking: retention write.
 	 */
 	void setRetentionTime(T txn, ContactId c, long retention, long version)
 			throws DbException;
@@ -662,7 +658,7 @@ interface Database<T> {
 	 * that is visible to the given contact, marks the message as seen by the
 	 * contact and returns true; otherwise returns false.
 	 * <p>
-	 * Locking: contact read, message write, subscription read.
+	 * Locking: message write, subscription read.
 	 */
 	boolean setStatusSeenIfVisible(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -672,7 +668,7 @@ interface Database<T> {
 	 * update with an equal or higher version number has already been received
 	 * from the contact.
 	 * <p>
-	 * Locking: contact read, subscription write.
+	 * Locking: subscription write.
 	 */
 	void setSubscriptions(T txn, ContactId c, Collection<Group> subs,
 			long version) throws DbException;
@@ -681,7 +677,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: contact read, retention write.
+	 * Locking: retention write.
 	 */
 	void setRetentionUpdateAcked(T txn, ContactId c, long version)
 			throws DbException;
@@ -690,7 +686,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: contact read, subscription write.
+	 * Locking: subscription write.
 	 */
 	void setSubscriptionUpdateAcked(T txn, ContactId c, long version)
 			throws DbException;
@@ -699,7 +695,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: contact read, transport write.
+	 * Locking: transport write.
 	 */
 	void setTransportUpdateAcked(T txn, ContactId c, TransportId t,
 			long version) throws DbException;
@@ -709,7 +705,7 @@ interface Database<T> {
 	 * contact, using the given transmission counts and the latency of the
 	 * transport over which they were sent.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	void updateExpiryTimes(T txn, ContactId c, Map<MessageId, Integer> sent,
 			long maxLatency) throws DbException;
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 7cb268aaef2d1fa4e64a978fa64e0757b670dd51..5456d379717b7461f7325c23206c788ed5f1c24d 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -254,21 +254,27 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.writeLock().lock();
 			try {
-				subscriptionLock.readLock().lock();
+				ratingLock.readLock().lock();
 				try {
-					T txn = db.startTransaction();
+					subscriptionLock.readLock().lock();
 					try {
-						// Don't store the message if the user has
-						// unsubscribed from the group
-						if(db.containsSubscription(txn, m.getGroup().getId()))
-							added = storeGroupMessage(txn, m, null);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
+						T txn = db.startTransaction();
+						try {
+							// Don't store the message if the user has
+							// unsubscribed from the group
+							GroupId g = m.getGroup().getId();
+							if(db.containsSubscription(txn, g))
+								added = storeGroupMessage(txn, m, null);
+							db.commitTransaction(txn);
+						} catch(DbException e) {
+							db.abortTransaction(txn);
+							throw e;
+						}
+					} finally {
+						subscriptionLock.readLock().unlock();
 					}
 				} finally {
-					subscriptionLock.readLock().unlock();
+					ratingLock.readLock().unlock();
 				}
 			} finally {
 				messageLock.writeLock().unlock();
@@ -285,7 +291,7 @@ DatabaseCleaner.Callback {
 	 * sendability of its ancestors if necessary, marks the message as seen by
 	 * the sender and unseen by all other contacts, and returns true.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: contact read, message write, rating read.
 	 * @param sender may be null for a locally generated message.
 	 */
 	private boolean storeGroupMessage(T txn, Message m, ContactId sender)
@@ -315,7 +321,7 @@ DatabaseCleaner.Callback {
 	/**
 	 * Calculates and returns the sendability score of a message.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: message read, rating read.
 	 */
 	private int calculateSendability(T txn, Message m) throws DbException {
 		int sendability = 0;
@@ -431,13 +437,18 @@ DatabaseCleaner.Callback {
 		boolean added;
 		transportLock.writeLock().lock();
 		try {
-			T txn = db.startTransaction();
+			windowLock.writeLock().lock();
 			try {
-				added = db.addTransport(txn, t);
-				db.commitTransaction(txn);
-			} catch(DbException e) {
-				db.abortTransaction(txn);
-				throw e;
+				T txn = db.startTransaction();
+				try {
+					added = db.addTransport(txn, t);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				windowLock.writeLock().unlock();
 			}
 		} finally {
 			transportLock.writeLock().unlock();
@@ -452,7 +463,7 @@ DatabaseCleaner.Callback {
 	 * the given contact, depending on whether the message is outgoing or
 	 * incoming, respectively.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	private boolean storePrivateMessage(T txn, Message m, ContactId c,
 			boolean incoming) throws DbException {
@@ -515,7 +526,7 @@ DatabaseCleaner.Callback {
 		// Get some sendable messages from the database
 		contactLock.readLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			messageLock.readLock().lock();
 			try {
 				subscriptionLock.readLock().lock();
 				try {
@@ -537,7 +548,7 @@ DatabaseCleaner.Callback {
 					subscriptionLock.readLock().unlock();
 				}
 			} finally {
-				messageLock.writeLock().unlock();
+				messageLock.readLock().unlock();
 			}
 			if(messages.isEmpty()) return null;
 			// Record the messages as sent
@@ -627,15 +638,20 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.readLock().lock();
 			try {
-				T txn = db.startTransaction();
+				subscriptionLock.readLock().lock();
 				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					offered = db.getMessagesToOffer(txn, c, maxMessages);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+					T txn = db.startTransaction();
+					try {
+						if(!db.containsContact(txn, c))
+							throw new NoSuchContactException();
+						offered = db.getMessagesToOffer(txn, c, maxMessages);
+						db.commitTransaction(txn);
+					} catch(DbException e) {
+						db.abortTransaction(txn);
+						throw e;
+					}
+				} finally {
+					subscriptionLock.readLock().unlock();
 				}
 			} finally {
 				messageLock.readLock().unlock();
@@ -674,22 +690,27 @@ DatabaseCleaner.Callback {
 			throws DbException {
 		contactLock.readLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			messageLock.readLock().lock();
 			try {
-				T txn = db.startTransaction();
+				retentionLock.writeLock().lock();
 				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;
+					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 {
-				retentionLock.writeLock().unlock();
+				messageLock.readLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
@@ -882,17 +903,22 @@ DatabaseCleaner.Callback {
 			throws DbException {
 		messageLock.readLock().lock();
 		try {
-			T txn = db.startTransaction();
+			subscriptionLock.readLock().lock();
 			try {
-				if(!db.containsSubscription(txn, g))
-					throw new NoSuchSubscriptionException();
-				Collection<GroupMessageHeader> headers =
-						db.getMessageHeaders(txn, g);
-				db.commitTransaction(txn);
-				return headers;
-			} catch(DbException e) {
-				db.abortTransaction(txn);
-				throw e;
+				T txn = db.startTransaction();
+				try {
+					if(!db.containsSubscription(txn, g))
+						throw new NoSuchSubscriptionException();
+					Collection<GroupMessageHeader> headers =
+							db.getMessageHeaders(txn, g);
+					db.commitTransaction(txn);
+					return headers;
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				subscriptionLock.readLock().unlock();
 			}
 		} finally {
 			messageLock.readLock().unlock();
@@ -956,53 +982,37 @@ DatabaseCleaner.Callback {
 
 	public Map<ContactId, TransportProperties> getRemoteProperties(
 			TransportId t) throws DbException {
-		contactLock.readLock().lock();
+		transportLock.readLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					Map<ContactId, TransportProperties> properties =
-							db.getRemoteProperties(txn, t);
-					db.commitTransaction(txn);
-					return properties;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.readLock().unlock();
+				Map<ContactId, TransportProperties> properties =
+						db.getRemoteProperties(txn, t);
+				db.commitTransaction(txn);
+				return properties;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			transportLock.readLock().unlock();
 		}
 	}
 
 	public Collection<TemporarySecret> getSecrets() throws DbException {
-		contactLock.readLock().lock();
+		windowLock.readLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						Collection<TemporarySecret> secrets =
-								db.getSecrets(txn);
-						db.commitTransaction(txn);
-						return secrets;
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					windowLock.readLock().unlock();
-				}
-			} finally {
-				transportLock.readLock().unlock();
+				Collection<TemporarySecret> secrets = db.getSecrets(txn);
+				db.commitTransaction(txn);
+				return secrets;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			windowLock.readLock().unlock();
 		}
 	}
 
@@ -1045,20 +1055,14 @@ DatabaseCleaner.Callback {
 	public Map<GroupId, Integer> getUnreadMessageCounts() throws DbException {
 		messageLock.readLock().lock();
 		try {
-			subscriptionLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					Map<GroupId, Integer> counts =
-							db.getUnreadMessageCounts(txn);
-					db.commitTransaction(txn);
-					return counts;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.readLock().unlock();
+				Map<GroupId, Integer> counts = db.getUnreadMessageCounts(txn);
+				db.commitTransaction(txn);
+				return counts;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
 			messageLock.readLock().unlock();
@@ -1066,26 +1070,21 @@ DatabaseCleaner.Callback {
 	}
 
 	public Collection<ContactId> getVisibility(GroupId g) throws DbException {
-		contactLock.readLock().lock();
+		subscriptionLock.readLock().lock();
 		try {
-			subscriptionLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsSubscription(txn, g))
-						throw new NoSuchSubscriptionException();
-					Collection<ContactId> visible = db.getVisibility(txn, g);
-					db.commitTransaction(txn);
-					return visible;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.readLock().unlock();
+				if(!db.containsSubscription(txn, g))
+					throw new NoSuchSubscriptionException();
+				Collection<ContactId> visible = db.getVisibility(txn, g);
+				db.commitTransaction(txn);
+				return visible;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			subscriptionLock.readLock().unlock();
 		}
 	}
 
@@ -1250,21 +1249,26 @@ DatabaseCleaner.Callback {
 		try {
 			messageLock.writeLock().lock();
 			try {
-				subscriptionLock.readLock().lock();
+				ratingLock.readLock().lock();
 				try {
-					T txn = db.startTransaction();
+					subscriptionLock.readLock().lock();
 					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						added = storeMessage(txn, c, m);
-						db.addMessageToAck(txn, c, m.getId());
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
+						T txn = db.startTransaction();
+						try {
+							if(!db.containsContact(txn, c))
+								throw new NoSuchContactException();
+							added = storeMessage(txn, c, m);
+							db.addMessageToAck(txn, c, m.getId());
+							db.commitTransaction(txn);
+						} catch(DbException e) {
+							db.abortTransaction(txn);
+							throw e;
+						}
+					} finally {
+						subscriptionLock.readLock().unlock();
 					}
 				} finally {
-					subscriptionLock.readLock().unlock();
+					ratingLock.readLock().unlock();
 				}
 			} finally {
 				messageLock.writeLock().unlock();
@@ -1280,7 +1284,7 @@ DatabaseCleaner.Callback {
 	 * Attempts to store a message received from the given contact, and returns
 	 * true if it was stored.
 	 * <p>
-	 * Locking: contact read, message write, subscription read.
+	 * Locking: contact read, message write, rating read, subscription read.
 	 */
 	private boolean storeMessage(T txn, ContactId c, Message m)
 			throws DbException {
@@ -1530,15 +1534,20 @@ DatabaseCleaner.Callback {
 	public void removeTransport(TransportId t) throws DbException {
 		transportLock.writeLock().lock();
 		try {
-			T txn = db.startTransaction();
+			windowLock.writeLock().lock();
 			try {
-				if(!db.containsTransport(txn, t))
-					throw new NoSuchTransportException();
-				db.removeTransport(txn, t);
-				db.commitTransaction(txn);
-			} catch(DbException e) {
-				db.abortTransaction(txn);
-				throw e;
+				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();
 			}
 		} finally {
 			transportLock.writeLock().unlock();
@@ -1705,7 +1714,7 @@ DatabaseCleaner.Callback {
 	public void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException {
 		Collection<ContactId> affected = new ArrayList<ContactId>();
-		contactLock.writeLock().lock();
+		contactLock.readLock().lock();
 		try {
 			subscriptionLock.writeLock().lock();
 			try {
@@ -1739,7 +1748,7 @@ DatabaseCleaner.Callback {
 				subscriptionLock.writeLock().unlock();
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			contactLock.readLock().unlock();
 		}
 		if(!affected.isEmpty())
 			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
@@ -1767,31 +1776,26 @@ DatabaseCleaner.Callback {
 
 	public void unsubscribe(GroupId g) throws DbException {
 		Collection<ContactId> affected;
-		contactLock.writeLock().lock();
+		messageLock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			subscriptionLock.writeLock().lock();
 			try {
-				subscriptionLock.writeLock().lock();
+				T txn = db.startTransaction();
 				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsSubscription(txn, g))
-							throw new NoSuchSubscriptionException();
-						affected = db.getVisibility(txn, g);
-						db.removeSubscription(txn, g);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.writeLock().unlock();
+					if(!db.containsSubscription(txn, g))
+						throw new NoSuchSubscriptionException();
+					affected = db.getVisibility(txn, g);
+					db.removeSubscription(txn, g);
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
 				}
 			} finally {
-				messageLock.writeLock().unlock();
+				subscriptionLock.writeLock().unlock();
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			messageLock.writeLock().unlock();
 		}
 		callListeners(new LocalSubscriptionsUpdatedEvent(affected));
 	}
@@ -1817,34 +1821,28 @@ DatabaseCleaner.Callback {
 	 */
 	private boolean expireMessages(int size) throws DbException {
 		boolean removed = false;
-		contactLock.readLock().lock();
+		messageLock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			retentionLock.writeLock().lock();
 			try {
-				retentionLock.writeLock().lock();
+				T txn = db.startTransaction();
 				try {
-					T txn = db.startTransaction();
-					try {
-						Collection<MessageId> old =
-								db.getOldMessages(txn, size);
-						if(!old.isEmpty()) {
-							for(MessageId m : old) removeMessage(txn, m);
-							db.incrementRetentionVersions(txn);
-							removed = true;
-						}
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
+					Collection<MessageId> old = db.getOldMessages(txn, size);
+					if(!old.isEmpty()) {
+						for(MessageId m : old) removeMessage(txn, m);
+						db.incrementRetentionVersions(txn);
+						removed = true;
 					}
-				} finally {
-					retentionLock.writeLock().unlock();
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
 				}
 			} finally {
-				messageLock.writeLock().unlock();
+				retentionLock.writeLock().unlock();
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			messageLock.writeLock().unlock();
 		}
 		if(removed) callListeners(new MessageExpiredEvent());
 		return removed;
@@ -1853,7 +1851,7 @@ DatabaseCleaner.Callback {
 	/**
 	 * Removes the given message (and all associated state) from the database.
 	 * <p>
-	 * Locking: contact read, message write.
+	 * Locking: message write.
 	 */
 	private void removeMessage(T txn, MessageId m) throws DbException {
 		int sendability = db.getSendability(txn, m);
diff --git a/briar-core/src/net/sf/briar/db/H2Database.java b/briar-core/src/net/sf/briar/db/H2Database.java
index 6a396bddd0575aebb9bd5ba56a9eefd28d07a804..6c28bc77958a84b8fec70adc8e115e52136d5bba 100644
--- a/briar-core/src/net/sf/briar/db/H2Database.java
+++ b/briar-core/src/net/sf/briar/db/H2Database.java
@@ -65,6 +65,14 @@ class H2Database extends JdbcDatabase {
 		}
 	}
 
+	private long getDiskSpace(File f) {
+		long total = 0;
+		if(f.isDirectory()) {
+			for(File child : f.listFiles()) total += getDiskSpace(child);
+			return total;
+		} else return f.length();
+	}
+
 	@Override
 	protected Connection createConnection() throws SQLException {
 		Properties props = new Properties();
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 4be4420fd19ad1c4230d0e45868dd91ce18305c9..2ea61d2da3bb5e94d8456dbc7eb7a614a9a34f63 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -61,6 +61,7 @@ import net.sf.briar.util.FileUtils;
 abstract class JdbcDatabase implements Database<Connection> {
 
 	// Locking: contact
+	// Dependents: message, retention, subscription, transport, window
 	private static final String CREATE_CONTACTS =
 			"CREATE TABLE contacts "
 					+ " (contactId COUNTER,"
@@ -68,6 +69,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " PRIMARY KEY (contactId))";
 
 	// Locking: subscription
+	// Dependents: message
 	private static final String CREATE_GROUPS =
 			"CREATE TABLE groups"
 					+ " (groupId HASH NOT NULL,"
@@ -75,7 +77,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " key BINARY," // Null for unrestricted groups
 					+ " PRIMARY KEY (groupId))";
 
-	// Locking: contact read, subscription
+	// Locking: subscription
 	private static final String CREATE_GROUP_VISIBILITIES =
 			"CREATE TABLE groupVisibilities"
 					+ " (contactId INT NOT NULL,"
@@ -87,7 +89,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES groups (groupId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, subscription
+	// Locking: subscription
 	private static final String CREATE_CONTACT_GROUPS =
 			"CREATE TABLE contactGroups"
 					+ " (contactId INT NOT NULL,"
@@ -99,7 +101,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, subscription
+	// Locking: subscription
 	private static final String CREATE_GROUP_VERSIONS =
 			"CREATE TABLE groupVersions"
 					+ " (contactId INT NOT NULL,"
@@ -153,17 +155,17 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String INDEX_MESSAGES_BY_SENDABILITY =
 			"CREATE INDEX messagesBySendability ON messages (sendability)";
 
-	// Locking: contact read, message
+	// Locking: message
 	private static final String CREATE_MESSAGES_TO_ACK =
 			"CREATE TABLE messagesToAck"
-					+ " (messageId HASH NOT NULL,"
+					+ " (messageId HASH NOT NULL," // Not a foreign key
 					+ " contactId INT NOT NULL,"
 					+ " PRIMARY KEY (messageId, contactId),"
 					+ " FOREIGN KEY (contactId)"
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, message
+	// Locking: message
 	private static final String CREATE_STATUSES =
 			"CREATE TABLE statuses"
 					+ " (messageId HASH NOT NULL,"
@@ -192,7 +194,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " rating SMALLINT NOT NULL,"
 					+ " PRIMARY KEY (authorId))";
 
-	// Locking: contact read, retention
+	// Locking: retention
 	private static final String CREATE_RETENTION_VERSIONS =
 			"CREATE TABLE retentionVersions"
 					+ " (contactId INT NOT NULL,"
@@ -209,6 +211,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " ON DELETE CASCADE)";
 
 	// Locking: transport
+	// Dependents: window
 	private static final String CREATE_TRANSPORTS =
 			"CREATE TABLE transports (transportId HASH NOT NULL)";
 
@@ -234,7 +237,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, transport
+	// Locking: transport
 	private static final String CREATE_TRANSPORT_VERSIONS =
 			"CREATE TABLE transportVersions"
 					+ " (contactId INT NOT NULL,"
@@ -251,7 +254,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, transport
+	// Locking: transport
 	private static final String CREATE_CONTACT_TRANSPORT_PROPS =
 			"CREATE TABLE contactTransportProperties"
 					+ " (contactId INT NOT NULL,"
@@ -263,7 +266,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, transport
+	// Locking: transport
 	private static final String CREATE_CONTACT_TRANSPORT_VERSIONS =
 			"CREATE TABLE contactTransportVersions"
 					+ " (contactId INT NOT NULL,"
@@ -275,7 +278,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, transport read, window
+	// Locking: window
 	private static final String CREATE_ENDPOINTS =
 			"CREATE TABLE endpoints"
 					+ " (contactId INT NOT NULL,"
@@ -292,7 +295,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, transport read, window
+	// Locking: window
 	private static final String CREATE_SECRETS =
 			"CREATE TABLE secrets"
 					+ " (contactId INT NOT NULL,"
@@ -310,7 +313,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: contact read, window
+	// Locking: window
 	private static final String CREATE_CONNECTION_TIMES =
 			"CREATE TABLE connectionTimes"
 					+ " (contactId INT NOT NULL,"
@@ -754,7 +757,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT COUNT (groupId) from GROUPS";
+			String sql = "SELECT COUNT (groupId) FROM groups";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			if(!rs.next()) throw new DbStateException();
@@ -1118,14 +1121,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	protected long getDiskSpace(File f) {
-		long total = 0;
-		if(f.isDirectory()) {
-			for(File child : f.listFiles()) total += getDiskSpace(child);
-			return total;
-		} else return f.length();
-	}
-
 	public MessageId getGroupMessageParent(Connection txn, MessageId m)
 			throws DbException {
 		PreparedStatement ps = null;