diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabasePerformanceTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabasePerformanceTest.java
index 5144115da7f10c8cb32e3e6f9b0f9c5a2c9e8c85..b7580853cc0b764e1ecfabd7797c0287dee8a355 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabasePerformanceTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabasePerformanceTest.java
@@ -1,15 +1,17 @@
 package org.briarproject.bramble.db;
 
+import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
-import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.sync.ClientId;
 import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.sync.ValidationManager.State;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.system.SystemClock;
@@ -31,6 +33,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Random;
 
+import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
 import static org.briarproject.bramble.test.TestUtils.getAuthor;
@@ -40,6 +43,7 @@ import static org.briarproject.bramble.test.TestUtils.getMean;
 import static org.briarproject.bramble.test.TestUtils.getMedian;
 import static org.briarproject.bramble.test.TestUtils.getMessage;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.test.TestUtils.getStandardDeviation;
 import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
 import static org.briarproject.bramble.test.UTest.Result.INCONCLUSIVE;
@@ -83,6 +87,7 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 	private static final int METADATA_KEYS_PER_MESSAGE = 5;
 	private static final int METADATA_KEY_LENGTH = 10;
 	private static final int METADATA_VALUE_LENGTH = 100;
+	private static final int OFFERED_MESSAGES_PER_CONTACT = 100;
 
 	/**
 	 * How many times to run each benchmark while measuring.
@@ -92,10 +97,14 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 	private final File testDir = getTestDirectory();
 	private final Random random = new Random();
 
+	private LocalAuthor localAuthor;
 	private List<ClientId> clientIds;
+	private List<Contact> contacts;
 	private List<Group> groups;
 	private List<Message> messages;
 	private Map<GroupId, List<Metadata>> messageMeta;
+	private Map<ContactId, List<Group>> contactGroups;
+	private Map<GroupId, List<MessageId>> groupMessages;
 
 	protected abstract String getTestName();
 
@@ -112,6 +121,88 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 		deleteTestDirectory(testDir);
 	}
 
+	@Test
+	public void testContainsContactByAuthorId() throws Exception {
+		String name = "containsContact(T, AuthorId, AuthorId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			AuthorId remote = pickRandom(contacts).getAuthor().getId();
+			db.containsContact(txn, remote, localAuthor.getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testContainsContactByContactId() throws Exception {
+		String name = "containsContact(T, ContactId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.containsContact(txn, pickRandom(contacts).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testContainsGroup() throws Exception {
+		String name = "containsGroup(T, GroupId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.containsGroup(txn, pickRandom(groups).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testContainsLocalAuthor() throws Exception {
+		String name = "containsLocalAuthor(T, AuthorId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.containsLocalAuthor(txn, localAuthor.getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testContainsMessage() throws Exception {
+		String name = "containsMessage(T, MessageId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.containsMessage(txn, pickRandom(messages).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testContainsVisibleMessage() throws Exception {
+		String name = "containsVisibleMessage(T, ContactId, MessageId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.containsVisibleMessage(txn, pickRandom(contacts).getId(),
+					pickRandom(messages).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testCountOfferedMessages() throws Exception {
+		String name = "countOfferedMessages(T, ContactId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.countOfferedMessages(txn, pickRandom(contacts).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetContact() throws Exception {
+		String name = "getContact(T, ContactId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getContact(txn, pickRandom(contacts).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
 	@Test
 	public void testGetContacts() throws Exception {
 		String name = "getContacts(T)";
@@ -123,11 +214,114 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 	}
 
 	@Test
-	public void testGetRawMessage() throws Exception {
-		String name = "getRawMessage(T, MessageId)";
+	public void testGetContactsByRemoteAuthorId() throws Exception {
+		String name = "getContactsByAuthorId(T, AuthorId)";
 		benchmark(name, db -> {
 			Connection txn = db.startTransaction();
-			db.getRawMessage(txn, pickRandom(messages).getId());
+			AuthorId remote = pickRandom(contacts).getAuthor().getId();
+			db.getContactsByAuthorId(txn, remote);
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetContactsByLocalAuthorId() throws Exception {
+		String name = "getContacts(T, AuthorId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getContacts(txn, localAuthor.getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetGroup() throws Exception {
+		String name = "getGroup(T, GroupId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getGroup(txn, pickRandom(groups).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetGroupMetadata() throws Exception {
+		String name = "getGroupMetadata(T, GroupId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getGroupMetadata(txn, pickRandom(groups).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetGroups() throws Exception {
+		String name = "getGroups(T, ClientId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getGroups(txn, pickRandom(clientIds));
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetGroupVisibilityWithContactId() throws Exception {
+		String name = "getGroupVisibility(T, ContactId, GroupId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			ContactId c = pickRandom(contacts).getId();
+			db.getGroupVisibility(txn, c,
+					pickRandom(contactGroups.get(c)).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetGroupVisibility() throws Exception {
+		String name = "getGroupVisibility(T, GroupId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getGroupVisibility(txn, pickRandom(groups).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetLocalAuthor() throws Exception {
+		String name = "getLocalAuthor(T, AuthorId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getLocalAuthor(txn, localAuthor.getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetLocalAuthors() throws Exception {
+		String name = "getLocalAuthors(T)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getLocalAuthors(txn);
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessageDependencies() throws Exception {
+		String name = "getMessageDependencies(T, MessageId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getMessageDependencies(txn, pickRandom(messages).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessageDependents() throws Exception {
+		String name = "getMessageDependents(T, MessageId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getMessageDependents(txn, pickRandom(messages).getId());
 			db.commitTransaction(txn);
 		});
 	}
@@ -144,7 +338,7 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 
 	@Test
 	public void testGetMessageIdsWithMatchingQuery() throws Exception {
-		String name = "getMessageIds(T, GroupId, Metadata)";
+		String name = "getMessageIds(T, GroupId, Metadata) [match]";
 		benchmark(name, db -> {
 			Connection txn = db.startTransaction();
 			GroupId g = pickRandom(groups).getId();
@@ -155,7 +349,7 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 
 	@Test
 	public void testGetMessageIdsWithNonMatchingQuery() throws Exception {
-		String name = "getMessageIds(T, GroupId, Metadata)";
+		String name = "getMessageIds(T, GroupId, Metadata) [no match]";
 		benchmark(name, db -> {
 			Connection txn = db.startTransaction();
 			Metadata query = getMetadata(METADATA_KEYS_PER_MESSAGE);
@@ -165,31 +359,110 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 	}
 
 	@Test
-	public void testGetGroupMetadata() throws Exception {
-		String name = "getGroupMetadata(T, GroupId)";
+	public void testGetMessageMetadataByGroupId() throws Exception {
+		String name = "getMessageMetadata(T, GroupId)";
 		benchmark(name, db -> {
 			Connection txn = db.startTransaction();
-			db.getGroupMetadata(txn, pickRandom(groups).getId());
+			db.getMessageMetadata(txn, pickRandom(groups).getId());
 			db.commitTransaction(txn);
 		});
 	}
 
 	@Test
-	public void testGetMessageMetadataForGroup() throws Exception {
-		String name = "getMessageMetadata(T, GroupId)";
+	public void testGetMessageMetadataByMessageId() throws Exception {
+		String name = "getMessageMetadata(T, MessageId)";
 		benchmark(name, db -> {
 			Connection txn = db.startTransaction();
-			db.getMessageMetadata(txn, pickRandom(groups).getId());
+			db.getMessageMetadata(txn, pickRandom(messages).getId());
 			db.commitTransaction(txn);
 		});
 	}
 
 	@Test
-	public void testGetMessageMetadataForMessage() throws Exception {
-		String name = "getMessageMetadata(T, MessageId)";
+	public void testGetMessageMetadataForValidator() throws Exception {
+		String name = "getMessageMetadataForValidator(T, MessageId)";
 		benchmark(name, db -> {
 			Connection txn = db.startTransaction();
-			db.getMessageMetadata(txn, pickRandom(messages).getId());
+			db.getMessageMetadataForValidator(txn,
+					pickRandom(messages).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessageState() throws Exception {
+		String name = "getMessageState(T, MessageId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getMessageState(txn, pickRandom(messages).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessageStatusByGroupId() throws Exception {
+		String name = "getMessageStatus(T, ContactId, GroupId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			ContactId c = pickRandom(contacts).getId();
+			GroupId g = pickRandom(contactGroups.get(c)).getId();
+			db.getMessageStatus(txn, c, g);
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessageStatusByMessageId() throws Exception {
+		String name = "getMessageStatus(T, ContactId, MessageId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			ContactId c = pickRandom(contacts).getId();
+			GroupId g = pickRandom(contactGroups.get(c)).getId();
+			db.getMessageStatus(txn, c, pickRandom(groupMessages.get(g)));
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessagesToAck() throws Exception {
+		String name = "getMessagesToAck(T, ContactId, int)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getMessagesToAck(txn, pickRandom(contacts).getId(),
+					MAX_MESSAGE_IDS);
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessagesToOffer() throws Exception {
+		String name = "getMessagesToOffer(T, ContactId, int)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getMessagesToOffer(txn, pickRandom(contacts).getId(),
+					MAX_MESSAGE_IDS);
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessagesToRequest() throws Exception {
+		String name = "getMessagesToRequest(T, ContactId, int)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getMessagesToRequest(txn, pickRandom(contacts).getId(),
+					MAX_MESSAGE_IDS);
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetMessagesToSend() throws Exception {
+		String name = "getMessagesToSend(T, ContactId, int)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getMessagesToSend(txn, pickRandom(contacts).getId(),
+					MAX_MESSAGE_IDS);
 			db.commitTransaction(txn);
 		});
 	}
@@ -224,6 +497,27 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 		});
 	}
 
+	@Test
+	public void testGetRawMessage() throws Exception {
+		String name = "getRawMessage(T, MessageId)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getRawMessage(txn, pickRandom(messages).getId());
+			db.commitTransaction(txn);
+		});
+	}
+
+	@Test
+	public void testGetRequestedMessagesToSend() throws Exception {
+		String name = "getRequestedMessagesToSend(T, ContactId, int)";
+		benchmark(name, db -> {
+			Connection txn = db.startTransaction();
+			db.getRequestedMessagesToSend(txn, pickRandom(contacts).getId(),
+					MAX_MESSAGE_IDS);
+			db.commitTransaction(txn);
+		});
+	}
+
 	private <T> T pickRandom(List<T> list) {
 		return list.get(random.nextInt(list.size()));
 	}
@@ -270,26 +564,32 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 	}
 
 	private void populateDatabase(Database<Connection> db) throws DbException {
+		localAuthor = getLocalAuthor();
 		clientIds = new ArrayList<>();
+		contacts = new ArrayList<>();
 		groups = new ArrayList<>();
 		messages = new ArrayList<>();
 		messageMeta = new HashMap<>();
+		contactGroups = new HashMap<>();
+		groupMessages = new HashMap<>();
 
 		for (int i = 0; i < CLIENTS; i++) clientIds.add(getClientId());
 
 		Connection txn = db.startTransaction();
-		LocalAuthor localAuthor = getLocalAuthor();
 		db.addLocalAuthor(txn, localAuthor);
 		for (int i = 0; i < CONTACTS; i++) {
-			Author a = getAuthor();
-			ContactId contactId = db.addContact(txn, a, localAuthor.getId(),
+			ContactId c = db.addContact(txn, getAuthor(), localAuthor.getId(),
 					random.nextBoolean(), true);
+			contacts.add(db.getContact(txn, c));
+			contactGroups.put(c, new ArrayList<>());
 			for (int j = 0; j < GROUPS_PER_CONTACT; j++) {
 				Group g = getGroup(clientIds.get(j % CLIENTS));
 				groups.add(g);
 				messageMeta.put(g.getId(), new ArrayList<>());
+				contactGroups.get(c).add(g);
+				groupMessages.put(g.getId(), new ArrayList<>());
 				db.addGroup(txn, g);
-				db.addGroupVisibility(txn, contactId, g.getId(), true);
+				db.addGroupVisibility(txn, c, g.getId(), true);
 				Metadata gm = getMetadata(METADATA_KEYS_PER_GROUP);
 				db.mergeGroupMetadata(txn, g.getId(), gm);
 				for (int k = 0; k < MESSAGES_PER_GROUP; k++) {
@@ -297,16 +597,29 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 					messages.add(m);
 					State state = State.fromValue(random.nextInt(4));
 					db.addMessage(txn, m, state, random.nextBoolean());
+					db.addStatus(txn, c, m.getId(), random.nextBoolean(),
+							random.nextBoolean());
+					if (random.nextBoolean())
+						db.raiseRequestedFlag(txn, c, m.getId());
 					Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
 					messageMeta.get(g.getId()).add(mm);
 					db.mergeMessageMetadata(txn, m.getId(), mm);
+					if (k > 0) {
+						db.addMessageDependency(txn, g.getId(), m.getId(),
+								pickRandom(groupMessages.get(g.getId())));
+					}
+					groupMessages.get(g.getId()).add(m.getId());
 				}
 			}
+			for (int j = 0; j < OFFERED_MESSAGES_PER_CONTACT; j++) {
+				db.addOfferedMessage(txn, c, new MessageId(getRandomId()));
+			}
 		}
 		for (int i = 0; i < LOCAL_GROUPS; i++) {
 			Group g = getGroup(clientIds.get(i % CLIENTS));
 			groups.add(g);
 			messageMeta.put(g.getId(), new ArrayList<>());
+			groupMessages.put(g.getId(), new ArrayList<>());
 			db.addGroup(txn, g);
 			Metadata gm = getMetadata(METADATA_KEYS_PER_GROUP);
 			db.mergeGroupMetadata(txn, g.getId(), gm);
@@ -317,6 +630,11 @@ public abstract class JdbcDatabasePerformanceTest extends BrambleTestCase {
 				Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
 				messageMeta.get(g.getId()).add(mm);
 				db.mergeMessageMetadata(txn, m.getId(), mm);
+				if (j > 0) {
+					db.addMessageDependency(txn, g.getId(), m.getId(),
+							pickRandom(groupMessages.get(g.getId())));
+				}
+				groupMessages.get(g.getId()).add(m.getId());
 			}
 		}
 		db.commitTransaction(txn);