diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index a08da0163a8c139d324cf776d26ed10e8f39ca90..cc6be9b2fbd03aa4d78f4ae4b556251a3f44278f 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -125,6 +125,15 @@ interface Database<T> {
 	 */
 	void addSubscription(T txn, Group g) throws DbException;
 
+	/**
+	 * Records the given contact's subscription to the given group starting at
+	 * the given time.
+	 * <p>
+	 * Locking: contact read, subscription write.
+	 */
+	void addSubscription(T txn, ContactId c, Group g, long start)
+	throws DbException;
+
 	/**
 	 * Allocates and returns a local index for the given transport. Returns
 	 * null if all indices have been allocated.
@@ -526,6 +535,16 @@ interface Database<T> {
 	 */
 	void removeSubscription(T txn, GroupId g) throws DbException;
 
+	/**
+	 * Removes any subscriptions for the given contact with IDs between the
+	 * given IDs. If both of the given IDs are null, all subscriptions are
+	 * removed. If only the first is null, all subscriptions with IDs less than
+	 * the second ID are removed. If onlt the second is null, all subscriptions
+	 * with IDs greater than the first are removed.
+	 */
+	void removeSubscriptions(T txn, ContactId c, GroupId start, GroupId end)
+	throws DbException;
+
 	/**
 	 * Makes the given group invisible to the given contact.
 	 * <p>
@@ -551,6 +570,13 @@ interface Database<T> {
 	void setConnectionWindow(T txn, ContactId c, TransportIndex i,
 			ConnectionWindow w) throws DbException;
 
+	/**
+	 * Sets the given contact's database expiry time.
+	 * <p>
+	 * Locking: contact read, subscription write.
+	 */
+	void setExpiryTime(T txn, ContactId c, long expiry) throws DbException;
+
 	/**
 	 * Sets the local transport properties for the given transport, replacing
 	 * any existing properties for that transport.
@@ -611,21 +637,21 @@ interface Database<T> {
 	throws DbException;
 
 	/**
-	 * Sets the subscriptions for the given contact, replacing any existing
-	 * subscriptions unless the existing subscriptions have a newer timestamp.
+	 * Records the time of the latest subscription update acknowledged by the
+	 * given contact.
 	 * <p>
 	 * Locking: contact read, subscription write.
 	 */
-	void setSubscriptions(T txn, ContactId c, Map<Group, Long> subs,
-			long timestamp) throws DbException;
+	void setSubscriptionsAcked(T txn, ContactId c, long timestamp)
+	throws DbException;
 
 	/**
-	 * Records the time of the latest subscription modification acknowledged by
-	 * the given contact.
+	 * Records the time of the latest subscription update received from the
+	 * given contact.
 	 * <p>
 	 * Locking: contact read, subscription write.
 	 */
-	void setSubscriptionsAcked(T txn, ContactId c, long timestamp)
+	void setSubscriptionsReceived(T txn, ContactId c, long timestamp)
 	throws DbException;
 
 	/**
diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index 96f33d4a110a717a3ef9703c064a5010eec159cc..c9603296100e7f57e65aeaaf945c0c533580bd64 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -15,6 +15,7 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.logging.Level;
@@ -1175,8 +1176,17 @@ DatabaseCleaner.Callback {
 			try {
 				T txn = db.startTransaction();
 				try {
+					Map<GroupId, GroupId> holes = s.getHoles();
+					for(Entry<GroupId, GroupId> e : holes.entrySet()) {
+						GroupId start = e.getKey(), end = e.getValue();
+						db.removeSubscriptions(txn, c, start, end);
+					}
 					Map<Group, Long> subs = s.getSubscriptions();
-					db.setSubscriptions(txn, c, subs, s.getTimestamp());
+					for(Entry<Group, Long> e : subs.entrySet()) {
+						db.addSubscription(txn, c, e.getKey(), e.getValue());
+					}
+					db.setExpiryTime(txn, c, s.getExpiryTime());
+					db.setSubscriptionsReceived(txn, c, s.getTimestamp());
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index faecea8611fb799f44d4bba9df2d18d51b8d66c4..7e4daaec8f6e9e88db7eab57a5fdbf553d331997 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -735,10 +735,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 	public void addSubscription(Connection txn, Group g) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			// Add the group to the subscriptions table
 			String sql = "INSERT INTO subscriptions"
-				+ " (groupId, groupName, groupKey, start)"
-				+ " VALUES (?, ?, ?, ?)";
+				+ " (groupId, groupName, groupKey, start) VALUES (?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getId().getBytes());
 			ps.setString(2, g.getName());
@@ -754,6 +752,43 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void addSubscription(Connection txn, ContactId c, Group g,
+			long start) throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			// Check whether the subscription already exists
+			String sql = "SELECT NULL FROM contactSubscriptions"
+				+ " WHERE contactId = ? AND groupId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			ps.setBytes(2, g.getId().getBytes());
+			rs = ps.executeQuery();
+			boolean found = rs.next();
+			if(rs.next()) throw new DbStateException();
+			rs.close();
+			ps.close();
+			if(found) return;
+			// Add the subscription
+			sql = "INSERT INTO contactSubscriptions"
+				+ " (contactId, groupId, groupName, groupKey, start)"
+				+ " VALUES (?, ?, ?, ?, ?)";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			ps.setBytes(2, g.getId().getBytes());
+			ps.setString(3, g.getName());
+			ps.setBytes(4, g.getPublicKey());
+			ps.setLong(5, start);
+			int affected = ps.executeUpdate();
+			if(affected != 1) throw new DbStateException();
+			ps.close();
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public TransportIndex addTransport(Connection txn, TransportId t)
 	throws DbException {
 		PreparedStatement ps = null;
@@ -2234,6 +2269,49 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void removeSubscriptions(Connection txn, ContactId c, GroupId start,
+			GroupId end) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			if(start == null && end == null) {
+				// Delete everything
+				String sql = "DELETE FROM contactSubscriptions"
+					+ " WHERE contactId = ?";
+				ps = txn.prepareStatement(sql);
+				ps.setInt(1, c.getInt());
+			} else if(start == null) {
+				// Delete everything before end
+				String sql = "DELETE FROM contactSubscriptions"
+					+ " WHERE contactId = ? AND groupId < ?";
+				ps = txn.prepareStatement(sql);
+				ps.setInt(1, c.getInt());
+				ps.setBytes(2, end.getBytes());
+			} else if(end == null) {
+				// Delete everything after start
+				String sql = "DELETE FROM contactSubscriptions"
+					+ " WHERE contactId = ? AND groupId > ?";
+				ps = txn.prepareStatement(sql);
+				ps.setInt(1, c.getInt());
+				ps.setBytes(2, start.getBytes());
+			} else {
+				// Delete everything between start and end
+				String sql = "DELETE FROM contactSubscriptions"
+					+ " WHERE contactId = ?"
+					+ " AND groupId > ? AND groupId < ?";
+				ps = txn.prepareStatement(sql);
+				ps.setInt(1, c.getInt());
+				ps.setBytes(2, start.getBytes());
+				ps.setBytes(3, end.getBytes());
+			}
+			ps.executeUpdate();
+			ps.close();
+		} catch(SQLException e) {
+			e.printStackTrace();
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public void removeVisibility(Connection txn, ContactId c, GroupId g)
 	throws DbException {
 		PreparedStatement ps = null;
@@ -2348,6 +2426,24 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void setExpiryTime(Connection txn, ContactId c, long expiry)
+	throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "UPDATE subscriptionTimes SET expiry = ?"
+				+ " WHERE contactId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setLong(1, expiry);
+			ps.setInt(2, c.getInt());
+			int affected = ps.executeUpdate();
+			if(affected > 1) throw new DbStateException();
+			ps.close();
+		} catch(SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public void setLocalProperties(Connection txn, TransportId t,
 			TransportProperties p) throws DbException {
 		PreparedStatement ps = null;
@@ -2641,76 +2737,33 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void setSubscriptions(Connection txn, ContactId c,
-			Map<Group, Long> subs, long timestamp) throws DbException {
+	public void setSubscriptionsAcked(Connection txn, ContactId c,
+			long timestamp) throws DbException {
 		PreparedStatement ps = null;
-		ResultSet rs = null;
 		try {
-			// Return if the timestamp isn't fresh
-			String sql = "SELECT received FROM subscriptionTimes"
-				+ " WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			long lastTimestamp = rs.getLong(1);
-			if(rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			if(lastTimestamp >= timestamp) return;
-			// Delete any existing subscriptions
-			sql = "DELETE FROM contactSubscriptions WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.executeUpdate();
-			ps.close();
-			// Store the new subscriptions
-			sql = "INSERT INTO contactSubscriptions"
-				+ " (contactId, groupId, groupName, groupKey, start)"
-				+ " VALUES (?, ?, ?, ?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			for(Entry<Group, Long> e : subs.entrySet()) {
-				Group g = e.getKey();
-				ps.setBytes(2, g.getId().getBytes());
-				ps.setString(3, g.getName());
-				ps.setBytes(4, g.getPublicKey());
-				ps.setLong(5, e.getValue());
-				ps.addBatch();
-			}
-			int[] batchAffected = ps.executeBatch();
-			if(batchAffected.length != subs.size())
-				throw new DbStateException();
-			for(int i = 0; i < batchAffected.length; i++) {
-				if(batchAffected[i] != 1) throw new DbStateException();
-			}
-			ps.close();
-			// Update the timestamp
-			sql = "UPDATE subscriptionTimes SET received = ?"
+			String sql = "UPDATE subscriptionTimes SET acked = ?"
 				+ " WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setLong(1, timestamp);
 			ps.setInt(2, c.getInt());
 			int affected = ps.executeUpdate();
-			if(affected != 1) throw new DbStateException();
+			if(affected > 1) throw new DbStateException();
 			ps.close();
 		} catch(SQLException e) {
-			tryToClose(rs);
 			tryToClose(ps);
 			throw new DbException(e);
 		}
 	}
 
-	public void setSubscriptionsAcked(Connection txn, ContactId c,
+	public void setSubscriptionsReceived(Connection txn, ContactId c,
 			long timestamp) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			String sql = "UPDATE subscriptionTimes SET acked = ?"
+			String sql = "UPDATE subscriptionTimes SET received = ?"
 				+ " WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setLong(1, timestamp);
 			ps.setInt(2, c.getInt());
-			ps.setLong(3, timestamp);
 			int affected = ps.executeUpdate();
 			if(affected > 1) throw new DbStateException();
 			ps.close();
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 3f25f3f5aa24b312e1b4a403113b0a9f0159758e..013fcf2c080a966ab0092b88718c89bb371d189f 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -1231,7 +1231,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 
 	@Test
 	public void testReceiveSubscriptionUpdate() throws Exception {
-		final long timestamp = 1234L;
+		final GroupId start = new GroupId(TestUtils.getRandomId());
+		final GroupId end = new GroupId(TestUtils.getRandomId());
+		final long expiry = 1234L, timestamp = 5678L;
 		Mockery context = new Mockery();
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
@@ -1247,12 +1249,19 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			allowing(database).containsContact(txn, contactId);
 			will(returnValue(true));
 			// Get the contents of the update
+			oneOf(subscriptionUpdate).getHoles();
+			will(returnValue(Collections.singletonMap(start, end)));
 			oneOf(subscriptionUpdate).getSubscriptions();
 			will(returnValue(Collections.singletonMap(group, 0L)));
+			oneOf(subscriptionUpdate).getExpiryTime();
+			will(returnValue(expiry));
 			oneOf(subscriptionUpdate).getTimestamp();
 			will(returnValue(timestamp));
-			oneOf(database).setSubscriptions(txn, contactId,
-					Collections.singletonMap(group, 0L), timestamp);
+			// Store the contents of the update
+			oneOf(database).removeSubscriptions(txn, contactId, start, end);
+			oneOf(database).addSubscription(txn, contactId, group, 0L);
+			oneOf(database).setExpiryTime(txn, contactId, expiry);
+			oneOf(database).setSubscriptionsReceived(txn, contactId, timestamp);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				shutdown, packetFactory);
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 4a00ad10a9c5244f9a537e734fcdb27ff78a6210..671cc5905bdfb905fd4b10d458b1a915765ce71c 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -88,7 +88,6 @@ public class H2DatabaseTest extends BriarTestCase {
 	private final TransportProperties properties;
 	private final Map<ContactId, TransportProperties> remoteProperties;
 	private final Collection<Transport> remoteTransports;
-	private final Map<Group, Long> subscriptions;
 	private final byte[] inSecret, outSecret;
 	private final Collection<byte[]> erase;
 
@@ -128,7 +127,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		Transport remoteTransport = new Transport(transportId, remoteIndex,
 				properties);
 		remoteTransports = Collections.singletonList(remoteTransport);
-		subscriptions = Collections.singletonMap(group, 0L);
 		Random r = new Random();
 		inSecret = new byte[32];
 		r.nextBytes(inSecret);
@@ -356,7 +354,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
 
@@ -394,7 +392,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 
@@ -447,7 +445,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertFalse(it.hasNext());
 
 		// The contact subscribing should make the message sendable
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		assertTrue(db.hasSendableMessages(txn, contactId));
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertTrue(it.hasNext());
@@ -455,8 +453,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertFalse(it.hasNext());
 
 		// The contact unsubscribing should make the message unsendable
-		db.setSubscriptions(txn, contactId,
-				Collections.<Group, Long>emptyMap(), 2);
+		db.removeSubscriptions(txn, contactId, null, null);
 		assertFalse(db.hasSendableMessages(txn, contactId));
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertFalse(it.hasNext());
@@ -481,16 +478,15 @@ public class H2DatabaseTest extends BriarTestCase {
 
 		// The message is older than the contact's subscription, so it should
 		// not be sendable
-		db.setSubscriptions(txn, contactId,
-				Collections.singletonMap(group, timestamp + 1), 1);
+		db.addSubscription(txn, contactId, group, timestamp + 1);
 		assertFalse(db.hasSendableMessages(txn, contactId));
 		Iterator<MessageId> it =
 			db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertFalse(it.hasNext());
 
 		// Changing the contact's subscription should make the message sendable
-		db.setSubscriptions(txn, contactId,
-				Collections.singletonMap(group, timestamp), 2);
+		db.removeSubscriptions(txn, contactId, null, null);
+		db.addSubscription(txn, contactId, group, timestamp);
 		assertTrue(db.hasSendableMessages(txn, contactId));
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertTrue(it.hasNext());
@@ -510,7 +506,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -540,7 +536,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -675,7 +671,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -714,7 +710,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -1150,57 +1146,6 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.close();
 	}
 
-	@Test
-	public void testUpdateSubscriptions() throws Exception {
-		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Group group1 = groupFactory.createGroup(groupId1, "Another group name",
-				null);
-		Database<Connection> db = open(false);
-		Connection txn = db.startTransaction();
-
-		// Add a contact with some subscriptions
-		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
-		assertEquals(Collections.singletonList(group),
-				db.getSubscriptions(txn, contactId));
-
-		// Update the subscriptions
-		Map<Group, Long> subscriptions1 = Collections.singletonMap(group1, 0L);
-		db.setSubscriptions(txn, contactId, subscriptions1, 2);
-		assertEquals(Collections.singletonList(group1),
-				db.getSubscriptions(txn, contactId));
-
-		db.commitTransaction(txn);
-		db.close();
-	}
-
-	@Test
-	public void testSubscriptionsNotUpdatedIfTimestampIsOld()
-	throws Exception {
-		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Group group1 = groupFactory.createGroup(groupId1, "Another group name",
-				null);
-		Database<Connection> db = open(false);
-		Connection txn = db.startTransaction();
-
-		// Add a contact with some subscriptions
-		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
-		db.setSubscriptions(txn, contactId, subscriptions, 2);
-		assertEquals(Collections.singletonList(group),
-				db.getSubscriptions(txn, contactId));
-
-		// Try to update the subscriptions using a timestamp of 1
-		Map<Group, Long> subscriptions1 = Collections.singletonMap(group1, 0L);
-		db.setSubscriptions(txn, contactId, subscriptions1, 1);
-
-		// The old subscriptions should still be there
-		assertEquals(Collections.singletonList(group),
-				db.getSubscriptions(txn, contactId));
-
-		db.commitTransaction(txn);
-		db.close();
-	}
-
 	@Test
 	public void testGetMessageIfSendableReturnsNullIfNotInDatabase()
 	throws Exception {
@@ -1210,7 +1155,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Add a contact and subscribe to a group
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 
 		// The message is not in the database
 		assertNull(db.getMessageIfSendable(txn, contactId, messageId));
@@ -1228,7 +1173,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 
 		// Set the sendability to > 0 and the status to SEEN
@@ -1251,7 +1196,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Add a contact, subscribe to a group and store a message
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 
 		// Set the sendability to 0 and the status to NEW
@@ -1275,8 +1220,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		Map<Group, Long> subs = Collections.singletonMap(group, timestamp + 1);
-		db.setSubscriptions(txn, contactId, subs, 1);
+		db.addSubscription(txn, contactId, group, timestamp + 1);
 		db.addGroupMessage(txn, message);
 
 		// Set the sendability to > 0 and the status to NEW
@@ -1299,7 +1243,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 
 		// Set the sendability to > 0 and the status to NEW
@@ -1324,7 +1268,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 
 		// The message is not in the database
 		assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId));
@@ -1341,7 +1285,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 		// Add a contact with a subscription
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 
 		// There's no local subscription for the group
 		assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId));
@@ -1379,7 +1323,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addGroupMessage(txn, message);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
 
 		// The subscription is not visible
@@ -1399,7 +1343,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 
 		// The message has already been seen by the contact
@@ -1421,7 +1365,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn, inSecret, outSecret, erase));
 		db.addSubscription(txn, group);
 		db.addVisibility(txn, contactId, groupId);
-		db.setSubscriptions(txn, contactId, subscriptions, 1);
+		db.addSubscription(txn, contactId, group, 0L);
 		db.addGroupMessage(txn, message);
 
 		// The message has not been seen by the contact