diff --git a/briar-android-tests/src/test/java/org/briarproject/sync/SimplexMessagingComponent.java b/briar-android-tests/src/test/java/org/briarproject/sync/SimplexMessagingComponent.java
index 1d0a7d70f348b403f8c5712a265385ae706a3483..ea5ae968972378d332fd4f1b062ac72f8e277cff 100644
--- a/briar-android-tests/src/test/java/org/briarproject/sync/SimplexMessagingComponent.java
+++ b/briar-android-tests/src/test/java/org/briarproject/sync/SimplexMessagingComponent.java
@@ -1,6 +1,7 @@
 package org.briarproject.sync;
 
 import org.briarproject.TestDatabaseModule;
+import org.briarproject.TestPluginsModule;
 import org.briarproject.TestSystemModule;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.db.DatabaseComponent;
@@ -23,6 +24,7 @@ import org.briarproject.event.EventModule;
 import org.briarproject.identity.IdentityModule;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.messaging.MessagingModule;
+import org.briarproject.plugins.PluginsModule;
 import org.briarproject.transport.TransportModule;
 
 import javax.inject.Singleton;
@@ -30,23 +32,37 @@ import javax.inject.Singleton;
 import dagger.Component;
 
 @Singleton
-@Component(modules = {TestDatabaseModule.class, TestSystemModule.class,
-		LifecycleModule.class, ContactModule.class, CryptoModule.class,
-		DatabaseModule.class, EventModule.class, SyncModule.class,
-		DataModule.class, TransportModule.class, IdentityModule.class,
-		MessagingModule.class, ClientsModule.class})
+@Component(modules = {TestDatabaseModule.class, TestPluginsModule.class,
+		TestSystemModule.class, LifecycleModule.class, ContactModule.class,
+		CryptoModule.class, DatabaseModule.class, EventModule.class,
+		SyncModule.class, DataModule.class, TransportModule.class,
+		IdentityModule.class, MessagingModule.class, ClientsModule.class,
+		PluginsModule.class})
 public interface SimplexMessagingComponent {
+
 	void inject(SimplexMessagingIntegrationTest testCase);
-	LifecycleManager getLifeCycleManager();
+
+	LifecycleManager getLifecycleManager();
+
 	DatabaseComponent getDatabaseComponent();
+
 	IdentityManager getIdentityManager();
+
 	ContactManager getContactManager();
+
 	MessagingManager getMessagingManager();
+
 	KeyManager getKeyManager();
+
 	PrivateMessageFactory getPrivateMessageFactory();
+
 	PacketWriterFactory getPacketWriterFactory();
+
 	EventBus getEventBus();
+
 	StreamWriterFactory getStreamWriterFactory();
+
 	StreamReaderFactory getStreamReaderFactory();
+
 	PacketReaderFactory getPacketReaderFactory();
 }
diff --git a/briar-android-tests/src/test/java/org/briarproject/sync/SimplexMessagingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/sync/SimplexMessagingIntegrationTest.java
index 9ad9df0f2832ef25573aacb2b6e3c5158b9a3ed4..01f615511151b54caac2c6770873708ed38f6208 100644
--- a/briar-android-tests/src/test/java/org/briarproject/sync/SimplexMessagingIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/sync/SimplexMessagingIntegrationTest.java
@@ -4,12 +4,10 @@ import org.briarproject.BriarTestCase;
 import org.briarproject.ImmediateExecutor;
 import org.briarproject.TestDatabaseModule;
 import org.briarproject.TestUtils;
-import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.db.Transaction;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
@@ -36,27 +34,26 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
 
-public class SimplexMessagingIntegrationTest extends BriarTestCase {
+import static org.briarproject.TestPluginsModule.MAX_LATENCY;
+import static org.briarproject.TestPluginsModule.TRANSPORT_ID;
+import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
-	private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
+public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	private final File testDir = TestUtils.getTestDirectory();
 	private final File aliceDir = new File(testDir, "alice");
 	private final File bobDir = new File(testDir, "bob");
-	private final TransportId transportId = new TransportId("id");
 	private final SecretKey master = TestUtils.createSecretKey();
 	private final long timestamp = System.currentTimeMillis();
 	private final AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
@@ -80,7 +77,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	private byte[] write() throws Exception {
 		// Instantiate Alice's services
-		LifecycleManager lifecycleManager = alice.getLifeCycleManager();
+		LifecycleManager lifecycleManager = alice.getLifecycleManager();
 		DatabaseComponent db = alice.getDatabaseComponent();
 		IdentityManager identityManager = alice.getIdentityManager();
 		ContactManager contactManager = alice.getContactManager();
@@ -97,14 +94,6 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		// Start the lifecycle manager
 		lifecycleManager.startServices();
 		lifecycleManager.waitForStartup();
-		// Add a transport
-		Transaction txn = db.startTransaction();
-		try {
-			db.addTransport(txn, transportId, MAX_LATENCY);
-			txn.setComplete();
-		} finally {
-			db.endTransaction(txn);
-		}
 		// Add an identity for Alice
 		LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
 				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
@@ -122,7 +111,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 				groupId, timestamp, null, "text/plain", body);
 		messagingManager.addLocalMessage(message);
 		// Get a stream context
-		StreamContext ctx = keyManager.getStreamContext(contactId, transportId);
+		StreamContext ctx = keyManager.getStreamContext(contactId,
+				TRANSPORT_ID);
 		assertNotNull(ctx);
 		// Create a stream writer
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -132,8 +122,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
 				streamWriter);
 		SyncSession session = new SimplexOutgoingSession(db,
-				new ImmediateExecutor(), eventBus, contactId, transportId,
-				MAX_LATENCY, packetWriter);
+				new ImmediateExecutor(), eventBus, contactId, MAX_LATENCY,
+				packetWriter);
 		// Write whatever needs to be written
 		session.run();
 		streamWriter.close();
@@ -148,7 +138,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	private void read(byte[] stream) throws Exception {
 		// Instantiate Bob's services
-		LifecycleManager lifecycleManager = bob.getLifeCycleManager();
+		LifecycleManager lifecycleManager = bob.getLifecycleManager();
 		DatabaseComponent db = bob.getDatabaseComponent();
 		IdentityManager identityManager = bob.getIdentityManager();
 		ContactManager contactManager = bob.getContactManager();
@@ -162,14 +152,6 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		// Start the lifecyle manager
 		lifecycleManager.startServices();
 		lifecycleManager.waitForStartup();
-		// Add a transport
-		Transaction txn = db.startTransaction();
-		try {
-			db.addTransport(txn, transportId, MAX_LATENCY);
-			txn.setComplete();
-		} finally {
-			db.endTransaction(txn);
-		}
 		// Add an identity for Bob
 		LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
 				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
@@ -188,7 +170,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		byte[] tag = new byte[TAG_LENGTH];
 		int read = in.read(tag);
 		assertEquals(tag.length, read);
-		StreamContext ctx = keyManager.getStreamContext(transportId, tag);
+		StreamContext ctx = keyManager.getStreamContext(TRANSPORT_ID, tag);
 		assertNotNull(ctx);
 		// Create a stream reader
 		InputStream streamReader = streamReaderFactory.createStreamReader(
@@ -197,7 +179,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		PacketReader packetReader = packetReaderFactory.createPacketReader(
 				streamReader);
 		SyncSession session = new IncomingSession(db, new ImmediateExecutor(),
-				eventBus, contactId, transportId, packetReader);
+				eventBus, contactId, packetReader);
 		// No messages should have been added yet
 		assertFalse(listener.messageAdded);
 		// Read whatever needs to be read
diff --git a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
index 7718122f011489613a21bba5d61a3717aace09f7..feccd77ff41d90df68d1e644826213798da9024d 100644
--- a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
+++ b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
@@ -7,9 +7,8 @@ import org.briarproject.android.api.AndroidExecutor;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.plugins.BackoffFactory;
-import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
+import org.briarproject.api.plugins.PluginConfig;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
-import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
 import org.briarproject.api.system.LocationUtils;
 import org.briarproject.plugins.droidtooth.DroidtoothPluginFactory;
@@ -29,17 +28,8 @@ import dagger.Provides;
 public class AndroidPluginsModule {
 
 	@Provides
-	SimplexPluginConfig provideSimplexPluginConfig() {
-		return new SimplexPluginConfig() {
-			public Collection<SimplexPluginFactory> getFactories() {
-				return Collections.emptyList();
-			}
-		};
-	}
-
-	@Provides
-	public DuplexPluginConfig provideDuplexPluginConfig(
-			@IoExecutor Executor ioExecutor, AndroidExecutor androidExecutor,
+	public PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
+			AndroidExecutor androidExecutor,
 			SecureRandom random, BackoffFactory backoffFactory, Application app,
 			LocationUtils locationUtils, EventBus eventBus) {
 		Context appContext = app.getApplicationContext();
@@ -49,13 +39,19 @@ public class AndroidPluginsModule {
 				locationUtils, eventBus);
 		DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
 				backoffFactory, appContext);
-		final Collection<DuplexPluginFactory> factories =
+		final Collection<DuplexPluginFactory> duplex =
 				Arrays.asList(bluetooth, tor, lan);
-		return new DuplexPluginConfig() {
-			public Collection<DuplexPluginFactory> getFactories() {
-				return factories;
+		return new PluginConfig() {
+
+			@Override
+			public Collection<DuplexPluginFactory> getDuplexFactories() {
+				return duplex;
+			}
+
+			@Override
+			public Collection<SimplexPluginFactory> getSimplexFactories() {
+				return Collections.emptyList();
 			}
 		};
 	}
-
 }
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
index 229aba22722481cbb67b91e49a1aa6fcd3c66d39..ee7acf26ba47c4fb39128f3eede571189c39e4f7 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
@@ -2,8 +2,8 @@ package org.briarproject.plugins.droidtooth;
 
 import android.content.Context;
 
-import org.briarproject.api.TransportId;
 import org.briarproject.android.api.AndroidExecutor;
+import org.briarproject.api.TransportId;
 import org.briarproject.api.plugins.Backoff;
 import org.briarproject.api.plugins.BackoffFactory;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
@@ -40,6 +40,10 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
 		return DroidtoothPlugin.ID;
 	}
 
+	public int getMaxLatency() {
+		return MAX_LATENCY;
+	}
+
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
 		Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
 				MAX_POLLING_INTERVAL, BACKOFF_BASE);
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothTransportConnection.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothTransportConnection.java
index dfb5b41f31e892a5d351f590e082b8591d79c761..e4db5673d219c82a65e44105c7e8f8f945a544fe 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothTransportConnection.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothTransportConnection.java
@@ -1,16 +1,16 @@
 package org.briarproject.plugins.droidtooth;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.atomic.AtomicBoolean;
+import android.bluetooth.BluetoothSocket;
 
 import org.briarproject.api.plugins.Plugin;
 import org.briarproject.api.plugins.TransportConnectionReader;
 import org.briarproject.api.plugins.TransportConnectionWriter;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
 
-import android.bluetooth.BluetoothSocket;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 class DroidtoothTransportConnection implements DuplexTransportConnection {
 
@@ -39,10 +39,6 @@ class DroidtoothTransportConnection implements DuplexTransportConnection {
 
 	private class Reader implements TransportConnectionReader {
 
-		public long getMaxLatency() {
-			return plugin.getMaxLatency();
-		}
-
 		public InputStream getInputStream() throws IOException {
 			return socket.getInputStream();
 		}
diff --git a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
index 0813ebfdd71dc460f9038f20ff314dcc749f50fd..16d456ad3b22e47a2e128e45afd55705dccba3dc 100644
--- a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
@@ -34,6 +34,10 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
 		return LanTcpPlugin.ID;
 	}
 
+	public int getMaxLatency() {
+		return MAX_LATENCY;
+	}
+
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
 		Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
 				MAX_POLLING_INTERVAL, BACKOFF_BASE);
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
index 3c6a749867b89366a086e6dfddf518e4337a76df..836dd48b61ff0aace079062dd32024d899762307 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
@@ -44,6 +44,10 @@ public class TorPluginFactory implements DuplexPluginFactory {
 		return TorPlugin.ID;
 	}
 
+	public int getMaxLatency() {
+		return MAX_LATENCY;
+	}
+
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
 
 		// Check that we have a Tor binary for this architecture
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorTransportConnection.java b/briar-android/src/org/briarproject/plugins/tor/TorTransportConnection.java
index a847bc912377c23cb44b3bdd0101650826695213..f6d806c880f8c859704a10ad0b71b5f7a13b32a6 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorTransportConnection.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorTransportConnection.java
@@ -1,16 +1,16 @@
 package org.briarproject.plugins.tor;
 
+import org.briarproject.api.plugins.Plugin;
+import org.briarproject.api.plugins.TransportConnectionReader;
+import org.briarproject.api.plugins.TransportConnectionWriter;
+import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.briarproject.api.plugins.Plugin;
-import org.briarproject.api.plugins.TransportConnectionReader;
-import org.briarproject.api.plugins.TransportConnectionWriter;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-
 class TorTransportConnection implements DuplexTransportConnection {
 
 	private final Plugin plugin;
@@ -38,10 +38,6 @@ class TorTransportConnection implements DuplexTransportConnection {
 
 	private class Reader implements TransportConnectionReader {
 
-		public long getMaxLatency() {
-			return plugin.getMaxLatency();
-		}
-
 		public InputStream getInputStream() throws IOException {
 			return socket.getInputStream();
 		}
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index 08bc9692f9a9f9d77f3d2dbe4724c7d1a539c35a..2292bc0ba258a674251de8cdb5b6ebee64750251 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -26,10 +26,6 @@ import java.util.Map;
 /**
  * Encapsulates the database implementation and exposes high-level operations
  * to other components.
- * <p/>
- * This interface's methods are blocking, but they do not call out into other
- * components except to broadcast {@link org.briarproject.api.event.Event
- * Events}, so they can safely be called while holding locks.
  */
 public interface DatabaseComponent {
 
@@ -45,8 +41,12 @@ public interface DatabaseComponent {
 
 	/**
 	 * Starts a new transaction and returns an object representing it.
+	 * <p/>
+	 * This method acquires locks, so it must not be called while holding a
+	 * lock.
+	 * @param readOnly true if the transaction will only be used for reading.
 	 */
-	Transaction startTransaction() throws DbException;
+	Transaction startTransaction(boolean readOnly) throws DbException;
 
 	/**
 	 * Ends a transaction. If the transaction is marked as complete, the
@@ -142,70 +142,97 @@ public interface DatabaseComponent {
 
 	/**
 	 * Returns the contact with the given ID.
+	 * <p/>
+	 * Read-only.
 	 */
 	Contact getContact(Transaction txn, ContactId c) throws DbException;
 
 	/**
 	 * Returns all contacts.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<Contact> getContacts(Transaction txn) throws DbException;
 
 	/**
 	 * Returns all contacts associated with the given local pseudonym.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<ContactId> getContacts(Transaction txn, AuthorId a)
 			throws DbException;
 
 	/**
 	 * Returns the unique ID for this device.
+	 * <p/>
+	 * Read-only.
 	 */
 	DeviceId getDeviceId(Transaction txn) throws DbException;
 
 	/**
 	 * Returns the group with the given ID.
+	 * <p/>
+	 * Read-only.
 	 */
 	Group getGroup(Transaction txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns the metadata for the given group.
+	 * <p/>
+	 * Read-only.
 	 */
 	Metadata getGroupMetadata(Transaction txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns all groups belonging to the given client.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException;
 
 	/**
 	 * Returns the local pseudonym with the given ID.
+	 * <p/>
+	 * Read-only.
 	 */
 	LocalAuthor getLocalAuthor(Transaction txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns all local pseudonyms.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
 
 	/**
 	 * Returns the IDs of any messages that need to be validated by the given
 	 * client.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
 			throws DbException;
 
 	/**
-	 * Returns the message with the given ID, in serialised form.
+	 * Returns the message with the given ID, in serialised form, or null if
+	 * the message has been deleted.
+	 * <p/>
+	 * Read-only.
 	 */
 	byte[] getRawMessage(Transaction txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the metadata for all messages in the given group.
+	 * <p/>
+	 * Read-only.
 	 */
 	Map<MessageId, Metadata> getMessageMetadata(Transaction txn, GroupId g)
 			throws DbException;
 
 	/**
 	 * Returns the metadata for the given message.
+	 * <p/>
+	 * Read-only.
 	 */
 	Metadata getMessageMetadata(Transaction txn, MessageId m)
 			throws DbException;
@@ -213,6 +240,8 @@ public interface DatabaseComponent {
 	/**
 	 * Returns the status of all messages in the given group with respect to
 	 * the given contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<MessageStatus> getMessageStatus(Transaction txn, ContactId c,
 			GroupId g) throws DbException;
@@ -220,27 +249,27 @@ public interface DatabaseComponent {
 	/**
 	 * Returns the status of the given message with respect to the given
 	 * contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
 			throws DbException;
 
 	/**
 	 * Returns all settings in the given namespace.
+	 * <p/>
+	 * Read-only.
 	 */
 	Settings getSettings(Transaction txn, String namespace) throws DbException;
 
 	/**
 	 * Returns all transport keys for the given transport.
+	 * <p/>
+	 * Read-only.
 	 */
 	Map<ContactId, TransportKeys> getTransportKeys(Transaction txn,
 			TransportId t) throws DbException;
 
-	/**
-	 * Returns the maximum latencies in milliseconds of all transports.
-	 */
-	Map<TransportId, Integer> getTransportLatencies(Transaction txn)
-			throws DbException;
-
 	/**
 	 * Increments the outgoing stream counter for the given contact and
 	 * transport in the given rotation period .
@@ -250,6 +279,8 @@ public interface DatabaseComponent {
 
 	/**
 	 * Returns true if the given group is visible to the given contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean isVisibleToContact(Transaction txn, ContactId c, GroupId g)
 			throws DbException;
diff --git a/briar-api/src/org/briarproject/api/db/Transaction.java b/briar-api/src/org/briarproject/api/db/Transaction.java
index cb89f142ca3461582180f53f9ffb9e557e0cdc64..15297a5734cc0524345c679835f1212e7e029f86 100644
--- a/briar-api/src/org/briarproject/api/db/Transaction.java
+++ b/briar-api/src/org/briarproject/api/db/Transaction.java
@@ -12,12 +12,14 @@ import java.util.List;
 public class Transaction {
 
 	private final Object txn;
+	private final boolean readOnly;
 
 	private List<Event> events = null;
 	private boolean complete = false;
 
-	public Transaction(Object txn) {
+	public Transaction(Object txn, boolean readOnly) {
 		this.txn = txn;
+		this.readOnly = readOnly;
 	}
 
 	/**
@@ -28,6 +30,13 @@ public class Transaction {
 		return txn;
 	}
 
+	/**
+	 * Returns true if the transaction can only be used for reading.
+	 */
+	public boolean isReadOnly() {
+		return readOnly;
+	}
+
 	/**
 	 * Attaches an event to be broadcast when the transaction has been
 	 * committed.
diff --git a/briar-api/src/org/briarproject/api/event/TransportAddedEvent.java b/briar-api/src/org/briarproject/api/event/TransportAddedEvent.java
deleted file mode 100644
index b775ce6c9e5dd88c112f6821fe157b4a0e68d853..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/event/TransportAddedEvent.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.briarproject.api.event;
-
-import org.briarproject.api.TransportId;
-
-/** An event that is broadcast when a transport is added. */
-public class TransportAddedEvent extends Event {
-
-	private final TransportId transportId;
-	private final int maxLatency;
-
-	public TransportAddedEvent(TransportId transportId, int maxLatency) {
-		this.transportId = transportId;
-		this.maxLatency = maxLatency;
-	}
-
-	public TransportId getTransportId() {
-		return transportId;
-	}
-
-	public int getMaxLatency() {
-		return maxLatency;
-	}
-}
diff --git a/briar-api/src/org/briarproject/api/event/TransportRemovedEvent.java b/briar-api/src/org/briarproject/api/event/TransportRemovedEvent.java
deleted file mode 100644
index a0162d595e6a9705a5d8d4c03ee141cad4642284..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/event/TransportRemovedEvent.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.briarproject.api.event;
-
-import org.briarproject.api.TransportId;
-
-/** An event that is broadcast when a transport is removed. */
-public class TransportRemovedEvent extends Event {
-
-	private final TransportId transportId;
-
-	public TransportRemovedEvent(TransportId transportId) {
-		this.transportId = transportId;
-	}
-
-	public TransportId getTransportId() {
-		return transportId;
-	}
-}
diff --git a/briar-api/src/org/briarproject/api/plugins/PluginConfig.java b/briar-api/src/org/briarproject/api/plugins/PluginConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fda14476e8b3c900d1755e1bf948a5420fce46b
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/plugins/PluginConfig.java
@@ -0,0 +1,13 @@
+package org.briarproject.api.plugins;
+
+import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
+import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
+
+import java.util.Collection;
+
+public interface PluginConfig {
+
+	Collection<DuplexPluginFactory> getDuplexFactories();
+
+	Collection<SimplexPluginFactory> getSimplexFactories();
+}
diff --git a/briar-api/src/org/briarproject/api/plugins/TransportConnectionReader.java b/briar-api/src/org/briarproject/api/plugins/TransportConnectionReader.java
index e071c4432b0b8d5eeccd0f014294b25281ed262a..e2b2855dea9bec5c5a764946f59c8ccbc256bd2f 100644
--- a/briar-api/src/org/briarproject/api/plugins/TransportConnectionReader.java
+++ b/briar-api/src/org/briarproject/api/plugins/TransportConnectionReader.java
@@ -9,10 +9,9 @@ import java.io.InputStream;
  */
 public interface TransportConnectionReader {
 
-	/** Returns the maximum latency of the transport in milliseconds. */
-	long getMaxLatency();
-
-	/** Returns an input stream for reading from the transport connection. */
+	/**
+	 * Returns an input stream for reading from the transport connection.
+	 */
 	InputStream getInputStream() throws IOException;
 
 	/**
@@ -20,10 +19,13 @@ public interface TransportConnectionReader {
 	 * simplex, the connection is closed. If the transport is duplex, the
 	 * connection is closed if <tt>exception</tt> is true or the other side of
 	 * the connection has been marked as closed.
-	 * @param exception true if the connection is being closed because of an
-	 * exception. This may affect how resources are disposed of.
+	 *
+	 * @param exception  true if the connection is being closed because of an
+	 *                   exception. This may affect how resources are disposed
+	 *                   of.
 	 * @param recognised true if the connection is definitely a Briar transport
-	 * connection. This may affect how resources are disposed of.
+	 *                   connection. This may affect how resources are disposed
+	 *                   of.
 	 */
 	void dispose(boolean exception, boolean recognised) throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPluginConfig.java b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPluginConfig.java
deleted file mode 100644
index fc17637196d37fbf7e489dfeb8a4f2a3716c65fa..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPluginConfig.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.briarproject.api.plugins.duplex;
-
-import java.util.Collection;
-
-public interface DuplexPluginConfig {
-
-	Collection<DuplexPluginFactory> getFactories();
-}
diff --git a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPluginFactory.java b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPluginFactory.java
index 9e8cf7021759764dc4125e17e7f0b7e475b507fe..914402e3ca8f5666ee428a267bc2487d4260102a 100644
--- a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPluginFactory.java
+++ b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPluginFactory.java
@@ -2,12 +2,23 @@ package org.briarproject.api.plugins.duplex;
 
 import org.briarproject.api.TransportId;
 
-/** Factory for creating a plugin for a duplex transport. */
+/**
+ * Factory for creating a plugin for a duplex transport.
+ */
 public interface DuplexPluginFactory {
 
-	/** Returns the plugin's transport identifier. */
+	/**
+	 * Returns the plugin's transport identifier.
+	 */
 	TransportId getId();
 
-	/** Creates and returns a plugin, or null if no plugin can be created. */
+	/**
+	 * Returns the maximum latency of the transport in milliseconds.
+	 */
+	int getMaxLatency();
+
+	/**
+	 * Creates and returns a plugin, or null if no plugin can be created.
+	 */
 	DuplexPlugin createPlugin(DuplexPluginCallback callback);
 }
diff --git a/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPluginConfig.java b/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPluginConfig.java
deleted file mode 100644
index 59f4f6a079be3605d613dcf81bbcf671d5e75083..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPluginConfig.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.briarproject.api.plugins.simplex;
-
-import java.util.Collection;
-
-public interface SimplexPluginConfig {
-
-	Collection<SimplexPluginFactory> getFactories();
-}
diff --git a/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPluginFactory.java b/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPluginFactory.java
index bc5c955a52114a8ba732751b2ad8a0cf126c5586..38277550abfdc61311b68ac1e7975e0a6aff748d 100644
--- a/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPluginFactory.java
+++ b/briar-api/src/org/briarproject/api/plugins/simplex/SimplexPluginFactory.java
@@ -2,12 +2,23 @@ package org.briarproject.api.plugins.simplex;
 
 import org.briarproject.api.TransportId;
 
-/** Factory for creating a plugin for a simplex transport. */
+/**
+ * Factory for creating a plugin for a simplex transport.
+ */
 public interface SimplexPluginFactory {
 
-	/** Returns the plugin's transport identifier. */
+	/**
+	 * Returns the plugin's transport identifier.
+	 */
 	TransportId getId();
 
-	/** Creates and returns a plugin, or null if no plugin can be created. */
+	/**
+	 * Returns the maximum latency of the transport in milliseconds.
+	 */
+	int getMaxLatency();
+
+	/**
+	 * Creates and returns a plugin, or null if no plugin can be created.
+	 */
 	SimplexPlugin createPlugin(SimplexPluginCallback callback);
 }
diff --git a/briar-api/src/org/briarproject/api/sync/SyncSessionFactory.java b/briar-api/src/org/briarproject/api/sync/SyncSessionFactory.java
index b5db6a3493e9beba4af9ffcb9130325888de1c61..db5954ffedff642501d50301273972fc01521dd8 100644
--- a/briar-api/src/org/briarproject/api/sync/SyncSessionFactory.java
+++ b/briar-api/src/org/briarproject/api/sync/SyncSessionFactory.java
@@ -1,6 +1,5 @@
 package org.briarproject.api.sync;
 
-import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 
 import java.io.InputStream;
@@ -8,12 +7,11 @@ import java.io.OutputStream;
 
 public interface SyncSessionFactory {
 
-	SyncSession createIncomingSession(ContactId c, TransportId t,
-			InputStream in);
+	SyncSession createIncomingSession(ContactId c, InputStream in);
 
-	SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
-			int maxLatency, OutputStream out);
+	SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency,
+			OutputStream out);
 
-	SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
-			int maxLatency, int maxIdleTime, OutputStream out);
+	SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
+			int maxIdleTime, OutputStream out);
 }
diff --git a/briar-api/src/org/briarproject/api/transport/KeyManager.java b/briar-api/src/org/briarproject/api/transport/KeyManager.java
index 7cfd4d1eb98f1d978b68b330c8d66eac2f7d125d..8ee0578a860ab84eedde2a456d38822c3ee9338a 100644
--- a/briar-api/src/org/briarproject/api/transport/KeyManager.java
+++ b/briar-api/src/org/briarproject/api/transport/KeyManager.java
@@ -26,12 +26,14 @@ public interface KeyManager {
 	 * contact over the given transport, or null if an error occurs or the
 	 * contact does not support the transport.
 	 */
-	StreamContext getStreamContext(ContactId c, TransportId t);
+	StreamContext getStreamContext(ContactId c, TransportId t)
+			throws DbException;
 
 	/**
 	 * Looks up the given tag and returns a {@link StreamContext} for reading
 	 * from the corresponding stream, or null if an error occurs or the tag was
 	 * unexpected.
 	 */
-	StreamContext getStreamContext(TransportId t, byte[] tag);
+	StreamContext getStreamContext(TransportId t, byte[] tag)
+			throws DbException;
 }
diff --git a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java
index 40640c17ab397c780f6cd97ad0253ec3af946009..5c64dc98feba454d705a5703587f315c2d39bae2 100644
--- a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java
+++ b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java
@@ -57,7 +57,7 @@ class ClientHelperImpl implements ClientHelper {
 	@Override
 	public void addLocalMessage(Message m, ClientId c, BdfDictionary metadata,
 			boolean shared) throws DbException, FormatException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			addLocalMessage(txn, m, c, metadata, shared);
 			txn.setComplete();
@@ -89,7 +89,7 @@ class ClientHelperImpl implements ClientHelper {
 	public BdfDictionary getMessageAsDictionary(MessageId m) throws DbException,
 			FormatException {
 		BdfDictionary dictionary;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			dictionary = getMessageAsDictionary(txn, m);
 			txn.setComplete();
@@ -112,7 +112,7 @@ class ClientHelperImpl implements ClientHelper {
 	public BdfList getMessageAsList(MessageId m) throws DbException,
 			FormatException {
 		BdfList list;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			list = getMessageAsList(txn, m);
 			txn.setComplete();
@@ -135,7 +135,7 @@ class ClientHelperImpl implements ClientHelper {
 	public BdfDictionary getGroupMetadataAsDictionary(GroupId g)
 			throws DbException, FormatException {
 		BdfDictionary dictionary;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			dictionary = getGroupMetadataAsDictionary(txn, g);
 			txn.setComplete();
@@ -156,7 +156,7 @@ class ClientHelperImpl implements ClientHelper {
 	public BdfDictionary getMessageMetadataAsDictionary(MessageId m)
 			throws DbException, FormatException {
 		BdfDictionary dictionary;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			dictionary = getMessageMetadataAsDictionary(txn, m);
 			txn.setComplete();
@@ -177,7 +177,7 @@ class ClientHelperImpl implements ClientHelper {
 	public Map<MessageId, BdfDictionary> getMessageMetatataAsDictionary(
 			GroupId g) throws DbException, FormatException {
 		Map<MessageId, BdfDictionary> map;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			map = getMessageMetadataAsDictionary(txn, g);
 			txn.setComplete();
@@ -201,7 +201,7 @@ class ClientHelperImpl implements ClientHelper {
 	@Override
 	public void mergeGroupMetadata(GroupId g, BdfDictionary metadata)
 			throws DbException, FormatException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			mergeGroupMetadata(txn, g, metadata);
 			txn.setComplete();
@@ -219,7 +219,7 @@ class ClientHelperImpl implements ClientHelper {
 	@Override
 	public void mergeMessageMetadata(MessageId m, BdfDictionary metadata)
 			throws DbException, FormatException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			mergeMessageMetadata(txn, m, metadata);
 			txn.setComplete();
diff --git a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
index df75b73e1a17fbce161b1c527086dd2808233710..7ff709b70c9062f79d9cb29c72979a405daadae0 100644
--- a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
+++ b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
@@ -51,7 +51,7 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
 			long timestamp, boolean alice, boolean active)
 			throws DbException {
 		ContactId c;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			c = db.addContact(txn, remote, local, active);
 			keyManager.addContact(txn, c, master, timestamp, alice);
@@ -68,7 +68,7 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
 	@Override
 	public Contact getContact(ContactId c) throws DbException {
 		Contact contact;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			contact = db.getContact(txn, c);
 			txn.setComplete();
@@ -81,7 +81,7 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
 	@Override
 	public Collection<Contact> getActiveContacts() throws DbException {
 		Collection<Contact> contacts;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			contacts = db.getContacts(txn);
 			txn.setComplete();
@@ -95,7 +95,7 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
 
 	@Override
 	public void removeContact(ContactId c) throws DbException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			removeContact(txn, c);
 			txn.setComplete();
@@ -107,7 +107,7 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
 	@Override
 	public void setContactActive(ContactId c, boolean active)
 			throws DbException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			db.setContactActive(txn, c, active);
 			txn.setComplete();
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index 2544a96702e2b7b075cf55f4629b26c228e92dfe..bcd1b7f44865297b41dd97cd1ee657aa7adf0fe3 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -118,38 +118,52 @@ interface Database<T> {
 	/**
 	 * Returns true if the database contains the given contact for the given
 	 * local pseudonym.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean containsContact(T txn, AuthorId remote, AuthorId local)
 			throws DbException;
 
 	/**
 	 * Returns true if the database contains the given contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean containsContact(T txn, ContactId c) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given group.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean containsGroup(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given local pseudonym.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean containsLocalAuthor(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given message.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean containsMessage(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given transport.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean containsTransport(T txn, TransportId t) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given group and the group is
 	 * visible to the given contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean containsVisibleGroup(T txn, ContactId c, GroupId g)
 			throws DbException;
@@ -157,12 +171,16 @@ interface Database<T> {
 	/**
 	 * Returns true if the database contains the given message and the message
 	 * is visible to the given contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	boolean containsVisibleMessage(T txn, ContactId c, MessageId m)
 			throws DbException;
 
 	/**
 	 * Returns the number of messages offered by the given contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	int countOfferedMessages(T txn, ContactId c) throws DbException;
 
@@ -171,35 +189,39 @@ interface Database<T> {
 	 * {@link #removeMessage(Object, MessageId)}, the message ID and any other
 	 * associated data are not deleted, and
 	 * {@link #containsMessage(Object, MessageId)} will continue to return true.
-	 * <p>
-	 * Locking: write.
 	 */
 	void deleteMessage(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Deletes any metadata associated with the given message.
-	 * <p>
-	 * Locking: write.
 	 */
 	void deleteMessageMetadata(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the contact with the given ID.
+	 * <p/>
+	 * Read-only.
 	 */
 	Contact getContact(T txn, ContactId c) throws DbException;
 
 	/**
 	 * Returns all contacts.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<Contact> getContacts(T txn) throws DbException;
 
 	/**
 	 * Returns all contacts associated with the given local pseudonym.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns the unique ID for this device.
+	 * <p/>
+	 * Read-only.
 	 */
 	DeviceId getDeviceId(T txn) throws DbException;
 
@@ -212,48 +234,66 @@ interface Database<T> {
 
 	/**
 	 * Returns the group with the given ID.
+	 * <p/>
+	 * Read-only.
 	 */
 	Group getGroup(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns the metadata for the given group.
+	 * <p/>
+	 * Read-only.
 	 */
 	Metadata getGroupMetadata(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns all groups belonging to the given client.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<Group> getGroups(T txn, ClientId c) throws DbException;
 
 	/**
 	 * Returns the local pseudonym with the given ID.
+	 * <p/>
+	 * Read-only.
 	 */
 	LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns all local pseudonyms.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
 
 	/**
 	 * Returns the IDs of all messages in the given group.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<MessageId> getMessageIds(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns the metadata for all messages in the given group.
+	 * <p/>
+	 * Read-only.
 	 */
 	Map<MessageId, Metadata> getMessageMetadata(T txn, GroupId g)
 			throws DbException;
 
 	/**
 	 * Returns the metadata for the given message.
+	 * <p/>
+	 * Read-only.
 	 */
 	Metadata getMessageMetadata(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the status of all messages in the given group with respect
 	 * to the given contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<MessageStatus> getMessageStatus(T txn, ContactId c, GroupId g)
 			throws DbException;
@@ -261,6 +301,8 @@ interface Database<T> {
 	/**
 	 * Returns the status of the given message with respect to the given
 	 * contact.
+	 * <p/>
+	 * Read-only.
 	 */
 	MessageStatus getMessageStatus(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -268,6 +310,8 @@ 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/>
+	 * Read-only.
 	 */
 	Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
 			throws DbException;
@@ -275,6 +319,8 @@ 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/>
+	 * Read-only.
 	 */
 	Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
 			int maxMessages) throws DbException;
@@ -282,6 +328,8 @@ 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/>
+	 * Read-only.
 	 */
 	Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength)
 			throws DbException;
@@ -289,6 +337,8 @@ 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/>
+	 * Read-only.
 	 */
 	Collection<MessageId> getMessagesToRequest(T txn, ContactId c,
 			int maxMessages) throws DbException;
@@ -296,12 +346,17 @@ interface Database<T> {
 	/**
 	 * Returns the IDs of any messages that need to be validated by the given
 	 * client.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
 			throws DbException;
 
 	/**
-	 * Returns the message with the given ID, in serialised form.
+	 * Returns the message with the given ID, in serialised form, or null if
+	 * the message has been deleted.
+	 * <p/>
+	 * Read-only.
 	 */
 	byte[] getRawMessage(T txn, MessageId m) throws DbException;
 
@@ -309,28 +364,31 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be sent to the
 	 * given contact and have been requested by the contact, up to the given
 	 * total length.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
 			int maxLength) throws DbException;
 
 	/**
 	 * Returns all settings in the given namespace.
+	 * <p/>
+	 * Read-only.
 	 */
 	Settings getSettings(T txn, String namespace) throws DbException;
 
 	/**
 	 * Returns all transport keys for the given transport.
+	 * <p/>
+	 * Read-only.
 	 */
 	Map<ContactId, TransportKeys> getTransportKeys(T txn, TransportId t)
 			throws DbException;
 
-	/**
-	 * Returns the maximum latencies in milliseconds of all transports.
-	 */
-	Map<TransportId, Integer> getTransportLatencies(T txn) throws DbException;
-
 	/**
 	 * Returns the IDs of all contacts to which the given group is visible.
+	 * <p/>
+	 * Read-only.
 	 */
 	Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
 
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index a912407513482683afdb1e4f97fd61d025b981c5..d00159903596c83da6d3750dc115571dcab6215e 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -33,8 +33,6 @@ import org.briarproject.api.event.MessageValidatedEvent;
 import org.briarproject.api.event.MessagesAckedEvent;
 import org.briarproject.api.event.MessagesSentEvent;
 import org.briarproject.api.event.SettingsUpdatedEvent;
-import org.briarproject.api.event.TransportAddedEvent;
-import org.briarproject.api.event.TransportRemovedEvent;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
@@ -61,6 +59,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
@@ -80,6 +80,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 	private final EventBus eventBus;
 	private final ShutdownManager shutdown;
 	private final AtomicBoolean closed = new AtomicBoolean(false);
+	private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
 
 	private volatile int shutdownHandle = -1;
 
@@ -117,18 +118,33 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		db.close();
 	}
 
-	public Transaction startTransaction() throws DbException {
-		return new Transaction(db.startTransaction());
+	public Transaction startTransaction(boolean readOnly) throws DbException {
+		if (readOnly) lock.readLock().lock();
+		else lock.writeLock().lock();
+		try {
+			return new Transaction(db.startTransaction(), readOnly);
+		} catch (DbException e) {
+			if (readOnly) lock.readLock().unlock();
+			else lock.writeLock().unlock();
+			throw e;
+		} catch (RuntimeException e) {
+			if (readOnly) lock.readLock().unlock();
+			else lock.writeLock().unlock();
+			throw e;
+		}
 	}
 
 	public void endTransaction(Transaction transaction) throws DbException {
-		T txn = txnClass.cast(transaction.unbox());
-		if (transaction.isComplete()) {
-			db.commitTransaction(txn);
-			for (Event e : transaction.getEvents()) eventBus.broadcast(e);
-		} else {
-			db.abortTransaction(txn);
+		try {
+			T txn = txnClass.cast(transaction.unbox());
+			if (transaction.isComplete()) db.commitTransaction(txn);
+			else db.abortTransaction(txn);
+		} finally {
+			if (transaction.isReadOnly()) lock.readLock().unlock();
+			else lock.writeLock().unlock();
 		}
+		if (transaction.isComplete())
+			for (Event e : transaction.getEvents()) eventBus.broadcast(e);
 	}
 
 	private T unbox(Transaction transaction) {
@@ -138,6 +154,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public ContactId addContact(Transaction transaction, Author remote,
 			AuthorId local, boolean active) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsLocalAuthor(txn, local))
 			throw new NoSuchLocalAuthorException();
@@ -150,6 +167,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 	}
 
 	public void addGroup(Transaction transaction, Group g) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsGroup(txn, g.getId())) {
 			db.addGroup(txn, g);
@@ -159,6 +177,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void addLocalAuthor(Transaction transaction, LocalAuthor a)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsLocalAuthor(txn, a.getId())) {
 			db.addLocalAuthor(txn, a);
@@ -168,6 +187,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void addLocalMessage(Transaction transaction, Message m, ClientId c,
 			Metadata meta, boolean shared) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsGroup(txn, m.getGroupId()))
 			throw new NoSuchGroupException();
@@ -191,15 +211,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void addTransport(Transaction transaction, TransportId t,
 			int maxLatency) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
-		if (!db.containsTransport(txn, t)) {
+		if (!db.containsTransport(txn, t))
 			db.addTransport(txn, t, maxLatency);
-			transaction.attach(new TransportAddedEvent(t, maxLatency));
-		}
 	}
 
 	public void addTransportKeys(Transaction transaction, ContactId c,
 			TransportKeys k) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -210,6 +230,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void deleteMessage(Transaction transaction, MessageId m)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsMessage(txn, m))
 			throw new NoSuchMessageException();
@@ -218,6 +239,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void deleteMessageMetadata(Transaction transaction, MessageId m)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsMessage(txn, m))
 			throw new NoSuchMessageException();
@@ -226,6 +248,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public Ack generateAck(Transaction transaction, ContactId c,
 			int maxMessages) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -237,6 +260,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public Collection<byte[]> generateBatch(Transaction transaction,
 			ContactId c, int maxLength, int maxLatency) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -254,6 +278,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public Offer generateOffer(Transaction transaction, ContactId c,
 			int maxMessages, int maxLatency) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -265,6 +290,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public Request generateRequest(Transaction transaction, ContactId c,
 			int maxMessages) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -277,6 +303,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public Collection<byte[]> generateRequestedBatch(Transaction transaction,
 			ContactId c, int maxLength, int maxLatency) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -420,14 +447,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		return db.getTransportKeys(txn, t);
 	}
 
-	public Map<TransportId, Integer> getTransportLatencies(
-			Transaction transaction) throws DbException {
-		T txn = unbox(transaction);
-		return db.getTransportLatencies(txn);
-	}
-
 	public void incrementStreamCounter(Transaction transaction, ContactId c,
 			TransportId t, long rotationPeriod) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -448,6 +470,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void mergeGroupMetadata(Transaction transaction, GroupId g,
 			Metadata meta) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsGroup(txn, g))
 			throw new NoSuchGroupException();
@@ -456,6 +479,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void mergeMessageMetadata(Transaction transaction, MessageId m,
 			Metadata meta) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsMessage(txn, m))
 			throw new NoSuchMessageException();
@@ -464,6 +488,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void mergeSettings(Transaction transaction, Settings s,
 			String namespace) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		Settings old = db.getSettings(txn, namespace);
 		Settings merged = new Settings();
@@ -477,6 +502,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void receiveAck(Transaction transaction, ContactId c, Ack a)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -492,6 +518,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void receiveMessage(Transaction transaction, ContactId c, Message m)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -507,6 +534,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void receiveOffer(Transaction transaction, ContactId c, Offer o)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -529,6 +557,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void receiveRequest(Transaction transaction, ContactId c, Request r)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -545,6 +574,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void removeContact(Transaction transaction, ContactId c)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -554,6 +584,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void removeGroup(Transaction transaction, Group g)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		GroupId id = g.getId();
 		if (!db.containsGroup(txn, id))
@@ -566,6 +597,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void removeLocalAuthor(Transaction transaction, AuthorId a)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsLocalAuthor(txn, a))
 			throw new NoSuchLocalAuthorException();
@@ -575,15 +607,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void removeTransport(Transaction transaction, TransportId t)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsTransport(txn, t))
 			throw new NoSuchTransportException();
 		db.removeTransport(txn, t);
-		transaction.attach(new TransportRemovedEvent(t));
 	}
 
 	public void setContactActive(Transaction transaction, ContactId c,
 			boolean active) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -593,6 +626,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void setMessageShared(Transaction transaction, Message m,
 			boolean shared) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsMessage(txn, m.getId()))
 			throw new NoSuchMessageException();
@@ -602,6 +636,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void setMessageValid(Transaction transaction, Message m, ClientId c,
 			boolean valid) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsMessage(txn, m.getId()))
 			throw new NoSuchMessageException();
@@ -612,6 +647,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 	public void setReorderingWindow(Transaction transaction, ContactId c,
 			TransportId t, long rotationPeriod, long base, byte[] bitmap)
 			throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -622,6 +658,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void setVisibleToContact(Transaction transaction, ContactId c,
 			GroupId g, boolean visible) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		if (!db.containsContact(txn, c))
 			throw new NoSuchContactException();
@@ -647,6 +684,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public void updateTransportKeys(Transaction transaction,
 			Map<ContactId, TransportKeys> keys) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
 		Map<ContactId, TransportKeys> filtered =
 				new HashMap<ContactId, TransportKeys>();
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 7fc836348386c6aa042532889700060295bb69cc..536161562462bc9ce06144418701eb11d4b78510 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -1542,30 +1542,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Map<TransportId, Integer> getTransportLatencies(Connection txn)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT transportId, maxLatency FROM transports";
-			ps = txn.prepareStatement(sql);
-			rs = ps.executeQuery();
-			Map<TransportId, Integer> latencies =
-					new HashMap<TransportId, Integer>();
-			while (rs.next()) {
-				TransportId id = new TransportId(rs.getString(1));
-				latencies.put(id, rs.getInt(2));
-			}
-			rs.close();
-			ps.close();
-			return Collections.unmodifiableMap(latencies);
-		} catch (SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Collection<ContactId> getVisibility(Connection txn, GroupId g)
 			throws DbException {
 		PreparedStatement ps = null;
diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
index d2b2d872e8ebd38954e273ae2a8579438638ba72..8d319ca4ad11a4ef6e66ebc431367829a4c3795d 100644
--- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
@@ -83,7 +83,7 @@ class ForumManagerImpl implements ForumManager {
 	public Forum getForum(GroupId g) throws DbException {
 		try {
 			Group group;
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(true);
 			try {
 				group = db.getGroup(txn, g);
 				txn.setComplete();
@@ -100,7 +100,7 @@ class ForumManagerImpl implements ForumManager {
 	public Collection<Forum> getForums() throws DbException {
 		try {
 			Collection<Group> groups;
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(true);
 			try {
 				groups = db.getGroups(txn, CLIENT_ID);
 				txn.setComplete();
@@ -132,7 +132,7 @@ class ForumManagerImpl implements ForumManager {
 		Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
 		Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
 		Map<MessageId, BdfDictionary> metadata;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			// Load the IDs of the user's identities
 			for (LocalAuthor a : db.getLocalAuthors(txn))
diff --git a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
index b0e8fd486f810180ea15631ba8d2eb57135db72b..c30834e8f6b0a570644533b7bbd0e3b47b5a8fda 100644
--- a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
@@ -131,7 +131,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 
 	@Override
 	public void addForum(Forum f) throws DbException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			db.addGroup(txn, f.getGroup());
 			txn.setComplete();
@@ -144,7 +144,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 	public void removeForum(Forum f) throws DbException {
 		try {
 			// Update the list shared with each contact
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(false);
 			try {
 				for (Contact c : db.getContacts(txn))
 					removeFromList(txn, getContactGroup(c).getId(), f);
@@ -162,7 +162,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 	public Collection<Forum> getAvailableForums() throws DbException {
 		try {
 			Set<Forum> available = new HashSet<Forum>();
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(true);
 			try {
 				// Get any forums we subscribe to
 				Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn,
@@ -196,7 +196,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 	public Collection<Contact> getSharedBy(GroupId g) throws DbException {
 		try {
 			List<Contact> subscribers = new ArrayList<Contact>();
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(true);
 			try {
 				for (Contact c : db.getContacts(txn)) {
 					if (listContains(txn, getContactGroup(c).getId(), g, false))
@@ -216,7 +216,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 	public Collection<ContactId> getSharedWith(GroupId g) throws DbException {
 		try {
 			List<ContactId> shared = new ArrayList<ContactId>();
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(true);
 			try {
 				for (Contact c : db.getContacts(txn)) {
 					if (listContains(txn, getContactGroup(c).getId(), g, true))
@@ -236,7 +236,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 	public void setSharedWith(GroupId g, Collection<ContactId> shared)
 			throws DbException {
 		try {
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(false);
 			try {
 				// Retrieve the forum
 				Forum f = parseForum(db.getGroup(txn, g));
@@ -268,7 +268,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 	@Override
 	public void setSharedWithAll(GroupId g) throws DbException {
 		try {
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(false);
 			try {
 				// Retrieve the forum
 				Forum f = parseForum(db.getGroup(txn, g));
diff --git a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
index 997456d2941dad65c40509e8507dc0eb9a5ca06e..b3d7a1f4d8213f8427963457ff8a3d80b8c91a0f 100644
--- a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
+++ b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
@@ -37,7 +37,7 @@ class IdentityManagerImpl implements IdentityManager {
 
 	@Override
 	public void addLocalAuthor(LocalAuthor localAuthor) throws DbException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			db.addLocalAuthor(txn, localAuthor);
 			for (AddIdentityHook hook : addHooks)
@@ -51,7 +51,7 @@ class IdentityManagerImpl implements IdentityManager {
 	@Override
 	public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
 		LocalAuthor author;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			author = db.getLocalAuthor(txn, a);
 			txn.setComplete();
@@ -64,7 +64,7 @@ class IdentityManagerImpl implements IdentityManager {
 	@Override
 	public Collection<LocalAuthor> getLocalAuthors() throws DbException {
 		Collection<LocalAuthor> authors;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			authors = db.getLocalAuthors(txn);
 			txn.setComplete();
@@ -76,7 +76,7 @@ class IdentityManagerImpl implements IdentityManager {
 
 	@Override
 	public void removeLocalAuthor(AuthorId a) throws DbException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			LocalAuthor localAuthor = db.getLocalAuthor(txn, a);
 			for (RemoveIdentityHook hook : removeHooks)
diff --git a/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java b/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
index aba60f16b147645e738cda56f258a17d73766c8c..f153a3a37714ca1ae8c7935fa12c2d9d950138ea 100644
--- a/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
+++ b/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
@@ -6,7 +6,6 @@ import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.ShutdownEvent;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.Service;
-import org.briarproject.api.system.Clock;
 
 import java.io.IOException;
 import java.util.Collection;
@@ -30,7 +29,6 @@ class LifecycleManagerImpl implements LifecycleManager {
 	private static final Logger LOG =
 			Logger.getLogger(LifecycleManagerImpl.class.getName());
 
-	private final Clock clock;
 	private final DatabaseComponent db;
 	private final EventBus eventBus;
 	private final Collection<Service> services;
@@ -41,8 +39,7 @@ class LifecycleManagerImpl implements LifecycleManager {
 	private final CountDownLatch shutdownLatch = new CountDownLatch(1);
 
 	@Inject
-	LifecycleManagerImpl(Clock clock, DatabaseComponent db, EventBus eventBus) {
-		this.clock = clock;
+	LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus) {
 		this.db = db;
 		this.eventBus = eventBus;
 		services = new CopyOnWriteArrayList<Service>();
@@ -68,9 +65,9 @@ class LifecycleManagerImpl implements LifecycleManager {
 		}
 		try {
 			LOG.info("Starting services");
-			long now = clock.currentTimeMillis();
+			long start = System.currentTimeMillis();
 			boolean reopened = db.open();
-			long duration = clock.currentTimeMillis() - now;
+			long duration = System.currentTimeMillis() - start;
 			if (LOG.isLoggable(INFO)) {
 				if (reopened)
 					LOG.info("Reopening database took " + duration + " ms");
@@ -78,9 +75,9 @@ class LifecycleManagerImpl implements LifecycleManager {
 			}
 			dbLatch.countDown();
 			for (Service s : services) {
-				now = clock.currentTimeMillis();
+				start = System.currentTimeMillis();
 				boolean started = s.start();
-				duration = clock.currentTimeMillis() - now;
+				duration = System.currentTimeMillis() - start;
 				if (!started) {
 					if (LOG.isLoggable(WARNING)) {
 						String name = s.getClass().getName();
diff --git a/briar-core/src/org/briarproject/lifecycle/LifecycleModule.java b/briar-core/src/org/briarproject/lifecycle/LifecycleModule.java
index 7b98244eb405d4e30dc32a4fe1de56e01251d8a6..7ad4cb6b0cb27f48c5e8a6fa6253ed0ee3176c07 100644
--- a/briar-core/src/org/briarproject/lifecycle/LifecycleModule.java
+++ b/briar-core/src/org/briarproject/lifecycle/LifecycleModule.java
@@ -1,6 +1,10 @@
 package org.briarproject.lifecycle;
 
-import static java.util.concurrent.TimeUnit.SECONDS;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.event.EventBus;
+import org.briarproject.api.lifecycle.IoExecutor;
+import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.lifecycle.ShutdownManager;
 
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
@@ -12,16 +16,11 @@ import java.util.concurrent.ThreadPoolExecutor;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
-import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.lifecycle.IoExecutor;
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.lifecycle.ShutdownManager;
-import org.briarproject.api.system.Clock;
-
 import dagger.Module;
 import dagger.Provides;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
 @Module
 public class LifecycleModule {
 
@@ -51,9 +50,9 @@ public class LifecycleModule {
 
 	@Provides
 	@Singleton
-	LifecycleManager provideLifeCycleManager(Clock clock, DatabaseComponent db,
+	LifecycleManager provideLifecycleManager(DatabaseComponent db,
 			EventBus eventBus) {
-		return new LifecycleManagerImpl(clock, db, eventBus);
+		return new LifecycleManagerImpl(db, eventBus);
 	}
 
 	@Provides
@@ -63,5 +62,4 @@ public class LifecycleModule {
 		lifecycleManager.registerForShutdown(ioExecutor);
 		return ioExecutor;
 	}
-
 }
diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
index c1a0eb797cad84b3130b76785e1d397bb682dc32..e7e64552f5c3a6d42f6b8ec2123c2edf03ebfe1b 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
@@ -106,7 +106,7 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 	@Override
 	public GroupId getConversationId(ContactId c) throws DbException {
 		Contact contact;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			contact = db.getContact(txn, c);
 			txn.setComplete();
@@ -121,7 +121,7 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 			throws DbException {
 		Map<MessageId, BdfDictionary> metadata;
 		Collection<MessageStatus> statuses;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			GroupId g = getContactGroup(db.getContact(txn, c)).getId();
 			metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
diff --git a/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java b/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
index 0b6fe073d310186b53c0fac4208846407e85d707..2eb502cf1e929aa30f9dbed84d11a1ee6d069328 100644
--- a/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
@@ -2,6 +2,7 @@ package org.briarproject.plugins;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
 import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.ConnectionRegistry;
@@ -90,8 +91,8 @@ class ConnectionManagerImpl implements ConnectionManager {
 			TransportConnectionReader r) throws IOException {
 		InputStream streamReader = streamReaderFactory.createStreamReader(
 				r.getInputStream(), ctx);
-		return syncSessionFactory.createIncomingSession(
-				ctx.getContactId(), ctx.getTransportId(), streamReader);
+		return syncSessionFactory.createIncomingSession(ctx.getContactId(),
+				streamReader);
 	}
 
 	private SyncSession createSimplexOutgoingSession(StreamContext ctx,
@@ -99,8 +100,7 @@ class ConnectionManagerImpl implements ConnectionManager {
 		OutputStream streamWriter = streamWriterFactory.createStreamWriter(
 				w.getOutputStream(), ctx);
 		return syncSessionFactory.createSimplexOutgoingSession(
-				ctx.getContactId(), ctx.getTransportId(), w.getMaxLatency(),
-				streamWriter);
+				ctx.getContactId(), w.getMaxLatency(), streamWriter);
 	}
 
 	private SyncSession createDuplexOutgoingSession(StreamContext ctx,
@@ -108,8 +108,8 @@ class ConnectionManagerImpl implements ConnectionManager {
 		OutputStream streamWriter = streamWriterFactory.createStreamWriter(
 				w.getOutputStream(), ctx);
 		return syncSessionFactory.createDuplexOutgoingSession(
-				ctx.getContactId(), ctx.getTransportId(), w.getMaxLatency(),
-				w.getMaxIdleTime(), streamWriter);
+				ctx.getContactId(), w.getMaxLatency(), w.getMaxIdleTime(),
+				streamWriter);
 	}
 
 	private class ManageIncomingSimplexConnection implements Runnable {
@@ -133,10 +133,14 @@ class ConnectionManagerImpl implements ConnectionManager {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				disposeReader(true, false);
 				return;
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+				disposeReader(true, false);
+				return;
 			}
 			if (ctx == null) {
 				LOG.info("Unrecognised tag");
-				disposeReader(true, false);
+				disposeReader(false, false);
 				return;
 			}
 			ContactId contactId = ctx.getContactId();
@@ -177,11 +181,17 @@ class ConnectionManagerImpl implements ConnectionManager {
 
 		public void run() {
 			// Allocate a stream context
-			StreamContext ctx = keyManager.getStreamContext(contactId,
-					transportId);
+			StreamContext ctx;
+			try {
+				ctx = keyManager.getStreamContext(contactId, transportId);
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+				disposeWriter(true);
+				return;
+			}
 			if (ctx == null) {
 				LOG.warning("Could not allocate stream context");
-				disposeWriter(true);
+				disposeWriter(false);
 				return;
 			}
 			connectionRegistry.registerConnection(contactId, transportId);
@@ -233,10 +243,14 @@ class ConnectionManagerImpl implements ConnectionManager {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				disposeReader(true, false);
 				return;
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+				disposeReader(true, false);
+				return;
 			}
 			if (ctx == null) {
 				LOG.info("Unrecognised tag");
-				disposeReader(true, false);
+				disposeReader(false, false);
 				return;
 			}
 			contactId = ctx.getContactId();
@@ -262,11 +276,17 @@ class ConnectionManagerImpl implements ConnectionManager {
 
 		private void runOutgoingSession() {
 			// Allocate a stream context
-			StreamContext ctx = keyManager.getStreamContext(contactId,
-					transportId);
+			StreamContext ctx;
+			try {
+				ctx = keyManager.getStreamContext(contactId, transportId);
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+				disposeWriter(true);
+				return;
+			}
 			if (ctx == null) {
 				LOG.warning("Could not allocate stream context");
-				disposeWriter(true);
+				disposeWriter(false);
 				return;
 			}
 			try {
@@ -321,11 +341,17 @@ class ConnectionManagerImpl implements ConnectionManager {
 
 		public void run() {
 			// Allocate a stream context
-			StreamContext ctx = keyManager.getStreamContext(contactId,
-					transportId);
+			StreamContext ctx;
+			try {
+				ctx = keyManager.getStreamContext(contactId, transportId);
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+				disposeWriter(true);
+				return;
+			}
 			if (ctx == null) {
 				LOG.warning("Could not allocate stream context");
-				disposeWriter(true);
+				disposeWriter(false);
 				return;
 			}
 			connectionRegistry.registerConnection(contactId, transportId);
@@ -358,6 +384,10 @@ class ConnectionManagerImpl implements ConnectionManager {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				disposeReader(true, true);
 				return;
+			} catch (DbException e) {
+				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+				disposeReader(true, true);
+				return;
 			}
 			// Unrecognised tags are suspicious in this case
 			if (ctx == null) {
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index bd110d0cf652715543b0b4670adfa7e7fc65f1ba..367b1b66c5be99be31d6e20c930f5702bab09893 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -2,9 +2,7 @@ package org.briarproject.plugins;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.db.Transaction;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.TransportDisabledEvent;
 import org.briarproject.api.event.TransportEnabledEvent;
@@ -13,23 +11,21 @@ import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.Plugin;
 import org.briarproject.api.plugins.PluginCallback;
+import org.briarproject.api.plugins.PluginConfig;
 import org.briarproject.api.plugins.PluginManager;
 import org.briarproject.api.plugins.TransportConnectionReader;
 import org.briarproject.api.plugins.TransportConnectionWriter;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
 import org.briarproject.api.plugins.simplex.SimplexPlugin;
 import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
-import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
 import org.briarproject.api.properties.TransportProperties;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.settings.Settings;
 import org.briarproject.api.settings.SettingsManager;
-import org.briarproject.api.system.Clock;
 import org.briarproject.api.ui.UiCallback;
 
 import java.io.IOException;
@@ -56,10 +52,7 @@ class PluginManagerImpl implements PluginManager, Service {
 
 	private final Executor ioExecutor;
 	private final EventBus eventBus;
-	private final SimplexPluginConfig simplexPluginConfig;
-	private final DuplexPluginConfig duplexPluginConfig;
-	private final Clock clock;
-	private final DatabaseComponent db;
+	private final PluginConfig pluginConfig;
 	private final Poller poller;
 	private final ConnectionManager connectionManager;
 	private final SettingsManager settingsManager;
@@ -71,19 +64,14 @@ class PluginManagerImpl implements PluginManager, Service {
 
 	@Inject
 	PluginManagerImpl(@IoExecutor Executor ioExecutor, EventBus eventBus,
-			SimplexPluginConfig simplexPluginConfig,
-			DuplexPluginConfig duplexPluginConfig, Clock clock,
-			DatabaseComponent db, Poller poller,
+			PluginConfig pluginConfig, Poller poller,
 			ConnectionManager connectionManager,
 			SettingsManager settingsManager,
 			TransportPropertyManager transportPropertyManager,
 			UiCallback uiCallback) {
 		this.ioExecutor = ioExecutor;
 		this.eventBus = eventBus;
-		this.simplexPluginConfig = simplexPluginConfig;
-		this.duplexPluginConfig = duplexPluginConfig;
-		this.clock = clock;
-		this.db = db;
+		this.pluginConfig = pluginConfig;
 		this.poller = poller;
 		this.connectionManager = connectionManager;
 		this.settingsManager = settingsManager;
@@ -99,14 +87,14 @@ class PluginManagerImpl implements PluginManager, Service {
 		// Instantiate and start the simplex plugins
 		LOG.info("Starting simplex plugins");
 		Collection<SimplexPluginFactory> sFactories =
-				simplexPluginConfig.getFactories();
+				pluginConfig.getSimplexFactories();
 		final CountDownLatch sLatch = new CountDownLatch(sFactories.size());
 		for (SimplexPluginFactory factory : sFactories)
 			ioExecutor.execute(new SimplexPluginStarter(factory, sLatch));
 		// Instantiate and start the duplex plugins
 		LOG.info("Starting duplex plugins");
 		Collection<DuplexPluginFactory> dFactories =
-				duplexPluginConfig.getFactories();
+				pluginConfig.getDuplexFactories();
 		final CountDownLatch dLatch = new CountDownLatch(dFactories.size());
 		for (DuplexPluginFactory factory : dFactories)
 			ioExecutor.execute(new DuplexPluginStarter(factory, dLatch));
@@ -185,26 +173,9 @@ class PluginManagerImpl implements PluginManager, Service {
 					return;
 				}
 				try {
-					long start = clock.currentTimeMillis();
-					Transaction txn = db.startTransaction();
-					try {
-						db.addTransport(txn, id, plugin.getMaxLatency());
-						txn.setComplete();
-					} finally {
-						db.endTransaction(txn);
-					}
-					long duration = clock.currentTimeMillis() - start;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Adding transport took " + duration + " ms");
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					return;
-				}
-				try {
-					long start = clock.currentTimeMillis();
+					long start = System.currentTimeMillis();
 					boolean started = plugin.start();
-					long duration = clock.currentTimeMillis() - start;
+					long duration = System.currentTimeMillis() - start;
 					if (started) {
 						plugins.put(id, plugin);
 						simplexPlugins.add(plugin);
@@ -254,26 +225,9 @@ class PluginManagerImpl implements PluginManager, Service {
 					return;
 				}
 				try {
-					long start = clock.currentTimeMillis();
-					Transaction txn = db.startTransaction();
-					try {
-						db.addTransport(txn, id, plugin.getMaxLatency());
-						txn.setComplete();
-					} finally {
-						db.endTransaction(txn);
-					}
-					long duration = clock.currentTimeMillis() - start;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Adding transport took " + duration + " ms");
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					return;
-				}
-				try {
-					long start = clock.currentTimeMillis();
+					long start = System.currentTimeMillis();
 					boolean started = plugin.start();
-					long duration = clock.currentTimeMillis() - start;
+					long duration = System.currentTimeMillis() - start;
 					if (started) {
 						plugins.put(id, plugin);
 						duplexPlugins.add(plugin);
@@ -311,9 +265,9 @@ class PluginManagerImpl implements PluginManager, Service {
 
 		public void run() {
 			try {
-				long start = clock.currentTimeMillis();
+				long start = System.currentTimeMillis();
 				plugin.stop();
-				long duration = clock.currentTimeMillis() - start;
+				long duration = System.currentTimeMillis() - start;
 				if (LOG.isLoggable(INFO)) {
 					String name = plugin.getClass().getSimpleName();
 					LOG.info("Stopping " + name + " took " + duration + " ms");
diff --git a/briar-core/src/org/briarproject/plugins/PluginsModule.java b/briar-core/src/org/briarproject/plugins/PluginsModule.java
index 6f3357b8b5ac88229644c4a6eae3e38c7f283477..d106d8920f549b308a30006094e01818ec8e1313 100644
--- a/briar-core/src/org/briarproject/plugins/PluginsModule.java
+++ b/briar-core/src/org/briarproject/plugins/PluginsModule.java
@@ -1,8 +1,5 @@
 package org.briarproject.plugins;
 
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.LifecycleManager;
@@ -19,10 +16,12 @@ import org.briarproject.api.transport.StreamWriterFactory;
 import java.security.SecureRandom;
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 import dagger.Module;
 import dagger.Provides;
 
-
 @Module
 public class PluginsModule {
 
@@ -45,8 +44,8 @@ public class PluginsModule {
 
 	@Provides
 	ConnectionManager provideConnectionManager(
-			@IoExecutor Executor ioExecutor,
-			KeyManager keyManager, StreamReaderFactory streamReaderFactory,
+			@IoExecutor Executor ioExecutor, KeyManager keyManager,
+			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			SyncSessionFactory syncSessionFactory,
 			ConnectionRegistry connectionRegistry) {
@@ -61,7 +60,6 @@ public class PluginsModule {
 		return new ConnectionRegistryImpl(eventBus);
 	}
 
-
 	@Provides
 	@Singleton
 	PluginManager getPluginManager(LifecycleManager lifecycleManager,
@@ -69,5 +67,4 @@ public class PluginsModule {
 		lifecycleManager.register(pluginManager);
 		return pluginManager;
 	}
-
 }
diff --git a/briar-core/src/org/briarproject/plugins/file/FileTransportReader.java b/briar-core/src/org/briarproject/plugins/file/FileTransportReader.java
index 39ea14b3eab4bcc3c477bf7b1a0222fb42403244..ed6219797c7dd5d33629eee3bb25f79955a708bd 100644
--- a/briar-core/src/org/briarproject/plugins/file/FileTransportReader.java
+++ b/briar-core/src/org/briarproject/plugins/file/FileTransportReader.java
@@ -1,13 +1,13 @@
 package org.briarproject.plugins.file;
 
-import static java.util.logging.Level.WARNING;
+import org.briarproject.api.plugins.TransportConnectionReader;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.logging.Logger;
 
-import org.briarproject.api.plugins.TransportConnectionReader;
+import static java.util.logging.Level.WARNING;
 
 class FileTransportReader implements TransportConnectionReader {
 
@@ -24,10 +24,6 @@ class FileTransportReader implements TransportConnectionReader {
 		this.plugin = plugin;
 	}
 
-	public long getMaxLatency() {
-		return plugin.getMaxLatency();
-	}
-
 	public InputStream getInputStream() {
 		return in;
 	}
diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
index 929f63a6b849b80454e24c33f7783cdd765b0c5f..9b354427a240b81f05bb54aadade681ccdf3727b 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
@@ -30,6 +30,10 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
 		return LanTcpPlugin.ID;
 	}
 
+	public int getMaxLatency() {
+		return MAX_LATENCY;
+	}
+
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
 		Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
 				MAX_POLLING_INTERVAL, BACKOFF_BASE);
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpTransportConnection.java b/briar-core/src/org/briarproject/plugins/tcp/TcpTransportConnection.java
index 213cb88ce5d1cf81c5075089d691d8d23bf90e33..c400975bf3d44bb21ba87d97b174a7e292b728ac 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpTransportConnection.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpTransportConnection.java
@@ -1,16 +1,16 @@
 package org.briarproject.plugins.tcp;
 
+import org.briarproject.api.plugins.Plugin;
+import org.briarproject.api.plugins.TransportConnectionReader;
+import org.briarproject.api.plugins.TransportConnectionWriter;
+import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.briarproject.api.plugins.Plugin;
-import org.briarproject.api.plugins.TransportConnectionReader;
-import org.briarproject.api.plugins.TransportConnectionWriter;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-
 class TcpTransportConnection implements DuplexTransportConnection {
 
 	private final Plugin plugin;
@@ -38,10 +38,6 @@ class TcpTransportConnection implements DuplexTransportConnection {
 
 	private class Reader implements TransportConnectionReader {
 
-		public long getMaxLatency() {
-			return plugin.getMaxLatency();
-		}
-
 		public InputStream getInputStream() throws IOException {
 			return socket.getInputStream();
 		}
diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
index 659b79f1c0536b80105ea855caf6a63e6a3b48a1..44fdf5edfdf1ffe9b7b4c60c04678f6002dd6d4f 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
@@ -33,6 +33,10 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
 		return WanTcpPlugin.ID;
 	}
 
+	public int getMaxLatency() {
+		return MAX_LATENCY;
+	}
+
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
 		Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
 				MAX_POLLING_INTERVAL, BACKOFF_BASE);
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
index 2c90166526ae97810aca79d0e1d8d6cddb1e4e1e..9910ff42fe9facb2d1a0a5eebebff2cc89b1f6da 100644
--- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
@@ -84,7 +84,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 	@Override
 	public void addRemoteProperties(ContactId c, DeviceId dev,
 			Map<TransportId, TransportProperties> props) throws DbException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			Group g = getContactGroup(db.getContact(txn, c));
 			for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
@@ -101,7 +101,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 	public Map<TransportId, TransportProperties> getLocalProperties()
 			throws DbException {
 		Map<TransportId, TransportProperties> local;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			local = getLocalProperties(txn);
 			txn.setComplete();
@@ -116,7 +116,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 			throws DbException {
 		try {
 			TransportProperties p = null;
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(true);
 			try {
 				// Find the latest local update
 				LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
@@ -146,7 +146,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 		try {
 			Map<ContactId, TransportProperties> remote =
 					new HashMap<ContactId, TransportProperties>();
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(true);
 			try {
 				for (Contact c : db.getContacts(txn)) {
 					Group g = getContactGroup(c);
@@ -173,7 +173,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 	public void mergeLocalProperties(TransportId t, TransportProperties p)
 			throws DbException {
 		try {
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(false);
 			try {
 				// Create the local group if necessary
 				db.addGroup(txn, localGroup);
diff --git a/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java b/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java
index 4ff0ff2217cd9c0ec5a5e16bba6e2f121e9fca25..3df277318cb41650fc69ff5af2f15ee80e44cb2d 100644
--- a/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java
+++ b/briar-core/src/org/briarproject/settings/SettingsManagerImpl.java
@@ -21,7 +21,7 @@ class SettingsManagerImpl implements SettingsManager {
 	@Override
 	public Settings getSettings(String namespace) throws DbException {
 		Settings s;
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(true);
 		try {
 			s = db.getSettings(txn, namespace);
 			txn.setComplete();
@@ -33,7 +33,7 @@ class SettingsManagerImpl implements SettingsManager {
 
 	@Override
 	public void mergeSettings(Settings s, String namespace) throws DbException {
-		Transaction txn = db.startTransaction();
+		Transaction txn = db.startTransaction(false);
 		try {
 			db.mergeSettings(txn, s, namespace);
 			txn.setComplete();
diff --git a/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java b/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java
index 8976eb7d16be1f951098999468f57a810740341c..7c87c3151419ed36f9d7a0d95bc93d9663a79479 100644
--- a/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java
+++ b/briar-core/src/org/briarproject/sync/DuplexOutgoingSession.java
@@ -1,6 +1,5 @@
 package org.briarproject.sync;
 
-import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
@@ -15,7 +14,6 @@ import org.briarproject.api.event.MessageSharedEvent;
 import org.briarproject.api.event.MessageToAckEvent;
 import org.briarproject.api.event.MessageToRequestEvent;
 import org.briarproject.api.event.ShutdownEvent;
-import org.briarproject.api.event.TransportRemovedEvent;
 import org.briarproject.api.sync.Ack;
 import org.briarproject.api.sync.Offer;
 import org.briarproject.api.sync.PacketWriter;
@@ -59,7 +57,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 	private final EventBus eventBus;
 	private final Clock clock;
 	private final ContactId contactId;
-	private final TransportId transportId;
 	private final int maxLatency, maxIdleTime;
 	private final PacketWriter packetWriter;
 	private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
@@ -67,15 +64,13 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 	private volatile boolean interrupted = false;
 
 	DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
-			EventBus eventBus, Clock clock, ContactId contactId,
-			TransportId transportId, int maxLatency, int maxIdleTime,
-			PacketWriter packetWriter) {
+			EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
+			int maxIdleTime, PacketWriter packetWriter) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
 		this.clock = clock;
 		this.contactId = contactId;
-		this.transportId = transportId;
 		this.maxLatency = maxLatency;
 		this.maxIdleTime = maxIdleTime;
 		this.packetWriter = packetWriter;
@@ -167,9 +162,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 				dbExecutor.execute(new GenerateRequest());
 		} else if (e instanceof ShutdownEvent) {
 			interrupt();
-		} else if (e instanceof TransportRemovedEvent) {
-			TransportRemovedEvent t = (TransportRemovedEvent) e;
-			if (t.getTransportId().equals(transportId)) interrupt();
 		}
 	}
 
@@ -180,7 +172,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 			if (interrupted) return;
 			try {
 				Ack a;
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					a = db.generateAck(txn, contactId, MAX_MESSAGE_IDS);
 					txn.setComplete();
@@ -221,7 +213,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 			if (interrupted) return;
 			try {
 				Collection<byte[]> b;
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					b = db.generateRequestedBatch(txn, contactId,
 							MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
@@ -263,7 +255,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 			if (interrupted) return;
 			try {
 				Offer o;
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
 							maxLatency);
@@ -305,7 +297,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
 			if (interrupted) return;
 			try {
 				Request r;
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					r = db.generateRequest(txn, contactId, MAX_MESSAGE_IDS);
 					txn.setComplete();
diff --git a/briar-core/src/org/briarproject/sync/IncomingSession.java b/briar-core/src/org/briarproject/sync/IncomingSession.java
index 5e5f4c6df220bcfb23ef55b6417d07eb8f79ed65..2a06d29cb0e18a70aa4a230b68223b0fb5b984e9 100644
--- a/briar-core/src/org/briarproject/sync/IncomingSession.java
+++ b/briar-core/src/org/briarproject/sync/IncomingSession.java
@@ -1,7 +1,6 @@
 package org.briarproject.sync;
 
 import org.briarproject.api.FormatException;
-import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
@@ -11,7 +10,6 @@ import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.ShutdownEvent;
-import org.briarproject.api.event.TransportRemovedEvent;
 import org.briarproject.api.sync.Ack;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.Offer;
@@ -37,19 +35,17 @@ class IncomingSession implements SyncSession, EventListener {
 	private final Executor dbExecutor;
 	private final EventBus eventBus;
 	private final ContactId contactId;
-	private final TransportId transportId;
 	private final PacketReader packetReader;
 
 	private volatile boolean interrupted = false;
 
 	IncomingSession(DatabaseComponent db, Executor dbExecutor,
-			EventBus eventBus, ContactId contactId, TransportId transportId,
+			EventBus eventBus, ContactId contactId,
 			PacketReader packetReader) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
 		this.contactId = contactId;
-		this.transportId = transportId;
 		this.packetReader = packetReader;
 	}
 
@@ -90,9 +86,6 @@ class IncomingSession implements SyncSession, EventListener {
 			if (c.getContactId().equals(contactId)) interrupt();
 		} else if (e instanceof ShutdownEvent) {
 			interrupt();
-		} else if (e instanceof TransportRemovedEvent) {
-			TransportRemovedEvent t = (TransportRemovedEvent) e;
-			if (t.getTransportId().equals(transportId)) interrupt();
 		}
 	}
 
@@ -106,7 +99,7 @@ class IncomingSession implements SyncSession, EventListener {
 
 		public void run() {
 			try {
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					db.receiveAck(txn, contactId, ack);
 					txn.setComplete();
@@ -130,7 +123,7 @@ class IncomingSession implements SyncSession, EventListener {
 
 		public void run() {
 			try {
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					db.receiveMessage(txn, contactId, message);
 					txn.setComplete();
@@ -154,7 +147,7 @@ class IncomingSession implements SyncSession, EventListener {
 
 		public void run() {
 			try {
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					db.receiveOffer(txn, contactId, offer);
 					txn.setComplete();
@@ -178,7 +171,7 @@ class IncomingSession implements SyncSession, EventListener {
 
 		public void run() {
 			try {
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					db.receiveRequest(txn, contactId, request);
 					txn.setComplete();
diff --git a/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java b/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java
index 31ca6268485c4f18b2de9f1c469e1a579287120d..5623c275e70d923e6be402fb3337c4dd7e2cc120 100644
--- a/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java
+++ b/briar-core/src/org/briarproject/sync/SimplexOutgoingSession.java
@@ -1,6 +1,5 @@
 package org.briarproject.sync;
 
-import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
@@ -10,7 +9,6 @@ import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.ShutdownEvent;
-import org.briarproject.api.event.TransportRemovedEvent;
 import org.briarproject.api.sync.Ack;
 import org.briarproject.api.sync.PacketWriter;
 import org.briarproject.api.sync.SyncSession;
@@ -48,7 +46,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 	private final Executor dbExecutor;
 	private final EventBus eventBus;
 	private final ContactId contactId;
-	private final TransportId transportId;
 	private final int maxLatency;
 	private final PacketWriter packetWriter;
 	private final AtomicInteger outstandingQueries;
@@ -57,13 +54,12 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 	private volatile boolean interrupted = false;
 
 	SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
-			EventBus eventBus, ContactId contactId, TransportId transportId,
+			EventBus eventBus, ContactId contactId,
 			int maxLatency, PacketWriter packetWriter) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
 		this.contactId = contactId;
-		this.transportId = transportId;
 		this.maxLatency = maxLatency;
 		this.packetWriter = packetWriter;
 		outstandingQueries = new AtomicInteger(2); // One per type of packet
@@ -108,9 +104,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 			if (c.getContactId().equals(contactId)) interrupt();
 		} else if (e instanceof ShutdownEvent) {
 			interrupt();
-		} else if (e instanceof TransportRemovedEvent) {
-			TransportRemovedEvent t = (TransportRemovedEvent) e;
-			if (t.getTransportId().equals(transportId)) interrupt();
 		}
 	}
 
@@ -121,7 +114,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 			if (interrupted) return;
 			try {
 				Ack a;
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					a = db.generateAck(txn, contactId, MAX_MESSAGE_IDS);
 					txn.setComplete();
@@ -163,7 +156,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
 			if (interrupted) return;
 			try {
 				Collection<byte[]> b;
-				Transaction txn = db.startTransaction();
+				Transaction txn = db.startTransaction(false);
 				try {
 					b = db.generateBatch(txn, contactId,
 							MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
diff --git a/briar-core/src/org/briarproject/sync/SyncSessionFactoryImpl.java b/briar-core/src/org/briarproject/sync/SyncSessionFactoryImpl.java
index 5a4021b1d41ba409a1bc2394edbcad0dc0043f24..6fac0012f6acfcad59e8790844d9a6fa06523d4c 100644
--- a/briar-core/src/org/briarproject/sync/SyncSessionFactoryImpl.java
+++ b/briar-core/src/org/briarproject/sync/SyncSessionFactoryImpl.java
@@ -1,6 +1,5 @@
 package org.briarproject.sync;
 
-import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DatabaseExecutor;
@@ -41,24 +40,22 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
 		this.packetWriterFactory = packetWriterFactory;
 	}
 
-	public SyncSession createIncomingSession(ContactId c, TransportId t,
-			InputStream in) {
+	public SyncSession createIncomingSession(ContactId c, InputStream in) {
 		PacketReader packetReader = packetReaderFactory.createPacketReader(in);
-		return new IncomingSession(db, dbExecutor, eventBus, c, t,
-				packetReader);
+		return new IncomingSession(db, dbExecutor, eventBus, c, packetReader);
 	}
 
-	public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
+	public SyncSession createSimplexOutgoingSession(ContactId c,
 			int maxLatency, OutputStream out) {
 		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
-		return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
+		return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
 				maxLatency, packetWriter);
 	}
 
-	public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
-			int maxLatency, int maxIdleTime, OutputStream out) {
+	public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
+			int maxIdleTime, OutputStream out) {
 		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
-		return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, t,
+		return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
 				maxLatency, maxIdleTime, packetWriter);
 	}
 }
diff --git a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java
index 4837a1b57a813175fefbd9a24b559d1ab3616efb..89c35459af995d1758670f8747de534a4a21373b 100644
--- a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java
+++ b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java
@@ -83,7 +83,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
 			public void run() {
 				try {
 					Queue<MessageId> unvalidated = new LinkedList<MessageId>();
-					Transaction txn = db.startTransaction();
+					Transaction txn = db.startTransaction(true);
 					try {
 						unvalidated.addAll(db.getMessagesToValidate(txn, c));
 						txn.setComplete();
@@ -106,7 +106,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
 				try {
 					Message m = null;
 					Group g = null;
-					Transaction txn = db.startTransaction();
+					Transaction txn = db.startTransaction(true);
 					try {
 						MessageId id = unvalidated.poll();
 						byte[] raw = db.getRawMessage(txn, id);
@@ -160,7 +160,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
 		dbExecutor.execute(new Runnable() {
 			public void run() {
 				try {
-					Transaction txn = db.startTransaction();
+					Transaction txn = db.startTransaction(false);
 					try {
 						if (meta == null) {
 							db.setMessageValid(txn, m, c, false);
@@ -198,7 +198,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
 			public void run() {
 				try {
 					Group g;
-					Transaction txn = db.startTransaction();
+					Transaction txn = db.startTransaction(true);
 					try {
 						g = db.getGroup(txn, m.getGroupId());
 						txn.setComplete();
diff --git a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
index 3cbfef824f96f241b75389fab118ab25114c9731..32a6ee3aada1dd7cb4f8b2a03b1382dc7b622765 100644
--- a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
+++ b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
@@ -13,15 +13,16 @@ import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.ContactStatusChangedEvent;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
-import org.briarproject.api.event.TransportAddedEvent;
-import org.briarproject.api.event.TransportRemovedEvent;
 import org.briarproject.api.lifecycle.Service;
+import org.briarproject.api.plugins.PluginConfig;
+import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
+import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.system.Timer;
 import org.briarproject.api.transport.KeyManager;
 import org.briarproject.api.transport.StreamContext;
 
-import java.util.Collection;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
@@ -30,6 +31,7 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
+import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
 class KeyManagerImpl implements KeyManager, Service, EventListener {
@@ -40,6 +42,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 	private final DatabaseComponent db;
 	private final CryptoComponent crypto;
 	private final ExecutorService dbExecutor;
+	private final PluginConfig pluginConfig;
 	private final Timer timer;
 	private final Clock clock;
 	private final Map<ContactId, Boolean> activeContacts;
@@ -47,11 +50,12 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 
 	@Inject
 	KeyManagerImpl(DatabaseComponent db, CryptoComponent crypto,
-			@DatabaseExecutor ExecutorService dbExecutor, Timer timer,
-			Clock clock) {
+			@DatabaseExecutor ExecutorService dbExecutor,
+			PluginConfig pluginConfig, Timer timer, Clock clock) {
 		this.db = db;
 		this.crypto = crypto;
 		this.dbExecutor = dbExecutor;
+		this.pluginConfig = pluginConfig;
 		this.timer = timer;
 		this.clock = clock;
 		// Use a ConcurrentHashMap as a thread-safe set
@@ -61,21 +65,29 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 
 	@Override
 	public boolean start() {
+		Map<TransportId, Integer> transports =
+				new HashMap<TransportId, Integer>();
+		for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
+			transports.put(f.getId(), f.getMaxLatency());
+		for (DuplexPluginFactory f : pluginConfig.getDuplexFactories())
+			transports.put(f.getId(), f.getMaxLatency());
 		try {
-			Collection<Contact> contacts;
-			Map<TransportId, Integer> latencies;
-			Transaction txn = db.startTransaction();
+			Transaction txn = db.startTransaction(false);
 			try {
-				contacts = db.getContacts(txn);
-				latencies = db.getTransportLatencies(txn);
+				for (Contact c : db.getContacts(txn))
+					if (c.isActive()) activeContacts.put(c.getId(), true);
+				for (Entry<TransportId, Integer> e : transports.entrySet())
+					db.addTransport(txn, e.getKey(), e.getValue());
+				for (Entry<TransportId, Integer> e : transports.entrySet()) {
+					TransportKeyManager m = new TransportKeyManager(db, crypto,
+							timer, clock, e.getKey(), e.getValue());
+					managers.put(e.getKey(), m);
+					m.start(txn);
+				}
 				txn.setComplete();
 			} finally {
 				db.endTransaction(txn);
 			}
-			for (Contact c : contacts)
-				if (c.isActive()) activeContacts.put(c.getId(), true);
-			for (Entry<TransportId, Integer> e : latencies.entrySet())
-				addTransport(e.getKey(), e.getValue());
 		} catch (DbException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			return false;
@@ -94,43 +106,49 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 			m.addContact(txn, c, master, timestamp, alice);
 	}
 
-	public StreamContext getStreamContext(ContactId c, TransportId t) {
+	public StreamContext getStreamContext(ContactId c, TransportId t)
+			throws DbException {
 		// Don't allow outgoing streams to inactive contacts
 		if (!activeContacts.containsKey(c)) return null;
 		TransportKeyManager m = managers.get(t);
-		return m == null ? null : m.getStreamContext(c);
+		if (m == null) {
+			if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
+			return null;
+		}
+		StreamContext ctx = null;
+		Transaction txn = db.startTransaction(false);
+		try {
+			ctx = m.getStreamContext(txn, c);
+			txn.setComplete();
+		} finally {
+			db.endTransaction(txn);
+		}
+		return ctx;
 	}
 
-	public StreamContext getStreamContext(TransportId t, byte[] tag) {
+	public StreamContext getStreamContext(TransportId t, byte[] tag)
+			throws DbException {
 		TransportKeyManager m = managers.get(t);
-		if (m == null) return null;
-		StreamContext ctx = m.getStreamContext(tag);
-		if (ctx == null) return null;
-		// Activate the contact if not already active
-		if (!activeContacts.containsKey(ctx.getContactId())) {
-			try {
-				Transaction txn = db.startTransaction();
-				try {
-					db.setContactActive(txn, ctx.getContactId(), true);
-					txn.setComplete();
-				} finally {
-					db.endTransaction(txn);
-				}
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				return null;
-			}
+		if (m == null) {
+			if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
+			return null;
+		}
+		StreamContext ctx = null;
+		Transaction txn = db.startTransaction(false);
+		try {
+			ctx = m.getStreamContext(txn, tag);
+			// Activate the contact if not already active
+			if (ctx != null && !activeContacts.containsKey(ctx.getContactId()))
+				db.setContactActive(txn, ctx.getContactId(), true);
+			txn.setComplete();
+		} finally {
+			db.endTransaction(txn);
 		}
 		return ctx;
 	}
 
 	public void eventOccurred(Event e) {
-		if (e instanceof TransportAddedEvent) {
-			TransportAddedEvent t = (TransportAddedEvent) e;
-			addTransport(t.getTransportId(), t.getMaxLatency());
-		} else if (e instanceof TransportRemovedEvent) {
-			removeTransport(((TransportRemovedEvent) e).getTransportId());
-		} else if (e instanceof ContactRemovedEvent) {
+		if (e instanceof ContactRemovedEvent) {
 			removeContact(((ContactRemovedEvent) e).getContactId());
 		} else if (e instanceof ContactStatusChangedEvent) {
 			ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
@@ -139,21 +157,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 		}
 	}
 
-	private void addTransport(final TransportId t, final int maxLatency) {
-		dbExecutor.execute(new Runnable() {
-			public void run() {
-				TransportKeyManager m = new TransportKeyManager(db, crypto,
-						timer, clock, t, maxLatency);
-				// Don't add transport twice if event is received during startup
-				if (managers.putIfAbsent(t, m) == null) m.start();
-			}
-		});
-	}
-
-	private void removeTransport(TransportId t) {
-		managers.remove(t);
-	}
-
 	private void removeContact(final ContactId c) {
 		activeContacts.remove(c);
 		dbExecutor.execute(new Runnable() {
diff --git a/briar-core/src/org/briarproject/transport/TransportKeyManager.java b/briar-core/src/org/briarproject/transport/TransportKeyManager.java
index b8bcdfb3f99693971e0a5c37e2c991f9091d3111..0c9b95bf4679e5d6bb09f95a8b733b83dc1b605c 100644
--- a/briar-core/src/org/briarproject/transport/TransportKeyManager.java
+++ b/briar-core/src/org/briarproject/transport/TransportKeyManager.java
@@ -60,46 +60,20 @@ class TransportKeyManager {
 		keys = new HashMap<ContactId, MutableTransportKeys>();
 	}
 
-	void start() {
+	void start(Transaction txn) throws DbException {
 		long now = clock.currentTimeMillis();
 		lock.lock();
 		try {
 			// Load the transport keys from the DB
-			Map<ContactId, TransportKeys> loaded;
-			try {
-				Transaction txn = db.startTransaction();
-				try {
-					loaded = db.getTransportKeys(txn, transportId);
-					txn.setComplete();
-				} finally {
-					db.endTransaction(txn);
-				}
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				return;
-			}
+			Map<ContactId, TransportKeys> loaded =
+					db.getTransportKeys(txn, transportId);
 			// Rotate the keys to the current rotation period
-			Map<ContactId, TransportKeys> rotated =
-					new HashMap<ContactId, TransportKeys>();
-			Map<ContactId, TransportKeys> current =
-					new HashMap<ContactId, TransportKeys>();
-			long rotationPeriod = now / rotationPeriodLength;
-			for (Entry<ContactId, TransportKeys> e : loaded.entrySet()) {
-				ContactId c = e.getKey();
-				TransportKeys k = e.getValue();
-				TransportKeys k1 = crypto.rotateTransportKeys(k,
-						rotationPeriod);
-				if (k1.getRotationPeriod() > k.getRotationPeriod())
-					rotated.put(c, k1);
-				current.put(c, k1);
-			}
+			RotationResult rotationResult = rotateKeys(loaded, now);
 			// Initialise mutable state for all contacts
-			for (Entry<ContactId, TransportKeys> e : current.entrySet())
-				addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
+			addKeys(rotationResult.current);
 			// Write any rotated keys back to the DB
-			updateTransportKeys(rotated);
-		} catch (DbException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			if (!rotationResult.rotated.isEmpty())
+				db.updateTransportKeys(txn, rotationResult.rotated);
 		} finally {
 			lock.unlock();
 		}
@@ -107,6 +81,27 @@ class TransportKeyManager {
 		scheduleKeyRotation(now);
 	}
 
+	private RotationResult rotateKeys(Map<ContactId, TransportKeys> keys,
+			long now) {
+		RotationResult rotationResult = new RotationResult();
+		long rotationPeriod = now / rotationPeriodLength;
+		for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
+			ContactId c = e.getKey();
+			TransportKeys k = e.getValue();
+			TransportKeys k1 = crypto.rotateTransportKeys(k, rotationPeriod);
+			if (k1.getRotationPeriod() > k.getRotationPeriod())
+				rotationResult.rotated.put(c, k1);
+			rotationResult.current.put(c, k1);
+		}
+		return rotationResult;
+	}
+
+	// Locking: lock
+	private void addKeys(Map<ContactId, TransportKeys> m) {
+		for (Entry<ContactId, TransportKeys> e : m.entrySet())
+			addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
+	}
+
 	// Locking: lock
 	private void addKeys(ContactId c, MutableTransportKeys m) {
 		encodeTags(c, m.getPreviousIncomingKeys());
@@ -126,23 +121,21 @@ class TransportKeyManager {
 		}
 	}
 
-	private void updateTransportKeys(Map<ContactId, TransportKeys> rotated)
-		throws DbException {
-		if (!rotated.isEmpty()) {
-			Transaction txn = db.startTransaction();
-			try {
-				db.updateTransportKeys(txn, rotated);
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
-		}
-	}
-
 	private void scheduleKeyRotation(long now) {
 		TimerTask task = new TimerTask() {
 			public void run() {
-				rotateKeys();
+				try {
+					Transaction txn = db.startTransaction(false);
+					try {
+						rotateKeys(txn);
+						txn.setComplete();
+					} finally {
+						db.endTransaction(txn);
+					}
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
 			}
 		};
 		long delay = rotationPeriodLength - now % rotationPeriodLength;
@@ -185,7 +178,8 @@ class TransportKeyManager {
 		}
 	}
 
-	StreamContext getStreamContext(ContactId c) {
+	StreamContext getStreamContext(Transaction txn, ContactId c)
+			throws DbException {
 		lock.lock();
 		try {
 			// Look up the outgoing keys for the contact
@@ -198,24 +192,16 @@ class TransportKeyManager {
 					outKeys.getStreamCounter());
 			// Increment the stream counter and write it back to the DB
 			outKeys.incrementStreamCounter();
-			Transaction txn = db.startTransaction();
-			try {
-				db.incrementStreamCounter(txn, c, transportId,
-						outKeys.getRotationPeriod());
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
+			db.incrementStreamCounter(txn, c, transportId,
+					outKeys.getRotationPeriod());
 			return ctx;
-		} catch (DbException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
 		} finally {
 			lock.unlock();
 		}
 	}
 
-	StreamContext getStreamContext(byte[] tag) {
+	StreamContext getStreamContext(Transaction txn, byte[] tag)
+			throws DbException {
 		lock.lock();
 		try {
 			// Look up the incoming keys for the tag
@@ -244,53 +230,33 @@ class TransportKeyManager {
 				inContexts.remove(new Bytes(removeTag));
 			}
 			// Write the window back to the DB
-			Transaction txn = db.startTransaction();
-			try {
-				db.setReorderingWindow(txn, tagCtx.contactId, transportId,
-						inKeys.getRotationPeriod(), window.getBase(),
-						window.getBitmap());
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
+			db.setReorderingWindow(txn, tagCtx.contactId, transportId,
+					inKeys.getRotationPeriod(), window.getBase(),
+					window.getBitmap());
 			return ctx;
-		} catch (DbException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-			return null;
 		} finally {
 			lock.unlock();
 		}
 	}
 
-	private void rotateKeys() {
+	private void rotateKeys(Transaction txn) throws DbException {
 		long now = clock.currentTimeMillis();
 		lock.lock();
 		try {
 			// Rotate the keys to the current rotation period
-			Map<ContactId, TransportKeys> rotated =
-					new HashMap<ContactId, TransportKeys>();
-			Map<ContactId, TransportKeys> current =
+			Map<ContactId, TransportKeys> snapshot =
 					new HashMap<ContactId, TransportKeys>();
-			long rotationPeriod = now / rotationPeriodLength;
-			for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet()) {
-				ContactId c = e.getKey();
-				TransportKeys k = e.getValue().snapshot();
-				TransportKeys k1 = crypto.rotateTransportKeys(k,
-						rotationPeriod);
-				if (k1.getRotationPeriod() > k.getRotationPeriod())
-					rotated.put(c, k1);
-				current.put(c, k1);
-			}
+			for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet())
+				snapshot.put(e.getKey(), e.getValue().snapshot());
+			RotationResult rotationResult = rotateKeys(snapshot, now);
 			// Rebuild the mutable state for all contacts
 			inContexts.clear();
 			outContexts.clear();
 			keys.clear();
-			for (Entry<ContactId, TransportKeys> e : current.entrySet())
-				addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
+			addKeys(rotationResult.current);
 			// Write any rotated keys back to the DB
-			updateTransportKeys(rotated);
-		} catch (DbException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			if (!rotationResult.rotated.isEmpty())
+				db.updateTransportKeys(txn, rotationResult.rotated);
 		} finally {
 			lock.unlock();
 		}
@@ -311,4 +277,14 @@ class TransportKeyManager {
 			this.streamNumber = streamNumber;
 		}
 	}
+
+	private static class RotationResult {
+
+		private final Map<ContactId, TransportKeys> current, rotated;
+
+		private RotationResult() {
+			current = new HashMap<ContactId, TransportKeys>();
+			rotated = new HashMap<ContactId, TransportKeys>();
+		}
+	}
 }
diff --git a/briar-desktop/src/org/briarproject/lifecycle/DesktopLifecycleModule.java b/briar-desktop/src/org/briarproject/lifecycle/DesktopLifecycleModule.java
index 4986668b1e5f25af228994f5c0f14824c391bfaa..79525935a0123371418ae744006f41563c3b2394 100644
--- a/briar-desktop/src/org/briarproject/lifecycle/DesktopLifecycleModule.java
+++ b/briar-desktop/src/org/briarproject/lifecycle/DesktopLifecycleModule.java
@@ -1,10 +1,6 @@
 package org.briarproject.lifecycle;
 
-import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.ShutdownManager;
-import org.briarproject.api.system.Clock;
 import org.briarproject.util.OsUtils;
 
 import javax.inject.Singleton;
@@ -15,22 +11,10 @@ import dagger.Provides;
 @Module
 public class DesktopLifecycleModule extends LifecycleModule {
 
-	@Provides
-	@Singleton
-	LifecycleManager provideLifecycleManager(Clock clock, DatabaseComponent db,
-			EventBus eventBus) {
-		return new LifecycleManagerImpl(clock, db, eventBus);
-	}
-
 	@Provides
 	@Singleton
 	ShutdownManager provideDesktopShutdownManager() {
-		if (OsUtils.isWindows()) {
-			return new WindowsShutdownManagerImpl();
-		}
-		else {
-			return new ShutdownManagerImpl();
-		}
+		if (OsUtils.isWindows()) return new WindowsShutdownManagerImpl();
+		else return new ShutdownManagerImpl();
 	}
-
 }
diff --git a/briar-desktop/src/org/briarproject/plugins/DesktopPluginsModule.java b/briar-desktop/src/org/briarproject/plugins/DesktopPluginsModule.java
index 6d727a6d89786c273f7ee9fd58c1abf1837bb65d..912a0a7d7a1d4950f972acef29779b5e4b6f7258 100644
--- a/briar-desktop/src/org/briarproject/plugins/DesktopPluginsModule.java
+++ b/briar-desktop/src/org/briarproject/plugins/DesktopPluginsModule.java
@@ -3,9 +3,8 @@ package org.briarproject.plugins;
 import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.ShutdownManager;
 import org.briarproject.api.plugins.BackoffFactory;
-import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
+import org.briarproject.api.plugins.PluginConfig;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
-import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
 import org.briarproject.api.reliability.ReliabilityLayerFactory;
 import org.briarproject.plugins.bluetooth.BluetoothPluginFactory;
@@ -27,21 +26,7 @@ import dagger.Provides;
 public class DesktopPluginsModule extends PluginsModule {
 
 	@Provides
-	SimplexPluginConfig getSimplexPluginConfig(
-			@IoExecutor Executor ioExecutor) {
-		SimplexPluginFactory removable =
-				new RemovableDrivePluginFactory(ioExecutor);
-		final Collection<SimplexPluginFactory> factories =
-				Collections.singletonList(removable);
-		return new SimplexPluginConfig() {
-			public Collection<SimplexPluginFactory> getFactories() {
-				return factories;
-			}
-		};
-	}
-
-	@Provides
-	DuplexPluginConfig getDuplexPluginConfig(@IoExecutor Executor ioExecutor,
+	PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
 			SecureRandom random, BackoffFactory backoffFactory,
 			ReliabilityLayerFactory reliabilityFactory,
 			ShutdownManager shutdownManager) {
@@ -53,11 +38,22 @@ public class DesktopPluginsModule extends PluginsModule {
 				backoffFactory);
 		DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor,
 				backoffFactory, shutdownManager);
-		final Collection<DuplexPluginFactory> factories =
+		SimplexPluginFactory removable =
+				new RemovableDrivePluginFactory(ioExecutor);
+		final Collection<SimplexPluginFactory> simplex =
+				Collections.singletonList(removable);
+		final Collection<DuplexPluginFactory> duplex =
 				Arrays.asList(bluetooth, modem, lan, wan);
-		return new DuplexPluginConfig() {
-			public Collection<DuplexPluginFactory> getFactories() {
-				return factories;
+		return new PluginConfig() {
+
+			@Override
+			public Collection<DuplexPluginFactory> getDuplexFactories() {
+				return duplex;
+			}
+
+			@Override
+			public Collection<SimplexPluginFactory> getSimplexFactories() {
+				return simplex;
 			}
 		};
 	}
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
index 2d7f156e22d861f777808cc98ba0fbbb69224d13..d4165bc410fbbe3ba5f6a1ff71e55a388bf8945d 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
@@ -32,6 +32,10 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
 		return BluetoothPlugin.ID;
 	}
 
+	public int getMaxLatency() {
+		return MAX_LATENCY;
+	}
+
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
 		Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
 				MAX_POLLING_INTERVAL, BACKOFF_BASE);
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothTransportConnection.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothTransportConnection.java
index 8f852f1ef820c87244e36f2f8b15df4f96a33e03..6139b8c14bba8b749537ed1e6533526cfaee6113 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothTransportConnection.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothTransportConnection.java
@@ -1,5 +1,10 @@
 package org.briarproject.plugins.bluetooth;
 
+import org.briarproject.api.plugins.Plugin;
+import org.briarproject.api.plugins.TransportConnectionReader;
+import org.briarproject.api.plugins.TransportConnectionWriter;
+import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -7,11 +12,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.microedition.io.StreamConnection;
 
-import org.briarproject.api.plugins.Plugin;
-import org.briarproject.api.plugins.TransportConnectionReader;
-import org.briarproject.api.plugins.TransportConnectionWriter;
-import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-
 class BluetoothTransportConnection implements DuplexTransportConnection {
 
 	private final Plugin plugin;
@@ -39,10 +39,6 @@ class BluetoothTransportConnection implements DuplexTransportConnection {
 
 	private class Reader implements TransportConnectionReader {
 
-		public long getMaxLatency() {
-			return plugin.getMaxLatency();
-		}
-
 		public InputStream getInputStream() throws IOException {
 			return stream.openInputStream();
 		}
diff --git a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
index f4ed1e348121584b22dcbc8f6ac21f693045608a..e2db0717d77aafdeade20519f45dbe55e8afb840 100644
--- a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
@@ -24,6 +24,10 @@ public class RemovableDrivePluginFactory implements SimplexPluginFactory {
 		return RemovableDrivePlugin.ID;
 	}
 
+	public int getMaxLatency() {
+		return MAX_LATENCY;
+	}
+
 	public SimplexPlugin createPlugin(SimplexPluginCallback callback) {
 		RemovableDriveFinder finder;
 		RemovableDriveMonitor monitor;
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
index 954529a5e8221fa1341f7b0a7b198409e33efa19..ffa06c2a477689045735b9c1aa5cd28ebd04ae07 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
@@ -194,10 +194,6 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 
 		private class Reader implements TransportConnectionReader {
 
-			public long getMaxLatency() {
-				return ModemPlugin.this.getMaxLatency();
-			}
-
 			public InputStream getInputStream() throws IOException {
 				return modem.getInputStream();
 			}
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
index 2e9561c077fa55e332c7fd4d9d24de706aed6ba0..690b3c7d78f6f603a400bd690a1c92464b263215 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
@@ -1,7 +1,5 @@
 package org.briarproject.plugins.modem;
 
-import java.util.concurrent.Executor;
-
 import org.briarproject.api.TransportId;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
@@ -9,6 +7,8 @@ import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
 import org.briarproject.api.reliability.ReliabilityLayerFactory;
 import org.briarproject.util.StringUtils;
 
+import java.util.concurrent.Executor;
+
 public class ModemPluginFactory implements DuplexPluginFactory {
 
 	private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
@@ -26,6 +26,10 @@ public class ModemPluginFactory implements DuplexPluginFactory {
 		return ModemPlugin.ID;
 	}
 
+	public int getMaxLatency() {
+		return MAX_LATENCY;
+	}
+
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
 		// This plugin is not enabled by default
 		String enabled = callback.getSettings().get("enabled");
diff --git a/briar-tests/src/org/briarproject/TestPluginsModule.java b/briar-tests/src/org/briarproject/TestPluginsModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..b7cf4209ee34199b051b1bc2e61c95d308291c43
--- /dev/null
+++ b/briar-tests/src/org/briarproject/TestPluginsModule.java
@@ -0,0 +1,55 @@
+package org.briarproject;
+
+import org.briarproject.api.TransportId;
+import org.briarproject.api.plugins.PluginConfig;
+import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
+import org.briarproject.api.plugins.simplex.SimplexPlugin;
+import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
+import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class TestPluginsModule {
+
+	public static final TransportId TRANSPORT_ID = new TransportId("id");
+	public static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
+
+	private final SimplexPluginFactory simplex = new SimplexPluginFactory() {
+
+		@Override
+		public TransportId getId() {
+			return TRANSPORT_ID;
+		}
+
+		@Override
+		public int getMaxLatency() {
+			return MAX_LATENCY;
+		}
+
+		@Override
+		public SimplexPlugin createPlugin(SimplexPluginCallback callback) {
+			return null;
+		}
+	};
+
+	@Provides
+	PluginConfig providePluginConfig() {
+		return new PluginConfig() {
+
+			@Override
+			public Collection<DuplexPluginFactory> getDuplexFactories() {
+				return Collections.emptyList();
+			}
+
+			@Override
+			public Collection<SimplexPluginFactory> getSimplexFactories() {
+				return Collections.singletonList(simplex);
+			}
+		};
+	}
+}
diff --git a/briar-tests/src/org/briarproject/clients/MessageQueueManagerImplTest.java b/briar-tests/src/org/briarproject/clients/MessageQueueManagerImplTest.java
index 3cbed06cb9c4ab6835d62355ca739d1cf54bfdff..cf816fd88e4614430de2bc34a99a56a08b6918b0 100644
--- a/briar-tests/src/org/briarproject/clients/MessageQueueManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/clients/MessageQueueManagerImplTest.java
@@ -56,7 +56,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
 				context.mock(QueueMessageFactory.class);
 		final ValidationManager validationManager =
 				context.mock(ValidationManager.class);
-		final Transaction txn = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
 		final byte[] body = new byte[123];
 		final Metadata groupMetadata = new Metadata();
 		final Metadata messageMetadata = new Metadata();
@@ -249,7 +249,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
 				new AtomicReference<IncomingMessageHook>();
 		final IncomingQueueMessageHook incomingQueueMessageHook =
 				context.mock(IncomingQueueMessageHook.class);
-		final Transaction txn = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
 		final Metadata groupMetadata = new Metadata();
 		final byte[] queueState = new byte[123];
 		groupMetadata.put(QUEUE_STATE_KEY, queueState);
@@ -300,7 +300,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
 				new AtomicReference<IncomingMessageHook>();
 		final IncomingQueueMessageHook incomingQueueMessageHook =
 				context.mock(IncomingQueueMessageHook.class);
-		final Transaction txn = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
 		final Metadata groupMetadata = new Metadata();
 		final byte[] queueState = new byte[123];
 		groupMetadata.put(QUEUE_STATE_KEY, queueState);
@@ -355,7 +355,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
 				new AtomicReference<IncomingMessageHook>();
 		final IncomingQueueMessageHook incomingQueueMessageHook =
 				context.mock(IncomingQueueMessageHook.class);
-		final Transaction txn = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
 		final Metadata groupMetadata = new Metadata();
 		final byte[] queueState = new byte[123];
 		groupMetadata.put(QUEUE_STATE_KEY, queueState);
@@ -412,7 +412,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
 				new AtomicReference<IncomingMessageHook>();
 		final IncomingQueueMessageHook incomingQueueMessageHook =
 				context.mock(IncomingQueueMessageHook.class);
-		final Transaction txn = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
 		final Metadata groupMetadata = new Metadata();
 		final byte[] queueState = new byte[123];
 		groupMetadata.put(QUEUE_STATE_KEY, queueState);
diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
index 4049a4091db098696bf672b5b4a6fdffbb343f79..02531d08d1165f34c1aaeea85ece32a9fd43cbeb 100644
--- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
+++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
@@ -192,7 +192,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 				shutdown);
 
 		assertFalse(db.open());
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalAuthor(transaction, localAuthor);
 			assertEquals(contactId,
@@ -233,7 +233,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalMessage(transaction, message, clientId, metadata, true);
 			fail();
@@ -276,7 +276,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalMessage(transaction, message, clientId, metadata, true);
 			transaction.setComplete();
@@ -306,7 +306,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addTransportKeys(transaction, contactId, createTransportKeys());
 			fail();
@@ -316,7 +316,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.generateAck(transaction, contactId, 123);
 			fail();
@@ -326,7 +326,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.generateBatch(transaction, contactId, 123, 456);
 			fail();
@@ -336,7 +336,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.generateOffer(transaction, contactId, 123, 456);
 			fail();
@@ -346,7 +346,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.generateRequest(transaction, contactId, 123);
 			fail();
@@ -356,7 +356,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getContact(transaction, contactId);
 			fail();
@@ -366,7 +366,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getMessageStatus(transaction, contactId, groupId);
 			fail();
@@ -376,7 +376,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getMessageStatus(transaction, contactId, messageId);
 			fail();
@@ -386,7 +386,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.incrementStreamCounter(transaction, contactId, transportId, 0);
 			fail();
@@ -396,7 +396,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.isVisibleToContact(transaction, contactId, groupId);
 			fail();
@@ -406,7 +406,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			Ack a = new Ack(Collections.singletonList(messageId));
 			db.receiveAck(transaction, contactId, a);
@@ -417,7 +417,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.receiveMessage(transaction, contactId, message);
 			fail();
@@ -427,7 +427,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			Offer o = new Offer(Collections.singletonList(messageId));
 			db.receiveOffer(transaction, contactId, o);
@@ -438,7 +438,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			Request r = new Request(Collections.singletonList(messageId));
 			db.receiveRequest(transaction, contactId, r);
@@ -449,7 +449,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.removeContact(transaction, contactId);
 			fail();
@@ -459,7 +459,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.setContactActive(transaction, contactId, true);
 			fail();
@@ -469,7 +469,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
 					new byte[REORDERING_WINDOW_SIZE / 8]);
@@ -480,7 +480,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.setVisibleToContact(transaction, contactId, groupId, true);
 			fail();
@@ -512,7 +512,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addContact(transaction, author, localAuthorId, true);
 			fail();
@@ -522,7 +522,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getLocalAuthor(transaction, localAuthorId);
 			fail();
@@ -532,7 +532,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.removeLocalAuthor(transaction, localAuthorId);
 			fail();
@@ -568,7 +568,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.getGroup(transaction, groupId);
 			fail();
@@ -578,7 +578,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getGroupMetadata(transaction, groupId);
 			fail();
@@ -588,7 +588,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getMessageStatus(transaction, contactId, groupId);
 			fail();
@@ -598,7 +598,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.isVisibleToContact(transaction, contactId, groupId);
 			fail();
@@ -608,7 +608,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.mergeGroupMetadata(transaction, groupId, metadata);
 			fail();
@@ -618,7 +618,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.removeGroup(transaction, group);
 			fail();
@@ -628,7 +628,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.setVisibleToContact(transaction, contactId, groupId, true);
 			fail();
@@ -663,7 +663,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.deleteMessage(transaction, messageId);
 			fail();
@@ -673,7 +673,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.deleteMessageMetadata(transaction, messageId);
 			fail();
@@ -683,7 +683,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getRawMessage(transaction, messageId);
 			fail();
@@ -693,7 +693,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getMessageMetadata(transaction, messageId);
 			fail();
@@ -703,7 +703,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getMessageStatus(transaction, contactId, messageId);
 			fail();
@@ -713,7 +713,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.mergeMessageMetadata(transaction, messageId, metadata);
 			fail();
@@ -723,7 +723,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.setMessageShared(transaction, message, true);
 			fail();
@@ -733,7 +733,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.setMessageValid(transaction, message, clientId, true);
 			fail();
@@ -787,7 +787,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalAuthor(transaction, localAuthor);
 			assertEquals(contactId,
@@ -797,7 +797,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.getTransportKeys(transaction, transportId);
 			fail();
@@ -807,7 +807,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.incrementStreamCounter(transaction, contactId, transportId, 0);
 			fail();
@@ -817,7 +817,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.removeTransport(transaction, transportId);
 			fail();
@@ -827,7 +827,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			db.endTransaction(transaction);
 		}
 
-		transaction = db.startTransaction();
+		transaction = db.startTransaction(false);
 		try {
 			db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
 					new byte[REORDERING_WINDOW_SIZE / 8]);
@@ -863,7 +863,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			Ack a = db.generateAck(transaction, contactId, 123);
 			assertEquals(messagesToAck, a.getMessageIds());
@@ -907,7 +907,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			assertEquals(messages, db.generateBatch(transaction, contactId,
 					size * 2, maxLatency));
@@ -944,7 +944,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			Offer o = db.generateOffer(transaction, contactId, 123, maxLatency);
 			assertEquals(ids, o.getMessageIds());
@@ -978,7 +978,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			Request r = db.generateRequest(transaction, contactId, 123);
 			assertEquals(ids, r.getMessageIds());
@@ -1023,7 +1023,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			assertEquals(messages, db.generateRequestedBatch(transaction,
 					contactId, size * 2, maxLatency));
@@ -1056,7 +1056,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			Ack a = new Ack(Collections.singletonList(messageId));
 			db.receiveAck(transaction, contactId, a);
@@ -1099,7 +1099,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.receiveMessage(transaction, contactId, message);
 			transaction.setComplete();
@@ -1135,7 +1135,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.receiveMessage(transaction, contactId, message);
 			transaction.setComplete();
@@ -1165,7 +1165,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.receiveMessage(transaction, contactId, message);
 			transaction.setComplete();
@@ -1217,7 +1217,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			Offer o = new Offer(Arrays.asList(messageId, messageId1,
 					messageId2, messageId3));
@@ -1252,7 +1252,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			Request r = new Request(Collections.singletonList(messageId));
 			db.receiveRequest(transaction, contactId, r);
@@ -1293,7 +1293,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.setVisibleToContact(transaction, contactId, groupId, true);
 			transaction.setComplete();
@@ -1326,7 +1326,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.setVisibleToContact(transaction, contactId, groupId, true);
 			transaction.setComplete();
@@ -1368,7 +1368,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			db.updateTransportKeys(transaction, keys);
 			assertEquals(keys, db.getTransportKeys(transaction, transportId));
@@ -1434,7 +1434,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
 
-		Transaction transaction = db.startTransaction();
+		Transaction transaction = db.startTransaction(false);
 		try {
 			// First merge should broadcast an event
 			db.mergeSettings(transaction, update, "namespace");
diff --git a/briar-tests/src/org/briarproject/db/TransactionIsolationTest.java b/briar-tests/src/org/briarproject/db/TransactionIsolationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b01f5770a9413b9a2f0508a1b6e57aa05a37ff03
--- /dev/null
+++ b/briar-tests/src/org/briarproject/db/TransactionIsolationTest.java
@@ -0,0 +1,277 @@
+package org.briarproject.db;
+
+import org.briarproject.BriarTestCase;
+import org.briarproject.TestUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TransactionIsolationTest extends BriarTestCase {
+
+	private static final String DROP_TABLE = "DROP TABLE foo IF EXISTS";
+	private static final String CREATE_TABLE = "CREATE TABLE foo"
+			+ " (key INT NOT NULL,"
+			+ " counter INT NOT NULL)";
+	private static final String INSERT_ROW =
+			"INSERT INTO foo (key, counter) VALUES (1, 123)";
+	private static final String GET_COUNTER =
+			"SELECT counter FROM foo WHERE key = 1";
+	private static final String SET_COUNTER =
+			"UPDATE foo SET counter = ? WHERE key = 1";
+
+	private final File testDir = TestUtils.getTestDirectory();
+	private final File db = new File(testDir, "db");
+	private final String withMvcc = "jdbc:h2:" + db.getAbsolutePath()
+			+ ";MV_STORE=TRUE;MVCC=TRUE";
+	private final String withoutMvcc = "jdbc:h2:" + db.getAbsolutePath()
+			+ ";MV_STORE=FALSE;MVCC=FALSE;LOCK_MODE=1";
+
+	@Before
+	public void setUp() throws Exception {
+		assertTrue(testDir.mkdirs());
+		Class.forName("org.h2.Driver");
+	}
+
+	@After
+	public void tearDown() throws Exception {
+		TestUtils.deleteTestDirectory(testDir);
+	}
+
+	@Test
+	public void testDoesNotReadUncommittedWritesWithMvcc() throws Exception {
+		Connection connection = openConnection(true);
+		try {
+			createTableAndInsertRow(connection);
+		} finally {
+			connection.close();
+		}
+		// Start the first transaction
+		Connection txn1 = openConnection(true);
+		try {
+			txn1.setAutoCommit(false);
+			// The first transaction should read the initial value
+			assertEquals(123, getCounter(txn1));
+			// The first transaction updates the value but doesn't commit it
+			assertEquals(1, setCounter(txn1, 234));
+			// Start the second transaction
+			Connection txn2 = openConnection(true);
+			try {
+				txn2.setAutoCommit(false);
+				// The second transaction should still read the initial value
+				assertEquals(123, getCounter(txn2));
+				// Commit the second transaction
+				txn2.commit();
+			} finally {
+				txn2.close();
+			}
+			// Commit the first transaction
+			txn1.commit();
+		} finally {
+			txn1.close();
+		}
+	}
+
+	@Test
+	public void testLastWriterWinsWithMvcc() throws Exception {
+		Connection connection = openConnection(true);
+		try {
+			createTableAndInsertRow(connection);
+		} finally {
+			connection.close();
+		}
+		// Start the first transaction
+		Connection txn1 = openConnection(true);
+		try {
+			txn1.setAutoCommit(false);
+			// The first transaction should read the initial value
+			assertEquals(123, getCounter(txn1));
+			// The first transaction updates the value but doesn't commit it
+			assertEquals(1, setCounter(txn1, 234));
+			// Start the second transaction
+			Connection txn2 = openConnection(true);
+			try {
+				txn2.setAutoCommit(false);
+				// The second transaction should still read the initial value
+				assertEquals(123, getCounter(txn2));
+				// Commit the first transaction
+				txn1.commit();
+				// The second transaction updates the value
+				assertEquals(1, setCounter(txn2, 345));
+				// Commit the second transaction
+				txn2.commit();
+			} finally {
+				txn2.close();
+			}
+		} finally {
+			txn1.close();
+		}
+		// The second transaction was the last writer, so it should win
+		connection = openConnection(true);
+		try {
+			assertEquals(345, getCounter(connection));
+		} finally {
+			connection.close();
+		}
+	}
+
+	@Test
+	public void testLockTimeoutOnRowWithMvcc() throws Exception {
+		Connection connection = openConnection(true);
+		try {
+			createTableAndInsertRow(connection);
+		} finally {
+			connection.close();
+		}
+		// Start the first transaction
+		Connection txn1 = openConnection(true);
+		try {
+			txn1.setAutoCommit(false);
+			// The first transaction should read the initial value
+			assertEquals(123, getCounter(txn1));
+			// Start the second transaction
+			Connection txn2 = openConnection(true);
+			try {
+				txn2.setAutoCommit(false);
+				// The second transaction should read the initial value
+				assertEquals(123, getCounter(txn2));
+				// The first transaction updates the value but doesn't commit it
+				assertEquals(1, setCounter(txn1, 234));
+				// The second transaction tries to update the value
+				try {
+					setCounter(txn2, 345);
+					fail();
+				} catch (SQLException expected) {
+					// Expected: the row is locked by the first transaction
+				}
+				// Abort the transactions
+				txn1.rollback();
+				txn2.rollback();
+			} finally {
+				txn2.close();
+			}
+		} finally {
+			txn1.close();
+		}
+	}
+
+	@Test
+	public void testReadLockTimeoutOnTableWithoutMvcc() throws Exception {
+		Connection connection = openConnection(false);
+		try {
+			createTableAndInsertRow(connection);
+		} finally {
+			connection.close();
+		}
+		// Start the first transaction
+		Connection txn1 = openConnection(false);
+		try {
+			txn1.setAutoCommit(false);
+			// The first transaction should read the initial value
+			assertEquals(123, getCounter(txn1));
+			// Start the second transaction
+			Connection txn2 = openConnection(false);
+			try {
+				txn2.setAutoCommit(false);
+				// The second transaction should read the initial value
+				assertEquals(123, getCounter(txn2));
+				// The first transaction tries to update the value
+				try {
+					setCounter(txn1, 345);
+					fail();
+				} catch (SQLException expected) {
+					// Expected: the table is locked by the second transaction
+				}
+				// Abort the transactions
+				txn1.rollback();
+				txn2.rollback();
+			} finally {
+				txn2.close();
+			}
+		} finally {
+			txn1.close();
+		}
+	}
+
+	@Test
+	public void testWriteLockTimeoutOnTableWithoutMvcc() throws Exception {
+		Connection connection = openConnection(false);
+		try {
+			createTableAndInsertRow(connection);
+		} finally {
+			connection.close();
+		}
+		// Start the first transaction
+		Connection txn1 = openConnection(false);
+		try {
+			txn1.setAutoCommit(false);
+			// The first transaction should read the initial value
+			assertEquals(123, getCounter(txn1));
+			// The first transaction updates the value but doesn't commit yet
+			assertEquals(1, setCounter(txn1, 345));
+			// Start the second transaction
+			Connection txn2 = openConnection(false);
+			try {
+				txn2.setAutoCommit(false);
+				// The second transaction tries to read the value
+				try {
+					getCounter(txn2);
+					fail();
+				} catch (SQLException expected) {
+					// Expected: the table is locked by the first transaction
+				}
+				// Abort the transactions
+				txn1.rollback();
+				txn2.rollback();
+			} finally {
+				txn2.close();
+			}
+		} finally {
+			txn1.close();
+		}
+	}
+
+	private Connection openConnection(boolean mvcc) throws SQLException {
+		return DriverManager.getConnection(mvcc ? withMvcc : withoutMvcc);
+	}
+
+	private void createTableAndInsertRow(Connection c) throws SQLException {
+		Statement s = c.createStatement();
+		s.executeUpdate(DROP_TABLE);
+		s.executeUpdate(CREATE_TABLE);
+		s.executeUpdate(INSERT_ROW);
+		s.close();
+	}
+
+	private int getCounter(Connection c) throws SQLException {
+		Statement s = c.createStatement();
+		ResultSet rs = s.executeQuery(GET_COUNTER);
+		assertTrue(rs.next());
+		int counter = rs.getInt(1);
+		assertFalse(rs.next());
+		rs.close();
+		s.close();
+		return counter;
+	}
+
+	private int setCounter(Connection c, int counter)
+			throws SQLException {
+		PreparedStatement ps = c.prepareStatement(SET_COUNTER);
+		ps.setInt(1, counter);
+		int rowsAffected = ps.executeUpdate();
+		ps.close();
+		return rowsAffected;
+	}
+}
diff --git a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
index 52b5b5448a30a545c4e8db25c68206aeb91ca9b7..2d2ea5ce2ce61e9547d46b7fa15e1f0bdb08e8b7 100644
--- a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
@@ -2,23 +2,18 @@ package org.briarproject.plugins;
 
 import org.briarproject.BriarTestCase;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.db.DatabaseComponent;
-import org.briarproject.api.db.Transaction;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.plugins.ConnectionManager;
+import org.briarproject.api.plugins.PluginConfig;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
-import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
 import org.briarproject.api.plugins.simplex.SimplexPlugin;
 import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
-import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.settings.SettingsManager;
-import org.briarproject.api.system.Clock;
 import org.briarproject.api.ui.UiCallback;
-import org.briarproject.system.SystemClock;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.jmock.lib.concurrent.Synchroniser;
@@ -34,17 +29,12 @@ public class PluginManagerImplTest extends BriarTestCase {
 
 	@Test
 	public void testStartAndStop() throws Exception {
-		Clock clock = new SystemClock();
 		Mockery context = new Mockery() {{
 			setThreadingPolicy(new Synchroniser());
 		}};
 		final Executor ioExecutor = Executors.newSingleThreadExecutor();
 		final EventBus eventBus = context.mock(EventBus.class);
-		final SimplexPluginConfig simplexPluginConfig =
-				context.mock(SimplexPluginConfig.class);
-		final DuplexPluginConfig duplexPluginConfig =
-				context.mock(DuplexPluginConfig.class);
-		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final PluginConfig pluginConfig = context.mock(PluginConfig.class);
 		final Poller poller = context.mock(Poller.class);
 		final ConnectionManager connectionManager =
 				context.mock(ConnectionManager.class);
@@ -53,33 +43,30 @@ public class PluginManagerImplTest extends BriarTestCase {
 		final TransportPropertyManager transportPropertyManager =
 				context.mock(TransportPropertyManager.class);
 		final UiCallback uiCallback = context.mock(UiCallback.class);
+
 		// Two simplex plugin factories: both create plugins, one fails to start
 		final SimplexPluginFactory simplexFactory =
 				context.mock(SimplexPluginFactory.class);
 		final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
 		final TransportId simplexId = new TransportId("simplex");
-		final int simplexLatency = 12345;
-		final Transaction simplexTxn = new Transaction(null);
 		final SimplexPluginFactory simplexFailFactory =
 				context.mock(SimplexPluginFactory.class, "simplexFailFactory");
 		final SimplexPlugin simplexFailPlugin =
 				context.mock(SimplexPlugin.class, "simplexFailPlugin");
 		final TransportId simplexFailId = new TransportId("simplex1");
-		final int simplexFailLatency = 23456;
-		final Transaction simplexFailTxn = new Transaction(null);
+
 		// Two duplex plugin factories: one creates a plugin, the other fails
 		final DuplexPluginFactory duplexFactory =
 				context.mock(DuplexPluginFactory.class);
 		final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
 		final TransportId duplexId = new TransportId("duplex");
-		final int duplexLatency = 34567;
-		final Transaction duplexTxn = new Transaction(null);
 		final DuplexPluginFactory duplexFailFactory =
 				context.mock(DuplexPluginFactory.class, "duplexFailFactory");
 		final TransportId duplexFailId = new TransportId("duplex1");
+
 		context.checking(new Expectations() {{
 			// First simplex plugin
-			oneOf(simplexPluginConfig).getFactories();
+			oneOf(pluginConfig).getSimplexFactories();
 			will(returnValue(Arrays.asList(simplexFactory,
 					simplexFailFactory)));
 			oneOf(simplexFactory).getId();
@@ -87,12 +74,6 @@ public class PluginManagerImplTest extends BriarTestCase {
 			oneOf(simplexFactory).createPlugin(with(any(
 					SimplexPluginCallback.class)));
 			will(returnValue(simplexPlugin)); // Created
-			oneOf(simplexPlugin).getMaxLatency();
-			will(returnValue(simplexLatency));
-			oneOf(db).startTransaction();
-			will(returnValue(simplexTxn));
-			oneOf(db).addTransport(simplexTxn, simplexId, simplexLatency);
-			oneOf(db).endTransaction(simplexTxn);
 			oneOf(simplexPlugin).start();
 			will(returnValue(true)); // Started
 			oneOf(simplexPlugin).shouldPoll();
@@ -104,29 +85,16 @@ public class PluginManagerImplTest extends BriarTestCase {
 			oneOf(simplexFailFactory).createPlugin(with(any(
 					SimplexPluginCallback.class)));
 			will(returnValue(simplexFailPlugin)); // Created
-			oneOf(simplexFailPlugin).getMaxLatency();
-			will(returnValue(simplexFailLatency));
-			oneOf(db).startTransaction();
-			will(returnValue(simplexFailTxn));
-			oneOf(db).addTransport(simplexFailTxn, simplexFailId,
-					simplexFailLatency);
-			oneOf(db).endTransaction(simplexFailTxn);
 			oneOf(simplexFailPlugin).start();
 			will(returnValue(false)); // Failed to start
 			// First duplex plugin
-			oneOf(duplexPluginConfig).getFactories();
+			oneOf(pluginConfig).getDuplexFactories();
 			will(returnValue(Arrays.asList(duplexFactory, duplexFailFactory)));
 			oneOf(duplexFactory).getId();
 			will(returnValue(duplexId));
 			oneOf(duplexFactory).createPlugin(with(any(
 					DuplexPluginCallback.class)));
 			will(returnValue(duplexPlugin)); // Created
-			oneOf(duplexPlugin).getMaxLatency();
-			will(returnValue(duplexLatency));
-			oneOf(db).startTransaction();
-			will(returnValue(duplexTxn));
-			oneOf(db).addTransport(duplexTxn, duplexId, duplexLatency);
-			oneOf(db).endTransaction(duplexTxn);
 			oneOf(duplexPlugin).start();
 			will(returnValue(true)); // Started
 			oneOf(duplexPlugin).shouldPoll();
@@ -143,14 +111,15 @@ public class PluginManagerImplTest extends BriarTestCase {
 			oneOf(simplexPlugin).stop();
 			oneOf(duplexPlugin).stop();
 		}});
+
 		PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
-				simplexPluginConfig, duplexPluginConfig, clock, db, poller,
-				connectionManager, settingsManager, transportPropertyManager,
-				uiCallback);
+				pluginConfig, poller, connectionManager, settingsManager,
+				transportPropertyManager, uiCallback);
 
 		// Two plugins should be started and stopped
 		assertTrue(p.start());
 		assertTrue(p.stop());
+
 		context.assertIsSatisfied();
 	}
 }
diff --git a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
index a62b7add1271d904c284c2af23d5313448a9a824..57e6eb2fe54c29f6184506ae34cd03249c4df35f 100644
--- a/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
+++ b/briar-tests/src/org/briarproject/sync/SimplexOutgoingSessionTest.java
@@ -3,7 +3,6 @@ package org.briarproject.sync;
 import org.briarproject.BriarTestCase;
 import org.briarproject.ImmediateExecutor;
 import org.briarproject.TestUtils;
-import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.Transaction;
@@ -28,7 +27,6 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 	private final Executor dbExecutor;
 	private final EventBus eventBus;
 	private final ContactId contactId;
-	private final TransportId transportId;
 	private final MessageId messageId;
 	private final int maxLatency;
 	private final PacketWriter packetWriter;
@@ -40,7 +38,6 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 		eventBus = context.mock(EventBus.class);
 		packetWriter = context.mock(PacketWriter.class);
 		contactId = new ContactId(234);
-		transportId = new TransportId("id");
 		messageId = new MessageId(TestUtils.getRandomId());
 		maxLatency = Integer.MAX_VALUE;
 	}
@@ -48,21 +45,21 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 	@Test
 	public void testNothingToSend() throws Exception {
 		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
-				dbExecutor, eventBus, contactId, transportId, maxLatency,
-				packetWriter);
-		final Transaction noAckTxn = new Transaction(null);
-		final Transaction noMsgTxn = new Transaction(null);
+				dbExecutor, eventBus, contactId, maxLatency, packetWriter);
+		final Transaction noAckTxn = new Transaction(null, false);
+		final Transaction noMsgTxn = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			// Add listener
 			oneOf(eventBus).addListener(session);
 			// No acks to send
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(noAckTxn));
 			oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
 			will(returnValue(null));
 			oneOf(db).endTransaction(noAckTxn);
 			// No messages to send
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(noMsgTxn));
 			oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
 					with(any(int.class)), with(maxLatency));
@@ -73,7 +70,9 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 			// Remove listener
 			oneOf(eventBus).removeListener(session);
 		}});
+
 		session.run();
+
 		context.assertIsSatisfied();
 	}
 
@@ -82,24 +81,24 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 		final Ack ack = new Ack(Collections.singletonList(messageId));
 		final byte[] raw = new byte[1234];
 		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
-				dbExecutor, eventBus, contactId, transportId, maxLatency,
-				packetWriter);
-		final Transaction ackTxn = new Transaction(null);
-		final Transaction noAckTxn = new Transaction(null);
-		final Transaction msgTxn = new Transaction(null);
-		final Transaction noMsgTxn = new Transaction(null);
+				dbExecutor, eventBus, contactId, maxLatency, packetWriter);
+		final Transaction ackTxn = new Transaction(null, false);
+		final Transaction noAckTxn = new Transaction(null, false);
+		final Transaction msgTxn = new Transaction(null, false);
+		final Transaction noMsgTxn = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			// Add listener
 			oneOf(eventBus).addListener(session);
 			// One ack to send
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(ackTxn));
 			oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
 			will(returnValue(ack));
 			oneOf(db).endTransaction(ackTxn);
 			oneOf(packetWriter).writeAck(ack);
 			// One message to send
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(msgTxn));
 			oneOf(db).generateBatch(with(msgTxn), with(contactId),
 					with(any(int.class)), with(maxLatency));
@@ -107,13 +106,13 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 			oneOf(db).endTransaction(msgTxn);
 			oneOf(packetWriter).writeMessage(raw);
 			// No more acks
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(noAckTxn));
 			oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
 			will(returnValue(null));
 			oneOf(db).endTransaction(noAckTxn);
 			// No more messages
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(noMsgTxn));
 			oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
 					with(any(int.class)), with(maxLatency));
@@ -124,7 +123,9 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 			// Remove listener
 			oneOf(eventBus).removeListener(session);
 		}});
+
 		session.run();
+
 		context.assertIsSatisfied();
 	}
 }
diff --git a/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java b/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java
index d883a1ef20f746256b698972bd0a450cbf837ca3..561bb26ce4d6f6cfcdc8fc29eb37bc2240dd544a 100644
--- a/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/sync/ValidationManagerImplTest.java
@@ -58,20 +58,20 @@ public class ValidationManagerImplTest extends BriarTestCase {
 		final MessageValidator validator = context.mock(MessageValidator.class);
 		final IncomingMessageHook hook =
 				context.mock(IncomingMessageHook.class);
-		final Transaction txn = new Transaction(null);
-		final Transaction txn1 = new Transaction(null);
-		final Transaction txn2 = new Transaction(null);
-		final Transaction txn3 = new Transaction(null);
-		final Transaction txn4 = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
+		final Transaction txn1 = new Transaction(null, false);
+		final Transaction txn2 = new Transaction(null, false);
+		final Transaction txn3 = new Transaction(null, false);
+		final Transaction txn4 = new Transaction(null, false);
 		context.checking(new Expectations() {{
 			// Get messages to validate
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn));
 			oneOf(db).getMessagesToValidate(txn, clientId);
 			will(returnValue(Arrays.asList(messageId, messageId1)));
 			oneOf(db).endTransaction(txn);
 			// Load the first raw message and group
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn1));
 			oneOf(db).getRawMessage(txn1, messageId);
 			will(returnValue(raw));
@@ -82,7 +82,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
 			oneOf(validator).validateMessage(message, group);
 			will(returnValue(metadata));
 			// Store the validation result for the first message
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(txn2));
 			oneOf(db).mergeMessageMetadata(txn2, messageId, metadata);
 			oneOf(db).setMessageValid(txn2, message, clientId, true);
@@ -91,7 +91,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
 			oneOf(hook).incomingMessage(txn2, message, metadata);
 			oneOf(db).endTransaction(txn2);
 			// Load the second raw message and group
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn3));
 			oneOf(db).getRawMessage(txn3, messageId1);
 			will(returnValue(raw));
@@ -102,7 +102,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
 			oneOf(validator).validateMessage(message1, group);
 			will(returnValue(null));
 			// Store the validation result for the second message
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(txn4));
 			oneOf(db).setMessageValid(txn4, message1, clientId, false);
 			oneOf(db).endTransaction(txn4);
@@ -127,25 +127,25 @@ public class ValidationManagerImplTest extends BriarTestCase {
 		final MessageValidator validator = context.mock(MessageValidator.class);
 		final IncomingMessageHook hook =
 				context.mock(IncomingMessageHook.class);
-		final Transaction txn = new Transaction(null);
-		final Transaction txn1 = new Transaction(null);
-		final Transaction txn2 = new Transaction(null);
-		final Transaction txn3 = new Transaction(null);
+		final Transaction txn = new Transaction(null, true);
+		final Transaction txn1 = new Transaction(null, true);
+		final Transaction txn2 = new Transaction(null, true);
+		final Transaction txn3 = new Transaction(null, false);
 		context.checking(new Expectations() {{
 			// Get messages to validate
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn));
 			oneOf(db).getMessagesToValidate(txn, clientId);
 			will(returnValue(Arrays.asList(messageId, messageId1)));
 			oneOf(db).endTransaction(txn);
 			// Load the first raw message - *gasp* it's gone!
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn1));
 			oneOf(db).getRawMessage(txn1, messageId);
 			will(throwException(new NoSuchMessageException()));
 			oneOf(db).endTransaction(txn1);
 			// Load the second raw message and group
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn2));
 			oneOf(db).getRawMessage(txn2, messageId1);
 			will(returnValue(raw));
@@ -156,7 +156,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
 			oneOf(validator).validateMessage(message1, group);
 			will(returnValue(null));
 			// Store the validation result for the second message
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(txn3));
 			oneOf(db).setMessageValid(txn3, message1, clientId, false);
 			oneOf(db).endTransaction(txn3);
@@ -181,19 +181,19 @@ public class ValidationManagerImplTest extends BriarTestCase {
 		final MessageValidator validator = context.mock(MessageValidator.class);
 		final IncomingMessageHook hook =
 				context.mock(IncomingMessageHook.class);
-		final Transaction txn = new Transaction(null);
-		final Transaction txn1 = new Transaction(null);
-		final Transaction txn2 = new Transaction(null);
-		final Transaction txn3 = new Transaction(null);
+		final Transaction txn = new Transaction(null, true);
+		final Transaction txn1 = new Transaction(null, true);
+		final Transaction txn2 = new Transaction(null, true);
+		final Transaction txn3 = new Transaction(null, false);
 		context.checking(new Expectations() {{
 			// Get messages to validate
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn));
 			oneOf(db).getMessagesToValidate(txn, clientId);
 			will(returnValue(Arrays.asList(messageId, messageId1)));
 			oneOf(db).endTransaction(txn);
 			// Load the first raw message
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn1));
 			oneOf(db).getRawMessage(txn1, messageId);
 			will(returnValue(raw));
@@ -202,7 +202,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
 			will(throwException(new NoSuchGroupException()));
 			oneOf(db).endTransaction(txn1);
 			// Load the second raw message and group
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn2));
 			oneOf(db).getRawMessage(txn2, messageId1);
 			will(returnValue(raw));
@@ -213,7 +213,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
 			oneOf(validator).validateMessage(message1, group);
 			will(returnValue(null));
 			// Store the validation result for the second message
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(txn3));
 			oneOf(db).setMessageValid(txn3, message1, clientId, false);
 			oneOf(db).endTransaction(txn3);
@@ -237,11 +237,11 @@ public class ValidationManagerImplTest extends BriarTestCase {
 		final MessageValidator validator = context.mock(MessageValidator.class);
 		final IncomingMessageHook hook =
 				context.mock(IncomingMessageHook.class);
-		final Transaction txn = new Transaction(null);
-		final Transaction txn1 = new Transaction(null);
+		final Transaction txn = new Transaction(null, true);
+		final Transaction txn1 = new Transaction(null, false);
 		context.checking(new Expectations() {{
 			// Load the group
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(true);
 			will(returnValue(txn));
 			oneOf(db).getGroup(txn, groupId);
 			will(returnValue(group));
@@ -250,7 +250,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
 			oneOf(validator).validateMessage(message, group);
 			will(returnValue(metadata));
 			// Store the validation result
-			oneOf(db).startTransaction();
+			oneOf(db).startTransaction(false);
 			will(returnValue(txn1));
 			oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
 			oneOf(db).setMessageValid(txn1, message, clientId, true);
diff --git a/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java b/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java
index 148262b27557af93fd8a541db9d881002d8a5e46..7fdfbcf7b3a1aeb30fc0a77b076cc1575487c393 100644
--- a/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java
+++ b/briar-tests/src/org/briarproject/transport/TransportKeyManagerTest.java
@@ -37,6 +37,7 @@ import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 public class TransportKeyManagerTest extends BriarTestCase {
 
@@ -57,7 +58,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final Timer timer = context.mock(Timer.class);
 		final Clock clock = context.mock(Clock.class);
-		final Transaction txn = new Transaction(null);
+
 		final Map<ContactId, TransportKeys> loaded =
 				new LinkedHashMap<ContactId, TransportKeys>();
 		final TransportKeys shouldRotate = createTransportKeys(900, 0);
@@ -65,17 +66,15 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		loaded.put(contactId, shouldRotate);
 		loaded.put(contactId1, shouldNotRotate);
 		final TransportKeys rotated = createTransportKeys(1000, 0);
-		final Transaction txn1 = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			// Get the current time (1 ms after start of rotation period 1000)
 			oneOf(clock).currentTimeMillis();
 			will(returnValue(rotationPeriodLength * 1000 + 1));
 			// Load the transport keys
-			oneOf(db).startTransaction();
-			will(returnValue(txn));
 			oneOf(db).getTransportKeys(txn, transportId);
 			will(returnValue(loaded));
-			oneOf(db).endTransaction(txn);
 			// Rotate the transport keys
 			oneOf(crypto).rotateTransportKeys(shouldRotate, 1000);
 			will(returnValue(rotated));
@@ -88,11 +87,8 @@ public class TransportKeyManagerTest extends BriarTestCase {
 				will(new EncodeTagAction());
 			}
 			// Save the keys that were rotated
-			oneOf(db).startTransaction();
-			will(returnValue(txn1));
-			oneOf(db).updateTransportKeys(txn1,
+			oneOf(db).updateTransportKeys(txn,
 					Collections.singletonMap(contactId, rotated));
-			oneOf(db).endTransaction(txn1);
 			// Schedule key rotation at the start of the next rotation period
 			oneOf(timer).schedule(with(any(TimerTask.class)),
 					with(rotationPeriodLength - 1));
@@ -100,7 +96,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
 
 		TransportKeyManager transportKeyManager = new TransportKeyManager(db,
 				crypto, timer, clock, transportId, maxLatency);
-		transportKeyManager.start();
+		transportKeyManager.start(txn);
 
 		context.assertIsSatisfied();
 	}
@@ -112,10 +108,12 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final Timer timer = context.mock(Timer.class);
 		final Clock clock = context.mock(Clock.class);
+
 		final boolean alice = true;
 		final TransportKeys transportKeys = createTransportKeys(999, 0);
 		final TransportKeys rotated = createTransportKeys(1000, 0);
-		final Transaction txn = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			oneOf(crypto).deriveTransportKeys(transportId, masterKey, 999,
 					alice);
@@ -155,9 +153,11 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		final Timer timer = context.mock(Timer.class);
 		final Clock clock = context.mock(Clock.class);
 
+		final Transaction txn = new Transaction(null, false);
+
 		TransportKeyManager transportKeyManager = new TransportKeyManager(db,
 				crypto, timer, clock, transportId, maxLatency);
-		assertNull(transportKeyManager.getStreamContext(contactId));
+		assertNull(transportKeyManager.getStreamContext(txn, contactId));
 
 		context.assertIsSatisfied();
 	}
@@ -170,11 +170,13 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final Timer timer = context.mock(Timer.class);
 		final Clock clock = context.mock(Clock.class);
+
 		final boolean alice = true;
 		// The stream counter has been exhausted
 		final TransportKeys transportKeys = createTransportKeys(1000,
 				MAX_32_BIT_UNSIGNED + 1);
-		final Transaction txn = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
 					alice);
@@ -201,7 +203,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		long timestamp = rotationPeriodLength * 1000;
 		transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
 				alice);
-		assertNull(transportKeyManager.getStreamContext(contactId));
+		assertNull(transportKeyManager.getStreamContext(txn, contactId));
 
 		context.assertIsSatisfied();
 	}
@@ -213,12 +215,13 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final Timer timer = context.mock(Timer.class);
 		final Clock clock = context.mock(Clock.class);
+
 		final boolean alice = true;
 		// The stream counter can be used one more time before being exhausted
 		final TransportKeys transportKeys = createTransportKeys(1000,
 				MAX_32_BIT_UNSIGNED);
-		final Transaction txn = new Transaction(null);
-		final Transaction txn1 = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
 					alice);
@@ -238,11 +241,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
 			// Save the keys
 			oneOf(db).addTransportKeys(txn, contactId, transportKeys);
 			// Increment the stream counter
-			oneOf(db).startTransaction();
-			will(returnValue(txn1));
-			oneOf(db).incrementStreamCounter(txn1, contactId, transportId,
-					1000);
-			oneOf(db).endTransaction(txn1);
+			oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
 		}});
 
 		TransportKeyManager transportKeyManager = new TransportKeyManager(db,
@@ -252,7 +251,8 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
 				alice);
 		// The first request should return a stream context
-		StreamContext ctx = transportKeyManager.getStreamContext(contactId);
+		StreamContext ctx = transportKeyManager.getStreamContext(txn,
+				contactId);
 		assertNotNull(ctx);
 		assertEquals(contactId, ctx.getContactId());
 		assertEquals(transportId, ctx.getTransportId());
@@ -260,7 +260,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		assertEquals(headerKey, ctx.getHeaderKey());
 		assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
 		// The second request should return null, the counter is exhausted
-		assertNull(transportKeyManager.getStreamContext(contactId));
+		assertNull(transportKeyManager.getStreamContext(txn, contactId));
 
 		context.assertIsSatisfied();
 	}
@@ -273,9 +273,11 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final Timer timer = context.mock(Timer.class);
 		final Clock clock = context.mock(Clock.class);
+
 		final boolean alice = true;
 		final TransportKeys transportKeys = createTransportKeys(1000, 0);
-		final Transaction txn = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
 					alice);
@@ -302,7 +304,8 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		long timestamp = rotationPeriodLength * 1000;
 		transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
 				alice);
-		assertNull(transportKeyManager.getStreamContext(new byte[TAG_LENGTH]));
+		assertNull(transportKeyManager.getStreamContext(txn,
+				new byte[TAG_LENGTH]));
 
 		context.assertIsSatisfied();
 	}
@@ -314,12 +317,13 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final Timer timer = context.mock(Timer.class);
 		final Clock clock = context.mock(Clock.class);
+
 		final boolean alice = true;
 		final TransportKeys transportKeys = createTransportKeys(1000, 0);
-		final Transaction txn = new Transaction(null);
-		final Transaction txn1 = new Transaction(null);
 		// Keep a copy of the tags
 		final List<byte[]> tags = new ArrayList<byte[]>();
+		final Transaction txn = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
 					alice);
@@ -343,11 +347,8 @@ public class TransportKeyManagerTest extends BriarTestCase {
 					with(tagKey), with((long) REORDERING_WINDOW_SIZE));
 			will(new EncodeTagAction(tags));
 			// Save the reordering window (previous rotation period, base 1)
-			oneOf(db).startTransaction();
-			will(returnValue(txn1));
-			oneOf(db).setReorderingWindow(txn1, contactId, transportId, 999,
+			oneOf(db).setReorderingWindow(txn, contactId, transportId, 999,
 					1, new byte[REORDERING_WINDOW_SIZE / 8]);
-			oneOf(db).endTransaction(txn1);
 		}});
 
 		TransportKeyManager transportKeyManager = new TransportKeyManager(db,
@@ -360,7 +361,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
 		byte[] tag = tags.get(0);
 		// The first request should return a stream context
-		StreamContext ctx = transportKeyManager.getStreamContext(tag);
+		StreamContext ctx = transportKeyManager.getStreamContext(txn, tag);
 		assertNotNull(ctx);
 		assertEquals(contactId, ctx.getContactId());
 		assertEquals(transportId, ctx.getTransportId());
@@ -370,7 +371,7 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		// Another tag should have been encoded
 		assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
 		// The second request should return null, the tag has already been used
-		assertNull(transportKeyManager.getStreamContext(tag));
+		assertNull(transportKeyManager.getStreamContext(txn, tag));
 
 		context.assertIsSatisfied();
 	}
@@ -382,22 +383,21 @@ public class TransportKeyManagerTest extends BriarTestCase {
 		final CryptoComponent crypto = context.mock(CryptoComponent.class);
 		final Timer timer = context.mock(Timer.class);
 		final Clock clock = context.mock(Clock.class);
-		final Transaction txn = new Transaction(null);
+
 		final TransportKeys transportKeys = createTransportKeys(1000, 0);
 		final Map<ContactId, TransportKeys> loaded =
 				Collections.singletonMap(contactId, transportKeys);
 		final TransportKeys rotated = createTransportKeys(1001, 0);
-		final Transaction txn1 = new Transaction(null);
+		final Transaction txn = new Transaction(null, false);
+		final Transaction txn1 = new Transaction(null, false);
+
 		context.checking(new Expectations() {{
 			// Get the current time (the start of rotation period 1000)
 			oneOf(clock).currentTimeMillis();
 			will(returnValue(rotationPeriodLength * 1000));
 			// Load the transport keys
-			oneOf(db).startTransaction();
-			will(returnValue(txn));
 			oneOf(db).getTransportKeys(txn, transportId);
 			will(returnValue(loaded));
-			oneOf(db).endTransaction(txn);
 			// Rotate the transport keys (the keys are unaffected)
 			oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
 			will(returnValue(transportKeys));
@@ -411,6 +411,9 @@ public class TransportKeyManagerTest extends BriarTestCase {
 			oneOf(timer).schedule(with(any(TimerTask.class)),
 					with(rotationPeriodLength));
 			will(new RunTimerTaskAction());
+			// Start a transaction for key rotation
+			oneOf(db).startTransaction(false);
+			will(returnValue(txn1));
 			// Get the current time (the start of rotation period 1001)
 			oneOf(clock).currentTimeMillis();
 			will(returnValue(rotationPeriodLength * 1001));
@@ -425,19 +428,19 @@ public class TransportKeyManagerTest extends BriarTestCase {
 				will(new EncodeTagAction());
 			}
 			// Save the keys that were rotated
-			oneOf(db).startTransaction();
-			will(returnValue(txn1));
 			oneOf(db).updateTransportKeys(txn1,
 					Collections.singletonMap(contactId, rotated));
-			oneOf(db).endTransaction(txn1);
 			// Schedule key rotation at the start of the next rotation period
 			oneOf(timer).schedule(with(any(TimerTask.class)),
 					with(rotationPeriodLength));
+			// Commit the key rotation transaction
+			oneOf(db).endTransaction(txn1);
 		}});
 
 		TransportKeyManager transportKeyManager = new TransportKeyManager(db,
 				crypto, timer, clock, transportId, maxLatency);
-		transportKeyManager.start();
+		transportKeyManager.start(txn);
+		assertTrue(txn1.isComplete());
 
 		context.assertIsSatisfied();
 	}