You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by bd...@apache.org on 2022/10/13 00:21:09 UTC

[cassandra-accord] branch trunk updated: Accord transaction state storage

This is an automated email from the ASF dual-hosted git repository.

bdeggleston pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-accord.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 5dd0045  Accord transaction state storage
5dd0045 is described below

commit 5dd0045da7cc353481be6a5caf2e8c8e933b3edc
Author: Blake Eggleston <be...@apple.com>
AuthorDate: Wed Oct 12 17:21:04 2022 -0700

    Accord transaction state storage
    
    Patch by Blake Eggleston; Reviewed by David Capwell and Benedict Elliott Smith for CASSANDRA-17104
---
 accord-core/build.gradle                           |   6 +-
 .../src/main/java/accord/api/ProgressLog.java      |  19 +-
 accord-core/src/main/java/accord/api/Read.java     |   5 +-
 accord-core/src/main/java/accord/api/Write.java    |   4 +-
 .../java/accord/coordinate/CheckOnCommitted.java   |  13 +-
 .../java/accord/coordinate/CheckOnUncommitted.java |   5 +-
 .../src/main/java/accord/coordinate/Execute.java   |  31 +-
 .../main/java/accord/coordinate/Invalidate.java    |   9 +-
 .../src/main/java/accord/coordinate/Recover.java   |   4 +-
 .../src/main/java/accord/impl/InMemoryCommand.java | 314 +++++++++++
 .../InMemoryCommandStore.java}                     | 301 ++++-------
 .../java/accord/impl/InMemoryCommandStores.java    | 110 ++++
 .../java/accord/impl/InMemoryCommandsForKey.java   | 137 +++++
 .../main/java/accord/impl/SimpleProgressLog.java   | 142 ++---
 .../src/main/java/accord/local/Command.java        | 573 +++++++++++----------
 .../src/main/java/accord/local/CommandStore.java   | 331 ++----------
 .../src/main/java/accord/local/CommandStores.java  | 345 ++++++-------
 .../src/main/java/accord/local/CommandsForKey.java |  83 ++-
 .../src/main/java/accord/local/Listener.java       |  15 +
 .../src/main/java/accord/local/Listeners.java      |   2 +-
 accord-core/src/main/java/accord/local/Node.java   |  71 +--
 .../src/main/java/accord/local/PartialCommand.java |  47 ++
 .../src/main/java/accord/local/PreLoadContext.java |  68 +++
 .../src/main/java/accord/messages/Accept.java      |  82 ++-
 .../src/main/java/accord/messages/Apply.java       |  57 +-
 .../main/java/accord/messages/BeginInvalidate.java |  37 +-
 .../main/java/accord/messages/BeginRecovery.java   |  65 ++-
 .../src/main/java/accord/messages/CheckStatus.java |  33 +-
 .../src/main/java/accord/messages/Commit.java      |  87 +++-
 .../java/accord/messages/InformOfPersistence.java  |  32 +-
 .../src/main/java/accord/messages/InformOfTxn.java |  32 +-
 .../src/main/java/accord/messages/PreAccept.java   |  66 ++-
 .../src/main/java/accord/messages/ReadData.java    | 128 ++++-
 .../src/main/java/accord/messages/Reply.java       |   1 -
 .../src/main/java/accord/messages/TxnRequest.java  |   3 +-
 .../main/java/accord/messages/WaitOnCommit.java    |  74 ++-
 .../src/main/java/accord/primitives/Deps.java      |  55 +-
 .../src/main/java/accord/primitives/KeyRanges.java |   5 +-
 .../src/main/java/accord/primitives/Keys.java      |  38 +-
 .../src/main/java/accord/primitives/Timestamp.java |   1 +
 .../src/main/java/accord/topology/Topologies.java  |   8 +-
 .../src/main/java/accord/topology/Topology.java    |  17 -
 accord-core/src/main/java/accord/txn/Txn.java      | 129 +++--
 accord-core/src/main/java/accord/txn/Writes.java   |  37 +-
 .../accord/utils/DeterministicIdentitySet.java     |  28 +-
 .../src/main/java/accord/utils/ReducingFuture.java |  54 ++
 .../src/main/java/accord/utils/SortedArrays.java   |   2 +-
 .../java/accord/utils/Utils.java}                  |  48 +-
 .../cassandra/concurrent/ExecutionFailure.java     | 180 -------
 .../apache/cassandra/concurrent/ExecutorPlus.java  | 185 -------
 .../cassandra/concurrent/ImmediateExecutor.java    |  44 --
 .../java/org/apache/cassandra/utils/Clock.java     | 124 -----
 .../java/org/apache/cassandra/utils/Intercept.java |  31 --
 .../java/org/apache/cassandra/utils/Shared.java    |  45 --
 .../cassandra/utils/concurrent/AbstractFuture.java | 508 ------------------
 .../cassandra/utils/concurrent/AsyncFuture.java    | 162 ------
 .../cassandra/utils/concurrent/AsyncPromise.java   | 268 ----------
 .../cassandra/utils/concurrent/Awaitable.java      | 404 ---------------
 .../cassandra/utils/concurrent/Condition.java      | 104 ----
 .../apache/cassandra/utils/concurrent/Future.java  | 191 -------
 .../utils/concurrent/ImmediateFuture.java          |  52 --
 .../apache/cassandra/utils/concurrent/Inline.java  |  23 -
 .../cassandra/utils/concurrent/IntrusiveStack.java | 156 ------
 .../cassandra/utils/concurrent/ListenerList.java   | 376 --------------
 .../apache/cassandra/utils/concurrent/Promise.java | 128 -----
 .../concurrent/UncheckedInterruptedException.java  |  38 --
 .../cassandra/utils/concurrent/WaitQueue.java      | 437 ----------------
 .../src/test/java/accord/NetworkFilter.java        |   2 +-
 accord-core/src/test/java/accord/Utils.java        |   4 +-
 .../src/test/java/accord/burn/BurnTest.java        |   8 +-
 .../src/test/java/accord/burn/TopologyUpdate.java  |  11 +-
 .../java/accord/coordinate/CoordinateTest.java     |  57 +-
 .../accord/coordinate/PreAcceptTrackerTest.java    |   4 +-
 .../test/java/accord/coordinate/RecoverTest.java   |   5 +-
 .../java/accord/coordinate/TopologyChangeTest.java |  13 +-
 .../coordinate/tracking/QuorumTrackerTest.java     |   4 +-
 .../coordinate/tracking/ReadTrackerTest.java       |   7 +-
 .../src/test/java/accord/impl/IntHashKey.java      |   8 +-
 accord-core/src/test/java/accord/impl/IntKey.java  |   4 +-
 .../src/test/java/accord/impl/TopologyFactory.java |   5 +-
 .../src/test/java/accord/impl/TopologyUtils.java   |   7 +-
 .../src/test/java/accord/impl/basic/Cluster.java   |   6 +-
 .../src/test/java/accord/impl/list/ListRead.java   |   8 +-
 .../src/test/java/accord/impl/list/ListWrite.java  |   9 +-
 .../src/test/java/accord/impl/mock/EpochSync.java  |   8 +-
 .../test/java/accord/impl/mock/MockCluster.java    |   6 +-
 .../src/test/java/accord/impl/mock/MockStore.java  |   9 +-
 .../src/test/java/accord/local/CommandTest.java    |  42 +-
 .../test/java/accord/messages/PreAcceptTest.java   |  17 +-
 accord-core/src/test/java/accord/txn/DepsTest.java |   7 +-
 .../test/java/accord/utils/SortedArraysTest.java   |   9 +-
 accord-maelstrom/build.gradle                      |   1 +
 .../src/main/java/accord/maelstrom/Body.java       |   8 +-
 .../src/main/java/accord/maelstrom/Cluster.java    |   6 +-
 .../src/main/java/accord/maelstrom/Datum.java      |   6 +-
 .../src/main/java/accord/maelstrom/Json.java       |  36 +-
 .../main/java/accord/maelstrom/MaelstromKey.java   |   2 +-
 .../main/java/accord/maelstrom/MaelstromRead.java  |   7 +-
 .../java/accord/maelstrom/MaelstromRequest.java    |   8 +-
 .../java/accord/maelstrom/MaelstromResult.java     |   2 +-
 .../main/java/accord/maelstrom/MaelstromWrite.java |   6 +-
 .../src/main/java/accord/maelstrom/Main.java       |   7 +-
 .../src/main/java/accord/maelstrom/Packet.java     |   2 +-
 .../java/accord/maelstrom/TopologyFactory.java     |   4 +-
 .../src/main/java/accord/maelstrom/Value.java      |   6 +-
 .../src/test/java/accord/maelstrom/Runner.java     |  10 +-
 .../src/main/groovy/accord.java-conventions.gradle |   2 +-
 107 files changed, 2659 insertions(+), 4924 deletions(-)

diff --git a/accord-core/build.gradle b/accord-core/build.gradle
index 1bd1a28..f8c4688 100644
--- a/accord-core/build.gradle
+++ b/accord-core/build.gradle
@@ -30,10 +30,8 @@ java {
 }
 
 dependencies {
-    // Future is part of our API and depends on netty/guava; for this reason need to expose netty/guava
-    // as part as our API (compile + runtime dependency for users)
-    api 'io.netty:netty-all:4.1.58.Final'
-    api 'com.google.guava:guava:30.1.1-jre'
+    implementation group: 'org.apache.cassandra', name: 'cassandra-all', version: '4.1-alpha1'
+    implementation group: "net.ju-n.compile-command-annotations", name: "compile-command-annotations", version: "1.2.0"
 
     // Dependencies we depend on that are not part of our API.
     // These act as runtimeOnly dependencies to users
diff --git a/accord-core/src/main/java/accord/api/ProgressLog.java b/accord-core/src/main/java/accord/api/ProgressLog.java
index 69c2091..a7c76f9 100644
--- a/accord-core/src/main/java/accord/api/ProgressLog.java
+++ b/accord-core/src/main/java/accord/api/ProgressLog.java
@@ -24,10 +24,11 @@ import javax.annotation.Nullable;
 
 import accord.coordinate.CheckOnUncommitted;
 import accord.coordinate.InformHomeOfTxn;
+import accord.local.Command;
 import accord.local.CommandStore;
 import accord.local.Node.Id;
+import accord.local.PartialCommand;
 import accord.primitives.Keys;
-import accord.primitives.TxnId;
 
 /**
  * This interface is responsible for managing incomplete transactions *and retrying them*.
@@ -70,7 +71,7 @@ public interface ProgressLog
      * A non-home shard should begin monitoring this transaction only to ensure it reaches the Accept phase, or is
      * witnessed by a majority of the home shard.
      */
-    void preaccept(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+    void preaccept(Command command, boolean isProgressShard, boolean isHomeShard);
 
     /**
      * Has been accepted
@@ -78,7 +79,7 @@ public interface ProgressLog
      * A home shard should monitor this transaction for global progress.
      * A non-home shard can safely ignore this transaction, as it has been witnessed by a majority of the home shard.
      */
-    void accept(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+    void accept(Command command, boolean isProgressShard, boolean isHomeShard);
 
     /**
      * Has committed
@@ -86,7 +87,7 @@ public interface ProgressLog
      * A home shard should monitor this transaction for global progress.
      * A non-home shard can safely ignore this transaction, as it has been witnessed by a majority of the home shard.
      */
-    void commit(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+    void commit(Command command, boolean isProgressShard, boolean isHomeShard);
 
     /**
      * The transaction is waiting to make progress, as all local dependencies have applied.
@@ -94,7 +95,7 @@ public interface ProgressLog
      * A home shard should monitor this transaction for global progress.
      * A non-home shard can safely ignore this transaction, as it has been witnessed by a majority of the home shard.
      */
-    void readyToExecute(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+    void readyToExecute(Command command, boolean isProgressShard, boolean isHomeShard);
 
     /**
      * The transaction's outcome has been durably recorded (but not necessarily applied) locally.
@@ -105,12 +106,12 @@ public interface ProgressLog
      *
      * May also permit aborting a pending waitingOn-triggered event.
      */
-    void execute(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+    void execute(Command command, boolean isProgressShard, boolean isHomeShard);
 
     /**
      * The transaction has been durably invalidated
      */
-    void invalidate(TxnId txnId, boolean isProgressShard, boolean isHomeShard);
+    void invalidate(Command command, boolean isProgressShard, boolean isHomeShard);
 
     /**
      * The transaction's outcome has been durably recorded (but not necessarily applied) at a quorum of all shards.
@@ -120,7 +121,7 @@ public interface ProgressLog
      *
      * Otherwise, this transaction no longer needs to be monitored by either home or non-home shards.
      */
-    void executedOnAllShards(TxnId txnId, Set<Id> persistedOn);
+    void executedOnAllShards(Command command, Set<Id> persistedOn);
 
     /**
      * The parameter is a command that some other command's execution is most proximally blocked by.
@@ -138,5 +139,5 @@ public interface ProgressLog
      *
      * In all other scenarios, the implementation is free to choose its course of action.
      */
-    void waiting(TxnId blockedBy, @Nullable Keys someKeys);
+    void waiting(PartialCommand command, @Nullable Keys someKeys);
 }
diff --git a/accord-core/src/main/java/accord/api/Read.java b/accord-core/src/main/java/accord/api/Read.java
index ef2f268..58a608b 100644
--- a/accord-core/src/main/java/accord/api/Read.java
+++ b/accord-core/src/main/java/accord/api/Read.java
@@ -20,6 +20,9 @@ package accord.api;
 
 import accord.primitives.Keys;
 import accord.primitives.Timestamp;
+import accord.local.CommandStore;
+import org.apache.cassandra.utils.concurrent.Future;
+
 
 /**
  * A read to be performed on potentially multiple shards, the inputs of which may be fed to a {@link Query}
@@ -29,5 +32,5 @@ import accord.primitives.Timestamp;
 public interface Read
 {
     Keys keys();
-    Data read(Key key, Timestamp executeAt, DataStore store);
+    Future<Data> read(Key key, boolean forWriteTxn, CommandStore commandStore, Timestamp executeAt, DataStore store);
 }
diff --git a/accord-core/src/main/java/accord/api/Write.java b/accord-core/src/main/java/accord/api/Write.java
index 5ef43ee..35cb871 100644
--- a/accord-core/src/main/java/accord/api/Write.java
+++ b/accord-core/src/main/java/accord/api/Write.java
@@ -19,6 +19,8 @@
 package accord.api;
 
 import accord.primitives.Timestamp;
+import accord.local.CommandStore;
+import org.apache.cassandra.utils.concurrent.Future;
 
 /**
  * A collection of data to write to one or more stores
@@ -27,5 +29,5 @@ import accord.primitives.Timestamp;
  */
 public interface Write
 {
-    void apply(Key key, Timestamp executeAt, DataStore store);
+    Future<Void> apply(Key key, CommandStore commandStore, Timestamp executeAt, DataStore store);
 }
diff --git a/accord-core/src/main/java/accord/coordinate/CheckOnCommitted.java b/accord-core/src/main/java/accord/coordinate/CheckOnCommitted.java
index 1f53ddd..c931765 100644
--- a/accord-core/src/main/java/accord/coordinate/CheckOnCommitted.java
+++ b/accord-core/src/main/java/accord/coordinate/CheckOnCommitted.java
@@ -21,10 +21,14 @@ package accord.coordinate;
 import accord.api.Key;
 import accord.local.Command;
 import accord.local.Node;
+import accord.local.PreLoadContext;
 import accord.messages.CheckStatus.CheckStatusOkFull;
 import accord.messages.CheckStatus.IncludeInfo;
 import accord.topology.Shard;
 import accord.primitives.TxnId;
+import com.google.common.collect.Iterables;
+
+import java.util.Collections;
 
 import static accord.local.Status.Executed;
 
@@ -71,24 +75,25 @@ public class CheckOnCommitted extends CheckShardStatus<CheckStatusOkFull>
                 return;
         }
 
-        Key progressKey = node.trySelectProgressKey(txnId, max.txn.keys, max.homeKey);
+        Key progressKey = node.trySelectProgressKey(txnId, max.txn.keys(), max.homeKey);
+        PreLoadContext loadContext = PreLoadContext.contextFor(Iterables.concat(Collections.singleton(txnId), max.deps.txnIds()), max.txn.keys());
         switch (max.status)
         {
             default: throw new IllegalStateException();
             case Executed:
             case Applied:
-                node.forEachLocalSince(max.txn.keys, max.executeAt.epoch, commandStore -> {
+                node.forEachLocalSince(loadContext, max.txn.keys(), max.executeAt.epoch, commandStore -> {
                     Command command = commandStore.command(txnId);
                     command.apply(max.txn, max.homeKey, progressKey, max.executeAt, max.deps, max.writes, max.result);
                 });
-                node.forEachLocal(max.txn.keys, txnId.epoch, max.executeAt.epoch - 1, commandStore -> {
+                node.forEachLocal(loadContext, max.txn.keys(), txnId.epoch, max.executeAt.epoch - 1, commandStore -> {
                     Command command = commandStore.command(txnId);
                     command.commit(max.txn, max.homeKey, progressKey, max.executeAt, max.deps);
                 });
                 break;
             case Committed:
             case ReadyToExecute:
-                node.forEachLocalSince(max.txn.keys, txnId.epoch, commandStore -> {
+                node.forEachLocalSince(loadContext, max.txn.keys(), txnId.epoch, commandStore -> {
                     Command command = commandStore.command(txnId);
                     command.commit(max.txn, max.homeKey, progressKey, max.executeAt, max.deps);
                 });
diff --git a/accord-core/src/main/java/accord/coordinate/CheckOnUncommitted.java b/accord-core/src/main/java/accord/coordinate/CheckOnUncommitted.java
index d2d0343..c87f5b1 100644
--- a/accord-core/src/main/java/accord/coordinate/CheckOnUncommitted.java
+++ b/accord-core/src/main/java/accord/coordinate/CheckOnUncommitted.java
@@ -21,6 +21,7 @@ package accord.coordinate;
 import accord.api.Key;
 import accord.local.Command;
 import accord.local.Node;
+import accord.local.PreLoadContext;
 import accord.messages.CheckStatus.CheckStatusOkFull;
 import accord.topology.Shard;
 import accord.primitives.Keys;
@@ -68,7 +69,7 @@ public class CheckOnUncommitted extends CheckOnCommitted
         {
             default: throw new IllegalStateException();
             case Invalidated:
-                node.forEachLocalSince(someKeys, txnId.epoch, commandStore -> {
+                node.forEachLocalSince(PreLoadContext.contextFor(txnId), someKeys, txnId.epoch, commandStore -> {
                     Command command = commandStore.ifPresent(txnId);
                     if (command != null)
                         command.commitInvalidate();
@@ -78,7 +79,7 @@ public class CheckOnUncommitted extends CheckOnCommitted
                 break;
             case PreAccepted:
             case Accepted:
-                node.forEachLocalSince(full.txn.keys, txnId.epoch, commandStore -> {
+                node.forEachLocalSince(PreLoadContext.contextFor(txnId), full.txn.keys(), txnId.epoch, commandStore -> {
                     Command command = commandStore.ifPresent(txnId);
                     if (command != null)
                         command.homeKey(full.homeKey);
diff --git a/accord-core/src/main/java/accord/coordinate/Execute.java b/accord-core/src/main/java/accord/coordinate/Execute.java
index ddfb1b5..6780436 100644
--- a/accord-core/src/main/java/accord/coordinate/Execute.java
+++ b/accord-core/src/main/java/accord/coordinate/Execute.java
@@ -37,6 +37,7 @@ import accord.local.Node.Id;
 import accord.messages.Commit;
 import accord.messages.ReadData;
 import accord.messages.ReadData.ReadOk;
+import com.google.common.base.Preconditions;
 
 class Execute implements Callback<ReadReply>
 {
@@ -61,7 +62,7 @@ class Execute implements Callback<ReadReply>
         this.executeAt = executeAt;
         this.deps = deps;
         this.topologies = node.topology().forEpoch(txn, executeAt.epoch);
-        Topologies readTopologies = node.topology().forEpoch(txn.read.keys(), executeAt.epoch);
+        Topologies readTopologies = node.topology().forEpoch(txn.read().keys(), executeAt.epoch);
         this.readTracker = new ReadTracker(readTopologies);
         this.callback = callback;
     }
@@ -70,6 +71,12 @@ class Execute implements Callback<ReadReply>
     {
         Set<Id> readSet = readTracker.computeMinimalReadSetAndMarkInflight();
         Commit.commitAndRead(node, topologies, txnId, txn, homeKey, executeAt, deps, readSet, this);
+        // skip straight to persistence if there's no read
+        if (txn.read().keys().isEmpty())
+        {
+            Preconditions.checkState(readTracker.hasCompletedRead());
+            persist();
+        }
     }
 
     public static void execute(Node node, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, BiConsumer<Result, Throwable> callback)
@@ -78,15 +85,20 @@ class Execute implements Callback<ReadReply>
         execute.start();
     }
 
+    private void persist()
+    {
+        isDone = true;
+        Result result = txn.result(data);
+        callback.accept(result, null);
+        Persist.persist(node, topologies, txnId, homeKey, txn, executeAt, deps, txn.execute(executeAt, data), result);
+    }
+
     @Override
     public void onSuccess(Id from, ReadReply reply)
     {
         if (isDone)
             return;
 
-        if (!reply.isFinal())
-            return;
-
         if (!reply.isOK())
         {
             isDone = true;
@@ -100,12 +112,7 @@ class Execute implements Callback<ReadReply>
         readTracker.recordReadSuccess(from);
 
         if (readTracker.hasCompletedRead())
-        {
-            isDone = true;
-            Result result = txn.result(data);
-            callback.accept(result, null);
-            Persist.persist(node, topologies, txnId, homeKey, txn, executeAt, deps, txn.execute(executeAt, data), result);
-        }
+            persist();
     }
 
     @Override
@@ -113,7 +120,7 @@ class Execute implements Callback<ReadReply>
     {
         Set<Id> readFrom = readTracker.computeMinimalReadSetAndMarkInflight();
         if (readFrom != null)
-            node.send(readFrom, to -> new ReadData(to, readTracker.topologies(), txnId, txn, homeKey, executeAt), this);
+            node.send(readFrom, to -> new ReadData(to, readTracker.topologies(), txnId, txn, deps, homeKey, executeAt), this);
     }
 
     @Override
@@ -133,7 +140,7 @@ class Execute implements Callback<ReadReply>
         Set<Id> readFrom = readTracker.computeMinimalReadSetAndMarkInflight();
         if (readFrom != null)
         {
-            node.send(readFrom, to -> new ReadData(to, readTracker.topologies(), txnId, txn, homeKey, executeAt), this);
+            node.send(readFrom, to -> new ReadData(to, readTracker.topologies(), txnId, txn, deps, homeKey, executeAt), this);
         }
         else if (readTracker.hasFailed())
         {
diff --git a/accord-core/src/main/java/accord/coordinate/Invalidate.java b/accord-core/src/main/java/accord/coordinate/Invalidate.java
index 3d44d4d..1f4e668 100644
--- a/accord-core/src/main/java/accord/coordinate/Invalidate.java
+++ b/accord-core/src/main/java/accord/coordinate/Invalidate.java
@@ -30,6 +30,7 @@ import accord.coordinate.Invalidate.Outcome;
 import accord.coordinate.tracking.AbstractQuorumTracker.QuorumShardTracker;
 import accord.local.Node;
 import accord.local.Node.Id;
+import accord.local.PreLoadContext;
 import accord.messages.BeginInvalidate;
 import accord.messages.BeginInvalidate.InvalidateNack;
 import accord.messages.BeginInvalidate.InvalidateOk;
@@ -90,9 +91,9 @@ public class Invalidate extends AsyncFuture<Outcome> implements Callback<Recover
             InvalidateNack nack = (InvalidateNack) response;
             if (nack.homeKey != null && nack.txn != null)
             {
-                Key progressKey = node.trySelectProgressKey(txnId.epoch, nack.txn.keys, nack.homeKey);
+                Key progressKey = node.trySelectProgressKey(txnId.epoch, nack.txn.keys(), nack.homeKey);
                 // TODO: consider limiting epoch upper bound we process this status for
-                node.ifLocalSince(someKey, txnId, instance -> {
+                node.ifLocalSince(PreLoadContext.contextFor(txnId, nack.txn.keys()), someKey, txnId, instance -> {
                     instance.command(txnId).preaccept(nack.txn, nack.homeKey, progressKey);
                     return null;
                 });
@@ -100,7 +101,7 @@ public class Invalidate extends AsyncFuture<Outcome> implements Callback<Recover
             else if (nack.homeKey != null)
             {
                 // TODO: consider limiting epoch upper bound we process this status for
-                node.ifLocalSince(someKey, txnId, instance -> {
+                node.ifLocalSince(PreLoadContext.contextFor(txnId), someKey, txnId, instance -> {
                     instance.command(txnId).homeKey(nack.homeKey);
                     return null;
                 });
@@ -172,7 +173,7 @@ public class Invalidate extends AsyncFuture<Outcome> implements Callback<Recover
             try
             {
                 Commit.commitInvalidate(node, txnId, someKeys, txnId);
-                node.forEachLocalSince(someKeys, txnId, instance -> {
+                node.forEachLocalSince(PreLoadContext.contextFor(txnId, someKeys), someKeys, txnId, instance -> {
                     instance.command(txnId).commitInvalidate();
                 });
             }
diff --git a/accord-core/src/main/java/accord/coordinate/Recover.java b/accord-core/src/main/java/accord/coordinate/Recover.java
index 2ca4572..397cad3 100644
--- a/accord-core/src/main/java/accord/coordinate/Recover.java
+++ b/accord-core/src/main/java/accord/coordinate/Recover.java
@@ -254,7 +254,7 @@ public class Recover extends AsyncFuture<Result> implements Callback<RecoverRepl
                         else
                         {
                             tryFailure(new Invalidated());
-                            Commit.commitInvalidate(node, txnId, txn.keys, recoverOks.stream().map(ok -> ok.executeAt).reduce(txnId, Timestamp::max));
+                            Commit.commitInvalidate(node, txnId, txn.keys(), recoverOks.stream().map(ok -> ok.executeAt).reduce(txnId, Timestamp::max));
                         }
                     });
                     return;
@@ -270,7 +270,7 @@ public class Recover extends AsyncFuture<Result> implements Callback<RecoverRepl
                 case Invalidated:
                     Timestamp invalidateUntil = recoverOks.stream().map(ok -> ok.executeAt).reduce(txnId, Timestamp::max);
                     node.withEpoch(invalidateUntil.epoch, () -> {
-                        Commit.commitInvalidate(node, txnId, txn.keys, invalidateUntil);
+                        Commit.commitInvalidate(node, txnId, txn.keys(), invalidateUntil);
                     });
                     tryFailure(new Invalidated());
                     return;
diff --git a/accord-core/src/main/java/accord/impl/InMemoryCommand.java b/accord-core/src/main/java/accord/impl/InMemoryCommand.java
new file mode 100644
index 0000000..7a5beb4
--- /dev/null
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommand.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.impl;
+
+import accord.api.Key;
+import accord.api.Result;
+import accord.local.*;
+import accord.primitives.Ballot;
+import accord.primitives.Deps;
+import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
+import accord.txn.*;
+
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import static accord.local.Status.NotWitnessed;
+
+public class InMemoryCommand extends Command
+{
+    public final CommandStore commandStore;
+    private final TxnId txnId;
+
+    private Key homeKey, progressKey;
+    private Txn txn;
+    private Ballot promised = Ballot.ZERO, accepted = Ballot.ZERO;
+    private Timestamp executeAt;
+    private Deps deps = Deps.NONE;
+    private Writes writes;
+    private Result result;
+
+    private Status status = NotWitnessed;
+
+    private boolean isGloballyPersistent; // only set on home shard
+
+    private NavigableMap<TxnId, PartialCommand> waitingOnCommit;
+    private NavigableMap<TxnId, PartialCommand> waitingOnApply;
+
+    private final Listeners listeners = new Listeners();
+
+    public InMemoryCommand(CommandStore commandStore, TxnId txnId)
+    {
+        this.commandStore = commandStore;
+        this.txnId = txnId;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        InMemoryCommand command = (InMemoryCommand) o;
+        return commandStore == command.commandStore
+                && txnId.equals(command.txnId)
+                && Objects.equals(homeKey, command.homeKey)
+                && Objects.equals(progressKey, command.progressKey)
+                && Objects.equals(txn, command.txn)
+                && promised.equals(command.promised)
+                && accepted.equals(command.accepted)
+                && Objects.equals(executeAt, command.executeAt)
+                && deps.equals(command.deps)
+                && Objects.equals(writes, command.writes)
+                && Objects.equals(result, command.result)
+                && status == command.status
+                && isGloballyPersistent == command.isGloballyPersistent
+                && Objects.equals(waitingOnCommit, command.waitingOnCommit)
+                && Objects.equals(waitingOnApply, command.waitingOnApply)
+                && Objects.equals(listeners, command.listeners);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash(commandStore, txnId, txn, promised, accepted, executeAt, deps, writes, result, status, waitingOnCommit, waitingOnApply, listeners);
+    }
+
+    @Override
+    public TxnId txnId()
+    {
+        return txnId;
+    }
+
+    @Override
+    public CommandStore commandStore()
+    {
+        return commandStore;
+    }
+
+    @Override
+    public Key homeKey()
+    {
+        return homeKey;
+    }
+
+    @Override
+    protected void setHomeKey(Key key)
+    {
+        this.homeKey = key;
+    }
+
+    @Override
+    public Key progressKey()
+    {
+        return progressKey;
+    }
+
+    @Override
+    protected void setProgressKey(Key key)
+    {
+        this.progressKey = key;
+    }
+
+    @Override
+    public Txn txn()
+    {
+        return txn;
+    }
+
+    @Override
+    protected void setTxn(Txn txn)
+    {
+        this.txn = txn;
+    }
+
+    @Override
+    public Ballot promised()
+    {
+        return promised;
+    }
+
+    @Override
+    public void promised(Ballot ballot)
+    {
+        this.promised = ballot;
+    }
+
+    @Override
+    public Ballot accepted()
+    {
+        return accepted;
+    }
+
+    @Override
+    public void accepted(Ballot ballot)
+    {
+        this.accepted = ballot;
+    }
+
+    @Override
+    public Timestamp executeAt()
+    {
+        return executeAt;
+    }
+
+    @Override
+    public void executeAt(Timestamp timestamp)
+    {
+        this.executeAt = timestamp;
+    }
+
+    @Override
+    public Deps savedDeps()
+    {
+        return deps;
+    }
+
+    @Override
+    public void savedDeps(Deps deps)
+    {
+        this.deps = deps;
+    }
+
+    @Override
+    public Writes writes()
+    {
+        return writes;
+    }
+
+    @Override
+    public void writes(Writes writes)
+    {
+        this.writes = writes;
+    }
+
+    @Override
+    public Result result()
+    {
+        return result;
+    }
+
+    @Override
+    public void result(Result result)
+    {
+        this.result = result;
+    }
+
+    @Override
+    public Status status()
+    {
+        return status;
+    }
+
+    @Override
+    public void status(Status status)
+    {
+        this.status = status;
+    }
+
+    @Override
+    public boolean isGloballyPersistent()
+    {
+        return isGloballyPersistent;
+    }
+
+    @Override
+    public void isGloballyPersistent(boolean v)
+    {
+        isGloballyPersistent = v;
+    }
+
+    @Override
+    public Command addListener(Listener listener)
+    {
+        listeners.add(listener);
+        return this;
+    }
+
+    @Override
+    public void removeListener(Listener listener)
+    {
+        listeners.remove(listener);
+    }
+
+    @Override
+    public void notifyListeners()
+    {
+        listeners.forEach(this);
+    }
+
+    @Override
+    public void addWaitingOnCommit(Command command)
+    {
+        if (waitingOnCommit == null)
+            waitingOnCommit = new TreeMap<>();
+
+        waitingOnCommit.put(command.txnId(), command);
+    }
+
+    @Override
+    public boolean isWaitingOnCommit()
+    {
+        return waitingOnCommit != null && !waitingOnCommit.isEmpty();
+    }
+
+    @Override
+    public void removeWaitingOnCommit(PartialCommand command)
+    {
+        if (waitingOnCommit == null)
+            return;
+        waitingOnCommit.remove(command.txnId());
+    }
+
+    @Override
+    public PartialCommand firstWaitingOnCommit()
+    {
+        return isWaitingOnCommit() ? waitingOnCommit.firstEntry().getValue() : null;
+    }
+
+    @Override
+    public void addWaitingOnApplyIfAbsent(PartialCommand command)
+    {
+        if (waitingOnApply == null)
+            waitingOnApply = new TreeMap<>();
+
+        waitingOnApply.putIfAbsent(command.txnId(), command);
+    }
+
+    @Override
+    public boolean isWaitingOnApply()
+    {
+        return waitingOnApply != null && !waitingOnApply.isEmpty();
+    }
+
+    @Override
+    public void removeWaitingOnApply(PartialCommand command)
+    {
+        if (waitingOnApply == null)
+            return;
+        waitingOnApply.remove(command.txnId());
+    }
+
+    @Override
+    public PartialCommand firstWaitingOnApply()
+    {
+        return isWaitingOnApply() ? waitingOnApply.firstEntry().getValue() : null;
+    }
+}
diff --git a/accord-core/src/main/java/accord/local/CommandStore.java b/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
similarity index 55%
copy from accord-core/src/main/java/accord/local/CommandStore.java
copy to accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
index 06b934a..54cdd0a 100644
--- a/accord-core/src/main/java/accord/local/CommandStore.java
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommandStore.java
@@ -16,96 +16,61 @@
  * limitations under the License.
  */
 
-package accord.local;
+package accord.impl;
 
 import accord.api.Agent;
+import accord.api.DataStore;
 import accord.api.Key;
-import accord.local.CommandStores.ShardedRanges;
 import accord.api.ProgressLog;
+import accord.local.Command;
+import accord.local.CommandStore;
+import accord.local.CommandsForKey;
+import accord.local.Node;
 import accord.primitives.KeyRange;
-import accord.api.DataStore;
 import accord.primitives.KeyRanges;
-import accord.primitives.Keys;
 import accord.primitives.Timestamp;
 import accord.primitives.TxnId;
+import accord.local.*;
 import org.apache.cassandra.utils.concurrent.AsyncPromise;
 import org.apache.cassandra.utils.concurrent.Future;
 import org.apache.cassandra.utils.concurrent.Promise;
 
-import com.google.common.base.Preconditions;
-
-import java.util.*;
-import java.util.concurrent.*;
+import java.util.Collection;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.LongSupplier;
+import java.util.stream.Collectors;
 
-import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
-
-/**
- * Single threaded internal shard of accord transaction metadata
- */
-public abstract class CommandStore
+public abstract class InMemoryCommandStore extends CommandStore
 {
-    public interface Factory
-    {
-        CommandStore create(int generation,
-                            int shardIndex,
-                            int numShards,
-                            Node node,
-                            Agent agent,
-                            DataStore store,
-                            ProgressLog.Factory progressLogFactory,
-                            RangesForEpoch rangesForEpoch);
-    }
+    private final NavigableMap<TxnId, Command> commands = new TreeMap<>();
+    private final NavigableMap<Key, CommandsForKey> commandsForKey = new TreeMap<>();
 
-    public interface RangesForEpoch
+    public static InMemoryCommandStore inMemory(CommandStore commandStore)
     {
-        KeyRanges at(long epoch);
-        KeyRanges since(long epoch);
-        boolean intersects(long epoch, Keys keys);
+        return (InMemoryCommandStore) commandStore;
     }
 
-    private final int generation;
-    private final int shardIndex;
-    private final int numShards;
-    private final Node node;
-    private final Agent agent;
-    private final DataStore store;
-    private final ProgressLog progressLog;
-    private final RangesForEpoch rangesForEpoch;
-
-    private final NavigableMap<TxnId, Command> commands = new TreeMap<>();
-    private final NavigableMap<Key, CommandsForKey> commandsForKey = new TreeMap<>();
-
-    public CommandStore(int generation,
-                        int shardIndex,
-                        int numShards,
-                        Node node,
-                        Agent agent,
-                        DataStore store,
-                        ProgressLog.Factory progressLogFactory,
-                        RangesForEpoch rangesForEpoch)
+    public InMemoryCommandStore(int generation, int index, int numShards, Function<Timestamp, Timestamp> uniqueNow, LongSupplier currentEpoch, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, RangesForEpoch rangesForEpoch)
     {
-        Preconditions.checkArgument(shardIndex < numShards);
-        this.generation = generation;
-        this.shardIndex = shardIndex;
-        this.numShards = numShards;
-        this.node = node;
-        this.agent = agent;
-        this.store = store;
-        this.progressLog = progressLogFactory.create(this);
-        this.rangesForEpoch = rangesForEpoch;
+        super(generation, index, numShards, uniqueNow, currentEpoch, agent, store, progressLogFactory, rangesForEpoch);
     }
 
+    @Override
     public Command ifPresent(TxnId txnId)
     {
         return commands.get(txnId);
     }
 
+    @Override
     public Command command(TxnId txnId)
     {
-        return commands.computeIfAbsent(txnId, id -> new Command(this, id));
+        return commands.computeIfAbsent(txnId, id -> new InMemoryCommand(this, id));
     }
 
     public boolean hasCommand(TxnId txnId)
@@ -113,14 +78,10 @@ public abstract class CommandStore
         return commands.containsKey(txnId);
     }
 
+    @Override
     public CommandsForKey commandsForKey(Key key)
     {
-        return commandsForKey.computeIfAbsent(key, ignore -> new CommandsForKey());
-    }
-
-    public CommandsForKey maybeCommandsForKey(Key key)
-    {
-        return commandsForKey.get(key);
+        return commandsForKey.computeIfAbsent(key, InMemoryCommandsForKey::new);
     }
 
     public boolean hasCommandsForKey(Key key)
@@ -128,50 +89,10 @@ public abstract class CommandStore
         return commandsForKey.containsKey(key);
     }
 
-    public DataStore store()
-    {
-        return store;
-    }
-
-    public Timestamp uniqueNow(Timestamp atLeast)
-    {
-        return node.uniqueNow(atLeast);
-    }
-
-    public Agent agent()
-    {
-        return agent;
-    }
-
-    public ProgressLog progressLog()
-    {
-        return progressLog;
-    }
-
-    public Node node()
-    {
-        return node;
-    }
-
-    public RangesForEpoch ranges()
-    {
-        return rangesForEpoch;
-    }
-
-    public long latestEpoch()
-    {
-        // TODO: why not inject the epoch to each command store?
-        return node.epoch();
-    }
-
-    protected Timestamp maxConflict(Keys keys)
+    @Override
+    public CommandsForKey maybeCommandsForKey(Key key)
     {
-        return keys.stream()
-                   .map(this::maybeCommandsForKey)
-                   .filter(Objects::nonNull)
-                   .map(CommandsForKey::max)
-                   .max(Comparator.naturalOrder())
-                   .orElse(Timestamp.NONE);
+        return commandsForKey.get(key);
     }
 
     public void forEpochCommands(KeyRanges ranges, long epoch, Consumer<Command> consumer)
@@ -186,7 +107,7 @@ public abstract class CommandStore
                                                                            range.endInclusive()).values();
             for (CommandsForKey commands : rangeCommands)
             {
-                commands.forWitnessed(minTimestamp, maxTimestamp, consumer);
+                commands.forWitnessed(minTimestamp, maxTimestamp, cmd -> consumer.accept((Command) cmd));
             }
         }
     }
@@ -204,36 +125,27 @@ public abstract class CommandStore
             for (CommandsForKey commands : rangeCommands)
             {
 
-                Collection<Command> committed = commands.committedByExecuteAt.subMap(minTimestamp,
-                                                                                     true,
-                                                                                     maxTimestamp,
-                                                                                     true).values();
+                Collection<Command> committed = commands.committedByExecuteAt()
+                        .between(minTimestamp, maxTimestamp).map(cmd -> (Command) cmd).collect(Collectors.toList());
                 committed.forEach(consumer);
             }
         }
     }
 
-    public boolean hashIntersects(Key key)
-    {
-        return ShardedRanges.keyIndex(key, numShards) == shardIndex;
-    }
-
-    public boolean intersects(Keys keys, KeyRanges ranges)
+    protected void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> promise)
     {
-        return keys.any(ranges, this::hashIntersects);
+        processInternal(cs -> {
+            consumer.accept(cs);
+            return null;
+        }, promise);
     }
 
-    public static void onEach(Collection<CommandStore> stores, Consumer<? super CommandStore> consumer)
-    {
-        for (CommandStore store : stores)
-            store.process(consumer);
-    }
-
-    <R> void processInternal(Function<? super CommandStore, R> function, Promise<R> promise)
+    protected <T> void processInternal(Function<? super CommandStore, T> function, Promise<T> promise)
     {
         try
         {
-            promise.setSuccess(function.apply(this));
+            T result = function.apply(this);
+            promise.setSuccess(result);
         }
         catch (Throwable e)
         {
@@ -241,61 +153,47 @@ public abstract class CommandStore
         }
     }
 
-    void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> promise)
+    public static class Synchronized extends InMemoryCommandStore
     {
-        try
-        {
-            consumer.accept(this);
-            promise.setSuccess(null);
-        }
-        catch (Throwable e)
+        public Synchronized(int generation,
+                            int index,
+                            int numShards,
+                            Function<Timestamp, Timestamp> uniqueNow,
+                            LongSupplier currentEpoch,
+                            Agent agent,
+                            DataStore store,
+                            ProgressLog.Factory progressLogFactory,
+                            RangesForEpoch rangesForEpoch)
         {
-            promise.tryFailure(e);
+            super(generation, index, numShards, uniqueNow, currentEpoch, agent, store, progressLogFactory, rangesForEpoch);
         }
-    }
-
-    public abstract Future<Void> process(Consumer<? super CommandStore> consumer);
 
-    public abstract <T> Future<T> process(Function<? super CommandStore, T> function);
-
-    public void processBlocking(Consumer<? super CommandStore> consumer)
-    {
-        try
-        {
-            process(consumer).get();
-        }
-        catch (InterruptedException e)
-        {
-            throw new UncheckedInterruptedException(e);
-        }
-        catch (ExecutionException e)
+        @Override
+        public synchronized Future<Void> processSetup(Consumer<? super CommandStore> function)
         {
-            throw new RuntimeException(e.getCause());
+            AsyncPromise<Void> promise = new AsyncPromise<>();
+            processInternal(function, promise);
+            return promise;
         }
-    }
 
-    public abstract void shutdown();
-
-    public static class Synchronized extends CommandStore
-    {
-        public Synchronized(int generation, int index, int numShards, Node node,
-                            Agent agent, DataStore store,
-                            ProgressLog.Factory progressLogFactory,
-                            RangesForEpoch rangesForEpoch)
+        @Override
+        public synchronized <T> Future<T> processSetup(Function<? super CommandStore, T> function)
         {
-            super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
+            AsyncPromise<T> promise = new AsyncPromise<>();
+            processInternal(function, promise);
+            return promise;
         }
 
         @Override
-        public synchronized Future<Void> process(Consumer<? super CommandStore> consumer)
+        public synchronized Future<Void> process(PreLoadContext context, Consumer<? super CommandStore> consumer)
         {
-            AsyncPromise<Void> promise = new AsyncPromise<>();
+            Promise<Void> promise = new AsyncPromise<>();
             processInternal(consumer, promise);
             return promise;
         }
 
         @Override
-        public <T> Future<T> process(Function<? super CommandStore, T> function)
+        public synchronized <T> Future<T> process(PreLoadContext context, Function<? super CommandStore, T> function)
         {
             AsyncPromise<T> promise = new AsyncPromise<>();
             processInternal(function, promise);
@@ -303,10 +201,10 @@ public abstract class CommandStore
         }
 
         @Override
-        public void shutdown() {}
+        public synchronized void shutdown() {}
     }
 
-    public static class SingleThread extends CommandStore
+    public static class SingleThread extends InMemoryCommandStore
     {
         private final ExecutorService executor;
 
@@ -342,20 +240,43 @@ public abstract class CommandStore
             }
         }
 
-        public SingleThread(int generation, int index, int numShards, Node node,
-                            Agent agent, DataStore store, ProgressLog.Factory progressLogFactory,
+        public SingleThread(int generation,
+                            int index,
+                            int numShards,
+                            Node.Id nodeId,
+                            Function<Timestamp, Timestamp> uniqueNow,
+                            LongSupplier currentEpoch,
+                            Agent agent,
+                            DataStore store,
+                            ProgressLog.Factory progressLogFactory,
                             RangesForEpoch rangesForEpoch)
         {
-            super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
+            super(generation, index, numShards, uniqueNow, currentEpoch, agent, store, progressLogFactory, rangesForEpoch);
             executor = Executors.newSingleThreadExecutor(r -> {
                 Thread thread = new Thread(r);
-                thread.setName(CommandStore.class.getSimpleName() + '[' + node.id() + ':' + index + ']');
+                thread.setName(CommandStore.class.getSimpleName() + '[' + nodeId + ':' + index + ']');
                 return thread;
             });
         }
 
         @Override
-        public Future<Void> process(Consumer<? super CommandStore> consumer)
+        public Future<Void> processSetup(Consumer<? super CommandStore> function)
+        {
+            ConsumerWrapper future = new ConsumerWrapper(function);
+            executor.execute(future);
+            return future;
+        }
+
+        @Override
+        public <T> Future<T> processSetup(Function<? super CommandStore, T> function)
+        {
+            FunctionWrapper<T> future = new FunctionWrapper<>(function);
+            executor.execute(future);
+            return future;
+        }
+
+        @Override
+        public Future<Void> process(PreLoadContext context, Consumer<? super CommandStore> consumer)
         {
             ConsumerWrapper future = new ConsumerWrapper(consumer);
             executor.execute(future);
@@ -363,7 +284,7 @@ public abstract class CommandStore
         }
 
         @Override
-        public <T> Future<T> process(Function<? super CommandStore, T> function)
+        public <T> Future<T> process(PreLoadContext context, Function<? super CommandStore, T> function)
         {
             FunctionWrapper<T> future = new FunctionWrapper<>(function);
             executor.execute(future);
@@ -377,15 +298,22 @@ public abstract class CommandStore
         }
     }
 
-    static class Debug extends SingleThread
+    public static class SingleThreadDebug extends SingleThread
     {
         private final AtomicReference<Thread> expectedThread = new AtomicReference<>();
 
-        public Debug(int generation, int index, int numShards, Node node,
-                     Agent agent, DataStore store, ProgressLog.Factory progressLogFactory,
-                     RangesForEpoch rangesForEpoch)
+        public SingleThreadDebug(int generation,
+                                 int index,
+                                 int numShards,
+                                 Node.Id nodeId,
+                                 Function<Timestamp, Timestamp> uniqueNow,
+                                 LongSupplier currentEpoch,
+                                 Agent agent,
+                                 DataStore store,
+                                 ProgressLog.Factory progressLogFactory,
+                                 RangesForEpoch rangesForEpoch)
         {
-            super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
+            super(generation, index, numShards, nodeId, uniqueNow, currentEpoch, agent, store, progressLogFactory, rangesForEpoch);
         }
 
         private void assertThread()
@@ -399,7 +327,8 @@ public abstract class CommandStore
                     break;
                 expectedThread.compareAndSet(null, Thread.currentThread());
             }
-            Preconditions.checkState(expected == current);
+            if (expected != current)
+                throw new IllegalStateException(String.format("Command store called from the wrong thread. Expected %s, got %s", expected, current));
         }
 
         @Override
@@ -431,18 +360,10 @@ public abstract class CommandStore
         }
 
         @Override
-        <R> void processInternal(Function<? super CommandStore, R> function, Promise<R> future)
+        protected void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> promise)
         {
             assertThread();
-            super.processInternal(function, future);
-        }
-
-        @Override
-        void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> future)
-        {
-            assertThread();
-            super.processInternal(consumer, future);
+            super.processInternal(consumer, promise);
         }
     }
-
 }
diff --git a/accord-core/src/main/java/accord/impl/InMemoryCommandStores.java b/accord-core/src/main/java/accord/impl/InMemoryCommandStores.java
new file mode 100644
index 0000000..f597ce0
--- /dev/null
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommandStores.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.impl;
+
+import accord.api.Agent;
+import accord.api.DataStore;
+import accord.api.ProgressLog;
+import accord.local.CommandStore;
+import accord.local.CommandStores;
+import accord.local.Node;
+import accord.primitives.Keys;
+
+import java.util.function.Consumer;
+
+import static java.lang.Boolean.FALSE;
+
+public abstract class InMemoryCommandStores extends CommandStores
+{
+    public InMemoryCommandStores(int num, Node node, Agent agent, DataStore store,
+                                 ProgressLog.Factory progressLogFactory)
+    {
+        super(num, node, agent, store, progressLogFactory);
+    }
+
+    public static InMemoryCommandStores inMemory(Node node)
+    {
+        return (InMemoryCommandStores) node.commandStores();
+    }
+
+    public void forEachLocal(Consumer<? super CommandStore> forEach)
+    {
+        foldl((ranges, o, minEpoch, maxEpoch) -> ranges.all(),
+              null, Long.MIN_VALUE, Long.MAX_VALUE,
+              (store, f, r, t) -> { f.accept(store); return null; }, forEach, null, ignore -> FALSE);
+    }
+
+    public void forEachLocal(Keys keys, long minEpoch, long maxEpoch, Consumer<? super CommandStore> forEach)
+    {
+        foldl(ShardedRanges::shards, keys, minEpoch, maxEpoch, (store, f, r, t) -> { f.accept(store); return null; }, forEach, null, ignore -> FALSE);
+    }
+
+    public void forEachLocal(Keys keys, long epoch, Consumer<? super CommandStore> forEach)
+    {
+        forEachLocal(keys, epoch, epoch, forEach);
+    }
+
+    public void forEachLocalSince(Keys keys, long epoch, Consumer<? super CommandStore> forEach)
+    {
+        forEachLocal(keys, epoch, Long.MAX_VALUE, forEach);
+    }
+
+    public static class Synchronized extends InMemoryCommandStores
+    {
+        public Synchronized(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
+        {
+            super(num, node, agent, store, progressLogFactory);
+        }
+
+        @Override
+        protected CommandStore createCommandStore(int generation, int index, int numShards, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.RangesForEpoch rangesForEpoch)
+        {
+            return new InMemoryCommandStore.Synchronized(generation, index, numShards, node::uniqueNow, node.topology()::epoch, agent, store, progressLogFactory, rangesForEpoch);
+        }
+    }
+
+    public static class SingleThread extends InMemoryCommandStores
+    {
+        public SingleThread(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
+        {
+            super(num, node, agent, store, progressLogFactory);
+        }
+
+        @Override
+        protected CommandStore createCommandStore(int generation, int index, int numShards, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.RangesForEpoch rangesForEpoch)
+        {
+            return new InMemoryCommandStore.SingleThread(generation, index, numShards, node.id(), node::uniqueNow, node.topology()::epoch, agent, store, progressLogFactory, rangesForEpoch);
+        }
+    }
+
+    public static class Debug extends InMemoryCommandStores.SingleThread
+    {
+        public Debug(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
+        {
+            super(num, node, agent, store, progressLogFactory);
+        }
+
+        @Override
+        protected CommandStore createCommandStore(int generation, int index, int numShards, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.RangesForEpoch rangesForEpoch)
+        {
+            return new InMemoryCommandStore.SingleThreadDebug(generation, index, numShards, node.id(), node::uniqueNow, node.topology()::epoch, agent, store, progressLogFactory, rangesForEpoch);
+        }
+    }
+
+}
diff --git a/accord-core/src/main/java/accord/impl/InMemoryCommandsForKey.java b/accord-core/src/main/java/accord/impl/InMemoryCommandsForKey.java
new file mode 100644
index 0000000..b4902ef
--- /dev/null
+++ b/accord-core/src/main/java/accord/impl/InMemoryCommandsForKey.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.impl;
+
+import accord.api.Key;
+import accord.local.Command;
+import accord.local.CommandsForKey;
+import accord.local.PartialCommand;
+import accord.primitives.Timestamp;
+
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+
+public class InMemoryCommandsForKey extends CommandsForKey
+{
+    static class InMemoryCommandTimeseries implements CommandTimeseries
+    {
+        private final NavigableMap<Timestamp, Command> commands = new TreeMap<>();
+
+        @Override
+        public PartialCommand.WithDeps get(Timestamp timestamp)
+        {
+            return commands.get(timestamp);
+        }
+
+        @Override
+        public void add(Timestamp timestamp, Command command)
+        {
+            if (commands.containsKey(timestamp) && !commands.get(timestamp).equals(command))
+                throw new IllegalStateException(String.format("Attempting to overwrite command at timestamp %s %s with %s.",
+                                                              timestamp, commands.get(timestamp), command));
+
+            commands.put(timestamp, command);
+        }
+
+        @Override
+        public void remove(Timestamp timestamp)
+        {
+            commands.remove(timestamp);
+        }
+
+        @Override
+        public boolean isEmpty()
+        {
+            return commands.isEmpty();
+        }
+
+        @Override
+        public Stream<PartialCommand.WithDeps> before(Timestamp timestamp)
+        {
+            return commands.headMap(timestamp, false).values().stream().map(cmd -> cmd);
+        }
+
+        @Override
+        public Stream<PartialCommand.WithDeps> after(Timestamp timestamp)
+        {
+            return commands.tailMap(timestamp, false).values().stream().map(cmd -> cmd);
+        }
+
+        @Override
+        public Stream<PartialCommand.WithDeps> between(Timestamp min, Timestamp max)
+        {
+            return commands.subMap(min, true, max, true).values().stream().map(cmd -> cmd);
+        }
+
+        @Override
+        public Stream<PartialCommand.WithDeps> all()
+        {
+            return commands.values().stream().map(cmd -> cmd);
+        }
+    }
+
+    private final Key key;
+    private final InMemoryCommandTimeseries uncommitted = new InMemoryCommandTimeseries();
+    private final InMemoryCommandTimeseries committedById = new InMemoryCommandTimeseries();
+    private final InMemoryCommandTimeseries committedByExecuteAt = new InMemoryCommandTimeseries();
+
+    private Timestamp max = Timestamp.NONE;
+
+    public InMemoryCommandsForKey(Key key)
+    {
+        this.key = key;
+    }
+
+    @Override
+    public Key key()
+    {
+        return key;
+    }
+
+    @Override
+    public Timestamp max()
+    {
+        return max;
+    }
+
+    @Override
+    public void updateMax(Timestamp timestamp)
+    {
+        max = Timestamp.max(max, timestamp);
+    }
+
+    @Override
+    public CommandTimeseries uncommitted()
+    {
+        return uncommitted;
+    }
+
+    @Override
+    public CommandTimeseries committedById()
+    {
+        return committedById;
+    }
+
+    @Override
+    public CommandTimeseries committedByExecuteAt()
+    {
+        return committedByExecuteAt;
+    }
+}
diff --git a/accord-core/src/main/java/accord/impl/SimpleProgressLog.java b/accord-core/src/main/java/accord/impl/SimpleProgressLog.java
index fd49324..9b9f013 100644
--- a/accord-core/src/main/java/accord/impl/SimpleProgressLog.java
+++ b/accord-core/src/main/java/accord/impl/SimpleProgressLog.java
@@ -18,6 +18,7 @@
 
 package accord.impl;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -28,6 +29,7 @@ import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Nullable;
 
+import accord.local.*;
 import com.google.common.base.Preconditions;
 
 import accord.api.Key;
@@ -36,11 +38,7 @@ import accord.api.Result;
 import accord.coordinate.CheckOnCommitted;
 import accord.coordinate.Invalidate;
 import accord.impl.SimpleProgressLog.HomeState.LocalStatus;
-import accord.local.Command;
-import accord.local.CommandStore;
-import accord.local.Node;
 import accord.local.Node.Id;
-import accord.local.Status;
 import accord.messages.Apply;
 import accord.messages.Callback;
 import accord.messages.CheckStatus.CheckStatusOk;
@@ -150,7 +148,7 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
 
         Object debugInvestigating;
 
-        void ensureAtLeast(LocalStatus newStatus, Progress newProgress, Node node, Command command)
+        void ensureAtLeast(Command command, LocalStatus newStatus, Progress newProgress, Node node)
         {
             if (newStatus == Committed && global.isAtLeast(Durable) && !command.executes())
             {
@@ -388,15 +386,15 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
         Status blockedOn = Status.NotWitnessed;
         Progress progress = NoneExpected;
         Keys someKeys;
-        Command blocking;
+        PartialCommand blocking;
 
         Object debugInvestigating;
 
-        void recordBlocking(Command blocking, Keys someKeys)
+        void recordBlocking(PartialCommand blocking, Keys someKeys)
         {
             this.blocking = blocking;
-            if (blocking.txn() != null && !blocking.txn().keys.containsAll(someKeys))
-                throw new IllegalStateException(String.format("The transaction does not involve some of the keys on which another transaction has taken a dependency upon it (%s vs %s)", blocking.txn().keys, someKeys));
+            if (blocking.txn() != null && !blocking.txn().keys().containsAll(someKeys))
+                throw new IllegalStateException(String.format("The transaction does not involve some of the keys on which another transaction has taken a dependency upon it (%s vs %s)", blocking.txn().keys(), someKeys));
 
             switch (blocking.status())
             {
@@ -501,7 +499,7 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
                             break;
                         case Committed:
                         case ReadyToExecute:
-                            Preconditions.checkState(command.hasBeen(Status.Committed) || !command.commandStore.ranges().intersects(txnId.epoch, someKeys));
+                            Preconditions.checkState(command.hasBeen(Status.Committed) || !command.commandStore().ranges().intersects(txnId.epoch, someKeys));
                             if (blockedOn == Status.Committed)
                                 progress = NoneExpected;
                             break;
@@ -528,19 +526,19 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
     static class State
     {
         final TxnId txnId;
-        final Command command;
+        final CommandStore commandStore;
 
         HomeState homeState;
         NonHomeState nonHomeState;
         BlockingState blockingState;
 
-        State(TxnId txnId, Command command)
+        State(TxnId txnId, CommandStore commandStore)
         {
             this.txnId = txnId;
-            this.command = command;
+            this.commandStore = commandStore;
         }
 
-        void recordBlocking(Command blockedByCommand, Keys someKeys)
+        void recordBlocking(PartialCommand blockedByCommand, Keys someKeys)
         {
             Preconditions.checkArgument(blockedByCommand.txnId().equals(txnId));
             if (blockingState == null)
@@ -561,12 +559,12 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
             return homeState;
         }
 
-        void ensureAtLeast(LocalStatus newStatus, Progress newProgress, Node node)
+        void ensureAtLeast(Command command, LocalStatus newStatus, Progress newProgress, Node node)
         {
-            home().ensureAtLeast(newStatus, newProgress, node, command);
+            home().ensureAtLeast(command, newStatus, newProgress, node);
         }
 
-        void updateNonHome(Node node)
+        void updateNonHome(Node node, Command command)
         {
             switch (nonHomeState)
             {
@@ -593,14 +591,18 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
 
         void update(Node node)
         {
-            if (blockingState != null)
-                blockingState.update(node, txnId, command);
+            PreLoadContext context = PreLoadContext.contextFor(txnId);
+            commandStore.process(context, cs -> {
+                Command command = cs.command(txnId);
+                if (blockingState != null)
+                    blockingState.update(node, txnId, command);
 
-            if (homeState != null)
-                homeState.update(node, txnId, command);
+                if (homeState != null)
+                    homeState.update(node, txnId, command);
 
-            if (nonHomeState != null)
-                updateNonHome(node);
+                if (nonHomeState != null)
+                    updateNonHome(node, command);
+            });
         }
 
         @Override
@@ -635,7 +637,7 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
 
         State ensure(TxnId txnId)
         {
-            return stateMap.computeIfAbsent(txnId, id -> new State(id, commandStore.command(id)));
+            return stateMap.computeIfAbsent(txnId, id -> new State(id, commandStore));
         }
 
         State ensure(TxnId txnId, State state)
@@ -644,12 +646,12 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
         }
 
         @Override
-        public void preaccept(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+        public void preaccept(Command command, boolean isProgressShard, boolean isHomeShard)
         {
             if (isProgressShard)
             {
-                State state = ensure(txnId);
-                if (isHomeShard) state.ensureAtLeast(Uncommitted, Expected, node);
+                State state = ensure(command.txnId());
+                if (isHomeShard) state.ensureAtLeast(command, Uncommitted, Expected, node);
                 else state.ensureAtLeast(NonHomeState.Unsafe);
             }
         }
@@ -670,79 +672,79 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
             return state;
         }
 
-        private void ensureSafeOrAtLeast(TxnId txnId, boolean isProgressShard, boolean isHomeShard, LocalStatus newStatus, Progress newProgress)
+        private void ensureSafeOrAtLeast(Command command, boolean isProgressShard, boolean isHomeShard, LocalStatus newStatus, Progress newProgress)
         {
             State state = null;
             assert newStatus.isAtMost(ReadyToExecute);
             if (newStatus.isAtLeast(LocalStatus.Committed))
-                state = recordCommit(txnId);
+                state = recordCommit(command.txnId());
 
             if (isProgressShard)
             {
-                state = ensure(txnId, state);
+                state = ensure(command.txnId(), state);
 
-                if (isHomeShard) state.ensureAtLeast(newStatus, newProgress, node);
-                else ensure(txnId).ensureAtLeast(Safe);
+                if (isHomeShard) state.ensureAtLeast(command, newStatus, newProgress, node);
+                else ensure(command.txnId()).ensureAtLeast(Safe);
             }
         }
 
         @Override
-        public void accept(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+        public void accept(Command command, boolean isProgressShard, boolean isHomeShard)
         {
-            ensureSafeOrAtLeast(txnId, isProgressShard, isHomeShard, Uncommitted, Expected);
+            ensureSafeOrAtLeast(command, isProgressShard, isHomeShard, Uncommitted, Expected);
         }
 
         @Override
-        public void commit(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+        public void commit(Command command, boolean isProgressShard, boolean isHomeShard)
         {
-            ensureSafeOrAtLeast(txnId, isProgressShard, isHomeShard, LocalStatus.Committed, NoneExpected);
+            ensureSafeOrAtLeast(command, isProgressShard, isHomeShard, LocalStatus.Committed, NoneExpected);
         }
 
         @Override
-        public void readyToExecute(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+        public void readyToExecute(Command command, boolean isProgressShard, boolean isHomeShard)
         {
-            ensureSafeOrAtLeast(txnId, isProgressShard, isHomeShard, LocalStatus.ReadyToExecute, Expected);
+            ensureSafeOrAtLeast(command, isProgressShard, isHomeShard, LocalStatus.ReadyToExecute, Expected);
         }
 
         @Override
-        public void execute(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+        public void execute(Command command, boolean isProgressShard, boolean isHomeShard)
         {
-            State state = recordExecute(txnId);
+            State state = recordExecute(command.txnId());
 
             if (isProgressShard)
             {
-                state = ensure(txnId, state);
+                state = ensure(command.txnId(), state);
 
-                if (isHomeShard) state.home().executed(node, state.command);
-                else ensure(txnId).ensureAtLeast(Safe);
+                if (isHomeShard) state.home().executed(node, command);
+                else ensure(command.txnId()).ensureAtLeast(Safe);
             }
         }
 
         @Override
-        public void invalidate(TxnId txnId, boolean isProgressShard, boolean isHomeShard)
+        public void invalidate(Command command, boolean isProgressShard, boolean isHomeShard)
         {
-            State state = recordExecute(txnId);
+            State state = recordExecute(command.txnId());
 
             if (isProgressShard)
             {
-                state = ensure(txnId, state);
+                state = ensure(command.txnId(), state);
 
-                if (isHomeShard) state.ensureAtLeast(LocalStatus.Done, Done, node);
-                else ensure(txnId).ensureAtLeast(Safe);
+                if (isHomeShard) state.ensureAtLeast(command, LocalStatus.Done, Done, node);
+                else ensure(command.txnId()).ensureAtLeast(Safe);
             }
         }
 
         @Override
-        public void executedOnAllShards(TxnId txnId, Set<Id> persistedOn)
+        public void executedOnAllShards(Command command, Set<Id> persistedOn)
         {
-            State state = ensure(txnId);
-            state.home().executedOnAllShards(node, state.command, persistedOn);
+            State state = ensure(command.txnId());
+            state.home().executedOnAllShards(node, command, persistedOn);
         }
 
         @Override
-        public void waiting(TxnId blockedBy, Keys someKeys)
+        public void waiting(PartialCommand blockedByCommand, Keys someKeys)
         {
-            Command blockedByCommand = commandStore.command(blockedBy);
+            TxnId blockedBy = blockedByCommand.txnId();
             if (!blockedByCommand.hasBeen(Executed))
                 ensure(blockedBy).recordBlocking(blockedByCommand, someKeys);
         }
@@ -756,20 +758,20 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
             // TODO: we want to be able to poll others about pending dependencies to check forward progress,
             //       as we don't know all dependencies locally (or perhaps any, at execution time) so we may
             //       begin expecting forward progress too early
-            instance.stateMap.values().forEach(state -> state.update(node));
+            // state map may be updated during iteration, so need to clone the values set
+            new ArrayList<>(instance.stateMap.values()).forEach(state -> state.update(node));
         }
     }
 
     static class CoordinateApplyAndCheck extends AsyncFuture<Void> implements Callback<ApplyAndCheckOk>
     {
         final TxnId txnId;
-        final Command command;
         final HomeState state;
         final Set<Id> waitingOnResponses;
 
         static Future<Void> applyAndCheck(Node node, TxnId txnId, Command command, HomeState state)
         {
-            CoordinateApplyAndCheck coordinate = new CoordinateApplyAndCheck(txnId, command, state);
+            CoordinateApplyAndCheck coordinate = new CoordinateApplyAndCheck(txnId, state);
             Topologies topologies = node.topology().preciseEpochs(command.txn(), command.executeAt().epoch);
             state.globalNotPersisted.retainAll(topologies.nodes()); // we might have had some nodes from older shards that are now redundant
             node.send(state.globalNotPersisted, id -> new ApplyAndCheck(id, topologies,
@@ -781,10 +783,9 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
             return coordinate;
         }
 
-        CoordinateApplyAndCheck(TxnId txnId, Command command, HomeState state)
+        CoordinateApplyAndCheck(TxnId txnId, HomeState state)
         {
             this.txnId = txnId;
-            this.command = command;
             this.state = state;
             this.waitingOnResponses = new HashSet<>(state.globalNotPersisted);
         }
@@ -812,18 +813,33 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
 
     public static class ApplyAndCheck extends Apply
     {
-        final Set<Id> notPersisted;
+        public static class SerializerSupport
+        {
+            public static ApplyAndCheck create(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result, Set<Id> notPersisted)
+            {
+                return new ApplyAndCheck(scope, waitForEpoch, txnId, txn, homeKey, executeAt, deps, writes, result, notPersisted);
+            }
+        }
+
+        public final Set<Id> notPersisted;
         ApplyAndCheck(Id id, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Deps deps, Timestamp executeAt, Writes writes, Result result, Set<Id> notPersisted)
         {
             super(id, topologies, txnId, txn, homeKey, executeAt, deps, writes, result);
             this.notPersisted = notPersisted;
         }
 
+        public ApplyAndCheck(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result, Set<Id> notPersisted)
+        {
+            super(scope, waitForEpoch, txnId, txn, homeKey, executeAt, deps, writes, result);
+            this.notPersisted = notPersisted;
+        }
+
         @Override
         public void process(Node node, Id from, ReplyContext replyContext)
         {
-            Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey);
-            node.reply(from, replyContext, node.mapReduceLocalSince(scope(), executeAt, instance -> {
+            Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey);
+            PreLoadContext context = PreLoadContext.contextFor(txnId, txn.keys());
+            node.reply(from, replyContext, node.mapReduceLocalSince(context, scope(), executeAt, instance -> {
                 Command command = instance.command(txnId);
                 command.apply(txn, homeKey, progressKey, executeAt, deps, writes, result);
                 if (homeKey.equals(progressKey) && command.handles(txnId.epoch, progressKey))
@@ -869,9 +885,9 @@ public class SimpleProgressLog implements Runnable, ProgressLog.Factory
 
     public static class ApplyAndCheckOk implements Reply
     {
-        final Set<Id> notPersisted;
+        public final Set<Id> notPersisted;
 
-        ApplyAndCheckOk(Set<Id> notPersisted)
+        public ApplyAndCheckOk(Set<Id> notPersisted)
         {
             this.notPersisted = notPersisted;
         }
diff --git a/accord-core/src/main/java/accord/local/Command.java b/accord-core/src/main/java/accord/local/Command.java
index f9e2d42..65efda4 100644
--- a/accord-core/src/main/java/accord/local/Command.java
+++ b/accord-core/src/main/java/accord/local/Command.java
@@ -18,129 +18,113 @@
 
 package accord.local;
 
-import java.util.NavigableMap;
-import java.util.TreeMap;
-import java.util.function.Consumer;
-
-import com.google.common.base.Preconditions;
-
-import accord.api.Key;
-import accord.api.Result;
+import accord.api.*;
 import accord.local.Node.Id;
-import accord.primitives.KeyRanges;
-import accord.primitives.Ballot;
-import accord.primitives.Deps;
-import accord.primitives.Keys;
-import accord.primitives.Timestamp;
+import accord.primitives.*;
 import accord.txn.Txn;
-import accord.primitives.TxnId;
 import accord.txn.Writes;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import org.apache.cassandra.utils.concurrent.Future;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
-import static accord.local.Status.Accepted;
-import static accord.local.Status.AcceptedInvalidate;
-import static accord.local.Status.Applied;
-import static accord.local.Status.Committed;
-import static accord.local.Status.Executed;
-import static accord.local.Status.Invalidated;
-import static accord.local.Status.NotWitnessed;
-import static accord.local.Status.PreAccepted;
-import static accord.local.Status.ReadyToExecute;
-
-// TODO: this needs to be backed by persistent storage
-public class Command implements Listener, Consumer<Listener>
+import static accord.local.Status.*;
+import static accord.utils.Utils.listOf;
+
+public abstract class Command implements Listener, Consumer<Listener>, PreLoadContext, PartialCommand.WithDeps
 {
-    public final CommandStore commandStore;
-    private final TxnId txnId;
-    private Key homeKey, progressKey;
-    private Txn txn; // TODO: only store this on the home shard, or split to each shard independently
-    private Ballot promised = Ballot.ZERO, accepted = Ballot.ZERO;
-    private Timestamp executeAt; // TODO: compress these states together
-    private Deps deps = Deps.NONE;
-    private Writes writes;
-    private Result result;
+    private static final Logger logger = LoggerFactory.getLogger(Command.class);
 
-    private Status status = NotWitnessed;
-    private boolean isGloballyPersistent; // only set on home shard
+    public abstract CommandStore commandStore();
 
-    private NavigableMap<TxnId, Command> waitingOnCommit;
-    private NavigableMap<Timestamp, Command> waitingOnApply;
+    public abstract Key homeKey();
 
-    private final Listeners listeners = new Listeners();
+    protected abstract void setHomeKey(Key key);
 
-    public Command(CommandStore commandStore, TxnId id)
-    {
-        this.commandStore = commandStore;
-        this.txnId = id;
-    }
+    public abstract Key progressKey();
 
-    public TxnId txnId()
-    {
-        return txnId;
-    }
+    protected abstract void setProgressKey(Key key);
 
-    public Txn txn()
-    {
-        return txn;
-    }
+    protected abstract void setTxn(Txn txn);
 
-    public Ballot promised()
-    {
-        return promised;
-    }
+    public abstract Ballot promised();
 
-    public Ballot accepted()
-    {
-        return accepted;
-    }
+    public abstract void promised(Ballot ballot);
 
-    public Timestamp executeAt()
-    {
-        return executeAt;
-    }
+    public abstract Ballot accepted();
 
-    public Deps savedDeps()
-    {
-        return deps;
-    }
+    public abstract void accepted(Ballot ballot);
 
-    public Writes writes()
-    {
-        return writes;
-    }
+    public abstract void executeAt(Timestamp timestamp);
 
-    public Result result()
-    {
-        return result;
-    }
+    public abstract Deps savedDeps();
 
-    public Status status()
-    {
-        return status;
-    }
+    public abstract void savedDeps(Deps deps);
 
-    public boolean hasBeen(Status status)
+    public abstract Writes writes();
+
+    public abstract void writes(Writes writes);
+
+    public abstract Result result();
+
+    public abstract void result(Result result);
+
+    public abstract void status(Status status);
+
+    public abstract boolean isGloballyPersistent();
+
+    public abstract void isGloballyPersistent(boolean v);
+
+    public abstract Command addListener(Listener listener);
+
+    public abstract void notifyListeners();
+
+    public abstract void addWaitingOnCommit(Command command);
+
+    public abstract boolean isWaitingOnCommit();
+
+    public abstract void removeWaitingOnCommit(PartialCommand command);
+
+    public abstract PartialCommand firstWaitingOnCommit();
+
+    public abstract void addWaitingOnApplyIfAbsent(PartialCommand command);
+
+    public abstract boolean isWaitingOnApply();
+
+    public abstract void removeWaitingOnApply(PartialCommand command);
+
+    public abstract PartialCommand firstWaitingOnApply();
+
+    public boolean isUnableToApply()
     {
-        return this.status.compareTo(status) >= 0;
+        return isWaitingOnCommit() || isWaitingOnApply();
     }
 
-    public boolean is(Status status)
+    @Override
+    public Iterable<TxnId> txnIds()
     {
-        return this.status == status;
+        return Iterables.concat(Collections.singleton(txnId()), savedDeps().txnIds());
     }
 
-    public boolean isGloballyPersistent()
+    @Override
+    public Iterable<Key> keys()
     {
-        return isGloballyPersistent;
+        return txn().keys();
     }
 
     public void setGloballyPersistent(Key homeKey, Timestamp executeAt)
     {
         homeKey(homeKey);
         if (!hasBeen(Committed))
-            this.executeAt = executeAt;
-        else if (!this.executeAt.equals(executeAt))
-            commandStore.agent().onInconsistentTimestamp(this, this.executeAt, executeAt);
-        isGloballyPersistent = true;
+            this.executeAt(executeAt);
+        else if (!this.executeAt().equals(executeAt))
+            commandStore().agent().onInconsistentTimestamp(this, this.executeAt(), executeAt);
+        isGloballyPersistent(true);
     }
 
     // requires that command != null
@@ -152,21 +136,22 @@ public class Command implements Listener, Consumer<Listener>
         homeKey(homeKey);
         progressKey(progressKey);
 
-        if (status == NotWitnessed)
-            status = PreAccepted;
+        if (status() == NotWitnessed)
+            status(PreAccepted);
 
-        if (executeAt == null)
+        if (executeAt() == null)
         {
-            Timestamp max = commandStore.maxConflict(txn.keys);
+            Timestamp max = commandStore().maxConflict(txn.keys());
             // unlike in the Accord paper, we partition shards within a node, so that to ensure a total order we must either:
             //  - use a global logical clock to issue new timestamps; or
             //  - assign each shard _and_ process a unique id, and use both as components of the timestamp
-            executeAt = txnId.compareTo(max) > 0 && txnId.epoch >= commandStore.latestEpoch()
-                        ? txnId : commandStore.uniqueNow(max);
+            Timestamp witnessed = txnId().compareTo(max) > 0 && txnId().epoch >= commandStore().latestEpoch()
+                    ? txnId() : commandStore().uniqueNow(max);
+            executeAt(witnessed);
 
-            txn.keys().foldl(commandStore.ranges().since(txnId.epoch), (i, key, param) -> {
-                if (commandStore.hashIntersects(key))
-                    commandStore.commandsForKey(key).register(this);
+            txn.keys().foldl(commandStore().ranges().since(txnId().epoch), (i, key, param) -> {
+                if (commandStore().hashIntersects(key))
+                    commandStore().commandsForKey(key).register(this);
                 return null;
             }, null);
         }
@@ -174,53 +159,76 @@ public class Command implements Listener, Consumer<Listener>
 
     public boolean preaccept(Txn txn, Key homeKey, Key progressKey)
     {
-        if (promised.compareTo(Ballot.ZERO) > 0)
+        if (promised().compareTo(Ballot.ZERO) > 0)
+        {
+            logger.trace("{}: skipping preaccept - witnessed higher ballot ({})", txnId(), promised());
             return false;
+        }
 
         if (hasBeen(PreAccepted))
+        {
+            logger.trace("{}: skipping preaccept - already preaccepted ({})", txnId(), status());
             return true;
+        }
 
         witness(txn, homeKey, progressKey);
-        boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
-        commandStore.progressLog().preaccept(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+        boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
+        commandStore().progressLog().preaccept(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
 
-        listeners.forEach(this);
+        logger.trace("{}: preaccepted with executeAt: {}, deps: {}", txnId(), executeAt(), savedDeps());
+        notifyListeners();
         return true;
     }
 
     public boolean accept(Ballot ballot, Txn txn, Key homeKey, Key progressKey, Timestamp executeAt, Deps deps)
     {
-        if (this.promised.compareTo(ballot) > 0)
+        if (promised().compareTo(ballot) > 0)
+        {
+            logger.trace("{}: skipping accept - witnessed higher ballot ({} > {})", txnId(), promised(), ballot);
             return false;
+        }
 
         if (hasBeen(Committed))
+        {
+            logger.trace("{}: skipping accept - already committed ({})", txnId(), status());
             return false;
+        }
 
         witness(txn, homeKey, progressKey);
-        this.deps = deps;
-        this.executeAt = executeAt;
-        promised = accepted = ballot;
-        status = Accepted;
+        savedDeps(deps);
+        executeAt(executeAt);
+        promised(ballot);
+        accepted(ballot);
+        status(Accepted);
 
-        boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
-        commandStore.progressLog().accept(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+        boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
+        commandStore().progressLog().accept(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+        logger.trace("{}: preaccepted with executeAt: {}, deps: {}", txnId(), executeAt, deps);
 
-        listeners.forEach(this);
+        notifyListeners();
         return true;
     }
 
     public boolean acceptInvalidate(Ballot ballot)
     {
-        if (this.promised.compareTo(ballot) > 0)
+        if (this.promised().compareTo(ballot) > 0)
+        {
+            logger.trace("{}: skipping accept invalidated - witnessed higher ballot ({} > {})", txnId(), promised(), ballot);
             return false;
+        }
 
         if (hasBeen(Committed))
+        {
+            logger.trace("{}: skipping accept invalidated - already committed ({})", txnId(), status());
             return false;
+        }
 
-        promised = accepted = ballot;
-        status = AcceptedInvalidate;
+        promised(ballot);
+        accepted(ballot);
+        status(AcceptedInvalidate);
+        logger.trace("{}: accepted invalidated", txnId());
 
-        listeners.forEach(this);
+        notifyListeners();
         return true;
     }
 
@@ -229,25 +237,28 @@ public class Command implements Listener, Consumer<Listener>
     {
         if (hasBeen(Committed))
         {
-            if (executeAt.equals(this.executeAt) && status != Invalidated)
+            logger.trace("{}: skipping commit - already committed ({})", txnId(), status());
+            if (executeAt.equals(executeAt()) && status() != Invalidated)
                 return false;
 
-            commandStore.agent().onInconsistentTimestamp(this, (status == Invalidated ? Timestamp.NONE : this.executeAt), executeAt);
+            commandStore().agent().onInconsistentTimestamp(this, (status() == Invalidated ? Timestamp.NONE : this.executeAt()), executeAt);
         }
 
         witness(txn, homeKey, progressKey);
-        this.status = Committed;
-        this.deps = deps;
-        this.executeAt = executeAt;
-        this.waitingOnCommit = new TreeMap<>();
-        this.waitingOnApply = new TreeMap<>();
-
-        KeyRanges ranges = commandStore.ranges().since(executeAt.epoch);
+        savedDeps(deps);
+        executeAt(executeAt);
+        status(Committed);
+        Preconditions.checkState(!isWaitingOnCommit());
+        Preconditions.checkState(!isWaitingOnApply());
+        logger.trace("{}: committed with executeAt: {}, deps: {}", txnId(), executeAt, deps);
+
+        KeyRanges ranges = commandStore().ranges().since(executeAt.epoch);
         if (ranges != null)
         {
-            savedDeps().forEachOn(ranges, commandStore::hashIntersects, txnId -> {
-                Command command = commandStore.command(txnId);
-                switch (command.status)
+            savedDeps().forEachOn(ranges, commandStore()::hashIntersects, txnId -> {
+                Command command = commandStore().command(txnId);
+                logger.trace("{}: dep {} is {}", txnId(), command.txnId(), command.status());
+                switch (command.status())
                 {
                     default:
                         throw new IllegalStateException();
@@ -257,7 +268,7 @@ public class Command implements Listener, Consumer<Listener>
                     case AcceptedInvalidate:
                         // we don't know when these dependencies will execute, and cannot execute until we do
                         command.addListener(this);
-                        waitingOnCommit.put(txnId, command);
+                        addWaitingOnCommit(command);
                         break;
                     case Committed:
                         // TODO: split into ReadyToRead and ReadyToWrite;
@@ -274,101 +285,112 @@ public class Command implements Listener, Consumer<Listener>
             });
         }
 
-        if (waitingOnCommit.isEmpty())
-        {
-            waitingOnCommit = null;
-            if (waitingOnApply.isEmpty())
-                waitingOnApply = null;
-        }
 
-        boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
-        commandStore.progressLog().commit(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+        boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
+        commandStore().progressLog().commit(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
 
-        maybeExecute(false);
-        listeners.forEach(this);
         return true;
     }
 
+    public Future<Void> commitAndBeginExecution(Txn txn, Key homeKey, Key progressKey, Timestamp executeAt, Deps deps)
+    {
+        if (!commit(txn, homeKey, progressKey, executeAt, deps))
+            return Writes.SUCCESS;
+
+        return maybeExecute(true);
+    }
+
+    // TODO (now): commitInvalidate may need to update cfks _if_ possible
     public boolean commitInvalidate()
     {
         if (hasBeen(Committed))
         {
+            logger.trace("{}: skipping commit invalidated - already committed ({})", txnId(), status());
             if (!hasBeen(Invalidated))
-                commandStore.agent().onInconsistentTimestamp(this, Timestamp.NONE, executeAt);
+                commandStore().agent().onInconsistentTimestamp(this, Timestamp.NONE, executeAt());
 
             return false;
         }
 
-        status = Invalidated;
+        status(Invalidated);
 
-        boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
-        commandStore.progressLog().invalidate(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+        boolean isProgressShard = progressKey() != null && handles(txnId().epoch, progressKey());
+        commandStore().progressLog().invalidate(this, isProgressShard, isProgressShard && progressKey().equals(homeKey()));
+        logger.trace("{}: committed invalidated", txnId());
 
-        listeners.forEach(this);
+        notifyListeners();
         return true;
     }
 
-    public boolean apply(Txn txn, Key homeKey, Key progressKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
+    public Future<Void> apply(Txn txn, Key homeKey, Key progressKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
     {
-        if (hasBeen(Executed) && executeAt.equals(this.executeAt))
-            return false;
+        if (hasBeen(Executed) && executeAt.equals(executeAt()))
+        {
+            logger.trace("{}: skipping apply - already executed ({})", txnId(), status());
+            return Writes.SUCCESS;
+        }
         else if (!hasBeen(Committed))
             commit(txn, homeKey, progressKey, executeAt, deps);
-        else if (!executeAt.equals(this.executeAt))
-            commandStore.agent().onInconsistentTimestamp(this, this.executeAt, executeAt);
+        else if (!executeAt.equals(executeAt()))
+            commandStore().agent().onInconsistentTimestamp(this, executeAt(), executeAt);
 
-        this.executeAt = executeAt;
-        this.writes = writes;
-        this.result = result;
-        this.status = Executed;
+        executeAt(executeAt);
+        writes(writes);
+        result(result);
+        status(Executed);
+        logger.trace("{}: apply, status set to Executed with executeAt: {}, deps: {}", txnId(), executeAt, deps);
 
-        boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
-        commandStore.progressLog().execute(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+        boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
+        commandStore().progressLog().execute(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
 
-        maybeExecute(false);
-        this.listeners.forEach(this);
-        return true;
+        return maybeExecute(true);
     }
 
     public boolean recover(Txn txn, Key homeKey, Key progressKey, Ballot ballot)
     {
-        if (this.promised.compareTo(ballot) > 0)
+        if (this.promised().compareTo(ballot) > 0)
+        {
+            logger.trace("{}: skipping recover - higher ballot witnessed ({})", txnId(), promised());
             return false;
+        }
 
-        Status status = this.status;
+        Status status = status();
         witness(txn, homeKey, progressKey);
-        boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
+        boolean isProgressShard = progressKey != null && handles(txnId().epoch, progressKey);
         if (status == NotWitnessed)
-            commandStore.progressLog().preaccept(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
-        this.promised = ballot;
+            commandStore().progressLog().preaccept(this, isProgressShard, isProgressShard && progressKey.equals(homeKey));
+        promised(ballot);
+        logger.trace("{}: recovery started with executeAt: {}, deps: {}", txnId(), executeAt(), savedDeps());
         return true;
     }
 
     public boolean preAcceptInvalidate(Ballot ballot)
     {
-        if (this.promised.compareTo(ballot) > 0)
+        if (promised().compareTo(ballot) > 0)
+        {
+            logger.trace("{}: skipping preaccept invalidate - higher ballot witnessed ({})", txnId(), promised());
             return false;
+        }
 
-        this.promised = ballot;
+        promised(ballot);
+        logger.trace("{}: preaccepted invalidated", txnId());
         return true;
     }
 
-    public boolean addListener(Listener listener)
-    {
-        return listeners.add(listener);
-    }
-
-    public void removeListener(Listener listener)
+    @Override
+    public PreLoadContext listenerPreLoadContext(TxnId caller)
     {
-        listeners.remove(listener);
+        return PreLoadContext.contextFor(listOf(txnId(), caller), Collections.emptyList());
     }
 
-    @Override
-    public void onChange(Command command)
+    private void onChangeInternal(PartialCommand command)
     {
-        switch (command.status)
+        logger.trace("{}: updating as listener in response to change on {} with status {} ({})",
+                     txnId(), command.txnId(), command.status(), command);
+        switch (command.status())
         {
-            default: throw new IllegalStateException();
+            default:
+                throw new IllegalStateException();
             case NotWitnessed:
             case PreAccepted:
             case Accepted:
@@ -380,97 +402,140 @@ public class Command implements Listener, Consumer<Listener>
             case Executed:
             case Applied:
             case Invalidated:
-                if (waitingOnApply != null)
+                if (isUnableToApply())
                 {
                     updatePredecessor(command);
-                    if (waitingOnCommit != null)
+                    if (isWaitingOnCommit())
                     {
-                        if (waitingOnCommit.remove(command.txnId) != null && waitingOnCommit.isEmpty())
-                            waitingOnCommit = null;
+                        removeWaitingOnCommit(command);
                     }
-                    if (waitingOnCommit == null && waitingOnApply.isEmpty())
-                        waitingOnApply = null;
                 }
                 else
                 {
                     command.removeListener(this);
                 }
-                maybeExecute(true);
+                maybeExecute(false);
                 break;
         }
     }
 
-    private void maybeExecute(boolean notifyListeners)
+    @Override
+    public void onChange(Command command)
     {
-        if (status != Committed && status != Executed)
-            return;
+        onChangeInternal(command);
+    }
 
-        if (waitingOnApply != null)
+    protected void postApply()
+    {
+        logger.trace("{} applied, setting status to Applied and notifying listeners", txnId());
+        status(Applied);
+        notifyListeners();
+    }
+
+    private static Function<CommandStore, Void> callPostApply(TxnId txnId)
+    {
+        return commandStore -> {
+            commandStore.command(txnId).postApply();
+            return null;
+        };
+    }
+
+    protected Future<Void> apply()
+    {
+        // important: we can't include a reference to *this* in the lambda, since the C* implementation may evict
+        // the command instance from memory between now and the write completing (and post apply being called)
+        return writes().apply(commandStore()).flatMap(unused ->
+            commandStore().process(this, callPostApply(txnId()))
+        );
+    }
+
+    public Future<Data> read(Keys scope)
+    {
+        return txn().read(this);
+    }
+
+    private Future<Void> maybeExecute(boolean notifyListenersOnNoop)
+    {
+        if (logger.isTraceEnabled())
+            logger.trace("{}: Maybe executing with status {}. Will notify listeners on noop: {}", txnId(), status(), notifyListenersOnNoop);
+
+        if (status() != Committed && status() != Executed)
+        {
+            if (notifyListenersOnNoop) notifyListeners();
+            return Writes.SUCCESS;
+        }
+
+        if (isUnableToApply())
         {
             BlockedBy blockedBy = blockedBy();
             if (blockedBy != null)
             {
-                commandStore.progressLog().waiting(blockedBy.txnId, blockedBy.someKeys);
-                return;
+                logger.trace("{}: not executing, blocked on {}", txnId(), blockedBy.command.txnId());
+                commandStore().progressLog().waiting(blockedBy.command, blockedBy.someKeys);
+                if (notifyListenersOnNoop) notifyListeners();
+                return Writes.SUCCESS;
             }
-            assert waitingOnApply == null;
+            assert !isWaitingOnApply();
         }
 
-        switch (status)
+        switch (status())
         {
             case Committed:
                 // TODO: maintain distinct ReadyToRead and ReadyToWrite states
-                status = ReadyToExecute;
-                boolean isProgressShard = progressKey != null && handles(txnId.epoch, progressKey);
-                commandStore.progressLog().readyToExecute(txnId, isProgressShard, isProgressShard && progressKey.equals(homeKey));
-                if (notifyListeners)
-                    listeners.forEach(this);
+                status(ReadyToExecute);
+                logger.trace("{}: set to ReadyToExecute", txnId());
+                boolean isProgressShard = progressKey() != null && handles(txnId().epoch, progressKey());
+                commandStore().progressLog().readyToExecute(this, isProgressShard, isProgressShard && progressKey().equals(homeKey()));
+                notifyListeners();
                 break;
             case Executed:
-                writes.apply(commandStore);
-                status = Applied;
-                if (notifyListeners)
-                    listeners.forEach(this);
+                logger.trace("{}: applying", txnId());
+                if (notifyListenersOnNoop) notifyListeners();
+                return apply();
         }
+        return Writes.SUCCESS;
     }
 
     /**
      * @param dependency is either committed or invalidated
      */
-    private void updatePredecessor(Command dependency)
+    private void updatePredecessor(PartialCommand dependency)
     {
         Preconditions.checkState(dependency.hasBeen(Committed));
         if (dependency.hasBeen(Invalidated))
         {
+            logger.trace("{}: {} is invalidated. Stop listening and removing from waiting on commit set.", txnId(), dependency.txnId());
             dependency.removeListener(this);
-            if (waitingOnCommit.remove(dependency.txnId) != null && waitingOnCommit.isEmpty())
-                waitingOnCommit = null;
+            removeWaitingOnCommit(dependency);
         }
-        else if (dependency.executeAt.compareTo(executeAt) > 0)
+        else if (dependency.executeAt().compareTo(executeAt()) > 0)
         {
             // cannot be a predecessor if we execute later
+            logger.trace("{}: {} executes after us. Stop listening.", txnId(), dependency.txnId());
             dependency.removeListener(this);
         }
         else if (dependency.hasBeen(Applied))
         {
-            waitingOnApply.remove(dependency.executeAt);
+            logger.trace("{}: {} has been applied. Stop listening and removing from waiting on apply set.", txnId(), dependency.txnId());
+            removeWaitingOnApply(dependency);
             dependency.removeListener(this);
         }
         else
         {
-            waitingOnApply.putIfAbsent(dependency.executeAt, dependency);
+            logger.trace("{}: adding {} to waiting on apply set.", txnId(), dependency.txnId());
+            addWaitingOnApplyIfAbsent(dependency);
         }
     }
 
     // TEMPORARY: once we can invalidate commands that have not been witnessed on any shard, we do not need to know the home shard
     static class BlockedBy
     {
-        final TxnId txnId;
+        final PartialCommand command;
         final Keys someKeys;
 
-        BlockedBy(TxnId txnId, Keys someKeys)
+        BlockedBy(PartialCommand command, Keys someKeys)
         {
-            this.txnId = txnId;
+            this.command = command;
             this.someKeys = someKeys;
         }
     }
@@ -478,21 +543,14 @@ public class Command implements Listener, Consumer<Listener>
     public BlockedBy blockedBy()
     {
         Command prev = this;
-        Command cur = directlyBlockedBy();
+        PartialCommand cur = directlyBlockedBy();
         if (cur == null)
             return null;
 
-        Command next;
-        while (null != (next = cur.directlyBlockedBy()))
-        {
-            prev = cur;
-            cur = next;
-        }
-
         Keys someKeys = cur.someKeys();
         if (someKeys == null)
-            someKeys = prev.deps.someKeys(cur.txnId);
-        return new BlockedBy(cur.txnId, someKeys);
+            someKeys = prev.savedDeps().someKeys(cur.txnId());
+        return new BlockedBy(cur, someKeys);
     }
 
     /**
@@ -506,21 +564,22 @@ public class Command implements Listener, Consumer<Listener>
      *
      * TODO: Markdown documentation explaining the home shard and local shard concepts
      */
-    public Key homeKey()
-    {
-        return homeKey;
-    }
 
-    public void homeKey(Key homeKey)
+    public final void homeKey(Key homeKey)
     {
-        if (this.homeKey == null) this.homeKey = homeKey;
-        else if (!this.homeKey.equals(homeKey)) throw new IllegalStateException();
+        Key current = homeKey();
+        if (current == null) setHomeKey(homeKey);
+        else if (!current.equals(homeKey)) throw new AssertionError();
     }
 
-    public Keys someKeys()
+
+    public Key someKey()
     {
-        if (txn != null)
-            return txn.keys;
+        if (homeKey() != null)
+            return homeKey();
+
+        if (txn().keys() != null)
+            return txn().keys().get(0);
 
         return null;
     }
@@ -532,36 +591,34 @@ public class Command implements Listener, Consumer<Listener>
      *
      * Preferentially, this is homeKey on nodes that replicate it, and otherwise any key that is replicated, as of txnId.epoch
      */
-    public Key progressKey()
-    {
-        return progressKey;
-    }
 
-    public void progressKey(Key progressKey)
+    public final void progressKey(Key progressKey)
     {
-        if (this.progressKey == null) this.progressKey = progressKey;
-        else if (!this.progressKey.equals(progressKey)) throw new IllegalStateException();
+        Key current = progressKey();
+        if (current == null) setProgressKey(progressKey);
+        else if (!current.equals(progressKey)) throw new AssertionError();
     }
 
     // does this specific Command instance execute (i.e. does it lose ownership post Commit)
     public boolean executes()
     {
-        KeyRanges ranges = commandStore.ranges().at(executeAt.epoch);
-        return ranges != null && txn.keys.any(ranges, commandStore::hashIntersects);
+        KeyRanges ranges = commandStore().ranges().at(executeAt().epoch);
+        return ranges != null && txn().keys().any(ranges, commandStore()::hashIntersects);
     }
 
-    public void txn(Txn txn)
+    public final void txn(Txn txn)
     {
-        if (this.txn == null) this.txn = txn;
-        else if (!this.txn.equals(txn)) throw new IllegalStateException();
+        Txn current = txn();
+        if (current == null) setTxn(txn);
+        else if (!current.equals(txn)) throw new AssertionError();
     }
 
     public boolean handles(long epoch, Key someKey)
     {
-        if (!commandStore.hashIntersects(someKey))
+        if (!commandStore().hashIntersects(someKey))
             return false;
 
-        KeyRanges ranges = commandStore.ranges().at(epoch);
+        KeyRanges ranges = commandStore().ranges().at(epoch);
         if (ranges == null)
             return false;
         return ranges.contains(someKey);
@@ -569,32 +626,32 @@ public class Command implements Listener, Consumer<Listener>
 
     private Id coordinator()
     {
-        if (promised.equals(Ballot.ZERO))
-            return txnId.node;
-        return promised.node;
+        if (promised().equals(Ballot.ZERO))
+            return txnId().node;
+        return promised().node;
     }
 
-    private Command directlyBlockedBy()
+    private PartialCommand directlyBlockedBy()
     {
         // firstly we're waiting on every dep to commit
-        while (waitingOnCommit != null)
+        while (isWaitingOnCommit())
         {
             // TODO: when we change our liveness mechanism this may not be a problem
             // cannot guarantee that listener updating this set is invoked before this method by another listener
             // so we must check the entry is still valid, and potentially remove it if not
-            Command waitingOn = waitingOnCommit.firstEntry().getValue();
+            PartialCommand waitingOn = firstWaitingOnCommit();
             if (!waitingOn.hasBeen(Committed)) return waitingOn;
-            onChange(waitingOn);
+            onChangeInternal(waitingOn);
         }
 
-        while (waitingOnApply != null)
+        while (isWaitingOnApply())
         {
             // TODO: when we change our liveness mechanism this may not be a problem
             // cannot guarantee that listener updating this set is invoked before this method by another listener
             // so we must check the entry is still valid, and potentially remove it if not
-            Command waitingOn = waitingOnApply.firstEntry().getValue();
+            PartialCommand waitingOn = firstWaitingOnApply();
             if (!waitingOn.hasBeen(Applied)) return waitingOn;
-            onChange(waitingOn);
+            onChangeInternal(waitingOn);
         }
 
         return null;
@@ -610,11 +667,11 @@ public class Command implements Listener, Consumer<Listener>
     public String toString()
     {
         return "Command{" +
-               "txnId=" + txnId +
-               ", status=" + status +
-               ", txn=" + txn +
-               ", executeAt=" + executeAt +
-               ", deps=" + deps +
+               "txnId=" + txnId() +
+               ", status=" + status() +
+               ", txn=" + txn() +
+               ", executeAt=" + executeAt() +
+               ", deps=" + savedDeps() +
                '}';
     }
 }
diff --git a/accord-core/src/main/java/accord/local/CommandStore.java b/accord-core/src/main/java/accord/local/CommandStore.java
index 06b934a..b6a4f4b 100644
--- a/accord-core/src/main/java/accord/local/CommandStore.java
+++ b/accord-core/src/main/java/accord/local/CommandStore.java
@@ -22,43 +22,25 @@ import accord.api.Agent;
 import accord.api.Key;
 import accord.local.CommandStores.ShardedRanges;
 import accord.api.ProgressLog;
-import accord.primitives.KeyRange;
 import accord.api.DataStore;
 import accord.primitives.KeyRanges;
 import accord.primitives.Keys;
 import accord.primitives.Timestamp;
 import accord.primitives.TxnId;
-import org.apache.cassandra.utils.concurrent.AsyncPromise;
 import org.apache.cassandra.utils.concurrent.Future;
-import org.apache.cassandra.utils.concurrent.Promise;
 
 import com.google.common.base.Preconditions;
 
 import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.function.Function;
-
-import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
+import java.util.function.LongSupplier;
 
 /**
  * Single threaded internal shard of accord transaction metadata
  */
 public abstract class CommandStore
 {
-    public interface Factory
-    {
-        CommandStore create(int generation,
-                            int shardIndex,
-                            int numShards,
-                            Node node,
-                            Agent agent,
-                            DataStore store,
-                            ProgressLog.Factory progressLogFactory,
-                            RangesForEpoch rangesForEpoch);
-    }
-
     public interface RangesForEpoch
     {
         KeyRanges at(long epoch);
@@ -69,19 +51,19 @@ public abstract class CommandStore
     private final int generation;
     private final int shardIndex;
     private final int numShards;
-    private final Node node;
+    private final Function<Timestamp, Timestamp> uniqueNow;
+    private final LongSupplier currentEpoch;
     private final Agent agent;
     private final DataStore store;
     private final ProgressLog progressLog;
     private final RangesForEpoch rangesForEpoch;
 
-    private final NavigableMap<TxnId, Command> commands = new TreeMap<>();
-    private final NavigableMap<Key, CommandsForKey> commandsForKey = new TreeMap<>();
 
     public CommandStore(int generation,
                         int shardIndex,
                         int numShards,
-                        Node node,
+                        Function<Timestamp, Timestamp> uniqueNow,
+                        LongSupplier currentEpoch,
                         Agent agent,
                         DataStore store,
                         ProgressLog.Factory progressLogFactory,
@@ -91,42 +73,23 @@ public abstract class CommandStore
         this.generation = generation;
         this.shardIndex = shardIndex;
         this.numShards = numShards;
-        this.node = node;
+        this.uniqueNow = uniqueNow;
+        this.currentEpoch = currentEpoch;
         this.agent = agent;
         this.store = store;
         this.progressLog = progressLogFactory.create(this);
         this.rangesForEpoch = rangesForEpoch;
     }
 
-    public Command ifPresent(TxnId txnId)
-    {
-        return commands.get(txnId);
-    }
+    public abstract Command ifPresent(TxnId txnId);
 
-    public Command command(TxnId txnId)
-    {
-        return commands.computeIfAbsent(txnId, id -> new Command(this, id));
-    }
+    public abstract Command command(TxnId txnId);
 
-    public boolean hasCommand(TxnId txnId)
-    {
-        return commands.containsKey(txnId);
-    }
+    public abstract CommandsForKey commandsForKey(Key key);
 
-    public CommandsForKey commandsForKey(Key key)
-    {
-        return commandsForKey.computeIfAbsent(key, ignore -> new CommandsForKey());
-    }
 
-    public CommandsForKey maybeCommandsForKey(Key key)
-    {
-        return commandsForKey.get(key);
-    }
-
-    public boolean hasCommandsForKey(Key key)
-    {
-        return commandsForKey.containsKey(key);
-    }
+    // TODO (now): is this needed?
+    public abstract CommandsForKey maybeCommandsForKey(Key key);
 
     public DataStore store()
     {
@@ -135,7 +98,7 @@ public abstract class CommandStore
 
     public Timestamp uniqueNow(Timestamp atLeast)
     {
-        return node.uniqueNow(atLeast);
+        return uniqueNow.apply(atLeast);
     }
 
     public Agent agent()
@@ -148,11 +111,6 @@ public abstract class CommandStore
         return progressLog;
     }
 
-    public Node node()
-    {
-        return node;
-    }
-
     public RangesForEpoch ranges()
     {
         return rangesForEpoch;
@@ -161,7 +119,7 @@ public abstract class CommandStore
     public long latestEpoch()
     {
         // TODO: why not inject the epoch to each command store?
-        return node.epoch();
+        return currentEpoch.getAsLong();
     }
 
     protected Timestamp maxConflict(Keys keys)
@@ -174,43 +132,17 @@ public abstract class CommandStore
                    .orElse(Timestamp.NONE);
     }
 
-    public void forEpochCommands(KeyRanges ranges, long epoch, Consumer<Command> consumer)
+
+    // TODO (now): rename to shardIndex
+    public int index()
     {
-        Timestamp minTimestamp = new Timestamp(epoch, Long.MIN_VALUE, Integer.MIN_VALUE, Node.Id.NONE);
-        Timestamp maxTimestamp = new Timestamp(epoch, Long.MAX_VALUE, Integer.MAX_VALUE, Node.Id.MAX);
-        for (KeyRange range : ranges)
-        {
-            Iterable<CommandsForKey> rangeCommands = commandsForKey.subMap(range.start(),
-                                                                           range.startInclusive(),
-                                                                           range.end(),
-                                                                           range.endInclusive()).values();
-            for (CommandsForKey commands : rangeCommands)
-            {
-                commands.forWitnessed(minTimestamp, maxTimestamp, consumer);
-            }
-        }
+        return shardIndex;
     }
 
-    public void forCommittedInEpoch(KeyRanges ranges, long epoch, Consumer<Command> consumer)
+    // TODO (now): rename to shardGeneration
+    public int generation()
     {
-        Timestamp minTimestamp = new Timestamp(epoch, Long.MIN_VALUE, Integer.MIN_VALUE, Node.Id.NONE);
-        Timestamp maxTimestamp = new Timestamp(epoch, Long.MAX_VALUE, Integer.MAX_VALUE, Node.Id.MAX);
-        for (KeyRange range : ranges)
-        {
-            Iterable<CommandsForKey> rangeCommands = commandsForKey.subMap(range.start(),
-                                                                           range.startInclusive(),
-                                                                           range.end(),
-                                                                           range.endInclusive()).values();
-            for (CommandsForKey commands : rangeCommands)
-            {
-
-                Collection<Command> committed = commands.committedByExecuteAt.subMap(minTimestamp,
-                                                                                     true,
-                                                                                     maxTimestamp,
-                                                                                     true).values();
-                committed.forEach(consumer);
-            }
-        }
+        return generation;
     }
 
     public boolean hashIntersects(Key key)
@@ -223,226 +155,23 @@ public abstract class CommandStore
         return keys.any(ranges, this::hashIntersects);
     }
 
-    public static void onEach(Collection<CommandStore> stores, Consumer<? super CommandStore> consumer)
+    public static void onEach(PreLoadContext scope, Collection<CommandStore> stores, Consumer<? super CommandStore> consumer)
     {
         for (CommandStore store : stores)
-            store.process(consumer);
+            store.process(scope, consumer);
     }
 
-    <R> void processInternal(Function<? super CommandStore, R> function, Promise<R> promise)
-    {
-        try
-        {
-            promise.setSuccess(function.apply(this));
-        }
-        catch (Throwable e)
-        {
-            promise.tryFailure(e);
-        }
-    }
+    /**
+     * for interacting with the command store in a threadsafe way, without needing any txns loaded. For configuration
+     */
+    public abstract Future<Void> processSetup(Consumer<? super CommandStore> function);
 
-    void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> promise)
-    {
-        try
-        {
-            consumer.accept(this);
-            promise.setSuccess(null);
-        }
-        catch (Throwable e)
-        {
-            promise.tryFailure(e);
-        }
-    }
-
-    public abstract Future<Void> process(Consumer<? super CommandStore> consumer);
+    public abstract <T> Future<T> processSetup(Function<? super CommandStore, T> function);
 
-    public abstract <T> Future<T> process(Function<? super CommandStore, T> function);
+    public abstract Future<Void> process(PreLoadContext context, Consumer<? super CommandStore> consumer);
 
-    public void processBlocking(Consumer<? super CommandStore> consumer)
-    {
-        try
-        {
-            process(consumer).get();
-        }
-        catch (InterruptedException e)
-        {
-            throw new UncheckedInterruptedException(e);
-        }
-        catch (ExecutionException e)
-        {
-            throw new RuntimeException(e.getCause());
-        }
-    }
+    public abstract <T> Future<T> process(PreLoadContext context, Function<? super CommandStore, T> function);
 
     public abstract void shutdown();
 
-    public static class Synchronized extends CommandStore
-    {
-        public Synchronized(int generation, int index, int numShards, Node node,
-                            Agent agent, DataStore store,
-                            ProgressLog.Factory progressLogFactory,
-                            RangesForEpoch rangesForEpoch)
-        {
-            super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
-        }
-
-        @Override
-        public synchronized Future<Void> process(Consumer<? super CommandStore> consumer)
-        {
-            AsyncPromise<Void> promise = new AsyncPromise<>();
-            processInternal(consumer, promise);
-            return promise;
-        }
-
-        @Override
-        public <T> Future<T> process(Function<? super CommandStore, T> function)
-        {
-            AsyncPromise<T> promise = new AsyncPromise<>();
-            processInternal(function, promise);
-            return promise;
-        }
-
-        @Override
-        public void shutdown() {}
-    }
-
-    public static class SingleThread extends CommandStore
-    {
-        private final ExecutorService executor;
-
-        private class ConsumerWrapper extends AsyncPromise<Void> implements Runnable
-        {
-            private final Consumer<? super CommandStore> consumer;
-
-            public ConsumerWrapper(Consumer<? super CommandStore> consumer)
-            {
-                this.consumer = consumer;
-            }
-
-            @Override
-            public void run()
-            {
-                processInternal(consumer, this);
-            }
-        }
-
-        private class FunctionWrapper<T> extends AsyncPromise<T> implements Runnable
-        {
-            private final Function<? super CommandStore, T> function;
-
-            public FunctionWrapper(Function<? super CommandStore, T> function)
-            {
-                this.function = function;
-            }
-
-            @Override
-            public void run()
-            {
-                processInternal(function, this);
-            }
-        }
-
-        public SingleThread(int generation, int index, int numShards, Node node,
-                            Agent agent, DataStore store, ProgressLog.Factory progressLogFactory,
-                            RangesForEpoch rangesForEpoch)
-        {
-            super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
-            executor = Executors.newSingleThreadExecutor(r -> {
-                Thread thread = new Thread(r);
-                thread.setName(CommandStore.class.getSimpleName() + '[' + node.id() + ':' + index + ']');
-                return thread;
-            });
-        }
-
-        @Override
-        public Future<Void> process(Consumer<? super CommandStore> consumer)
-        {
-            ConsumerWrapper future = new ConsumerWrapper(consumer);
-            executor.execute(future);
-            return future;
-        }
-
-        @Override
-        public <T> Future<T> process(Function<? super CommandStore, T> function)
-        {
-            FunctionWrapper<T> future = new FunctionWrapper<>(function);
-            executor.execute(future);
-            return future;
-        }
-
-        @Override
-        public void shutdown()
-        {
-            executor.shutdown();
-        }
-    }
-
-    static class Debug extends SingleThread
-    {
-        private final AtomicReference<Thread> expectedThread = new AtomicReference<>();
-
-        public Debug(int generation, int index, int numShards, Node node,
-                     Agent agent, DataStore store, ProgressLog.Factory progressLogFactory,
-                     RangesForEpoch rangesForEpoch)
-        {
-            super(generation, index, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
-        }
-
-        private void assertThread()
-        {
-            Thread current = Thread.currentThread();
-            Thread expected;
-            while (true)
-            {
-                expected = expectedThread.get();
-                if (expected != null)
-                    break;
-                expectedThread.compareAndSet(null, Thread.currentThread());
-            }
-            Preconditions.checkState(expected == current);
-        }
-
-        @Override
-        public Command command(TxnId txnId)
-        {
-            assertThread();
-            return super.command(txnId);
-        }
-
-        @Override
-        public boolean hasCommand(TxnId txnId)
-        {
-            assertThread();
-            return super.hasCommand(txnId);
-        }
-
-        @Override
-        public CommandsForKey commandsForKey(Key key)
-        {
-            assertThread();
-            return super.commandsForKey(key);
-        }
-
-        @Override
-        public boolean hasCommandsForKey(Key key)
-        {
-            assertThread();
-            return super.hasCommandsForKey(key);
-        }
-
-        @Override
-        <R> void processInternal(Function<? super CommandStore, R> function, Promise<R> future)
-        {
-            assertThread();
-            super.processInternal(function, future);
-        }
-
-        @Override
-        void processInternal(Consumer<? super CommandStore> consumer, Promise<Void> future)
-        {
-            assertThread();
-            super.processInternal(consumer, future);
-        }
-    }
-
 }
diff --git a/accord-core/src/main/java/accord/local/CommandStores.java b/accord-core/src/main/java/accord/local/CommandStores.java
index 254d38d..8990379 100644
--- a/accord-core/src/main/java/accord/local/CommandStores.java
+++ b/accord-core/src/main/java/accord/local/CommandStores.java
@@ -19,24 +19,29 @@
 package accord.local;
 
 import accord.api.Agent;
-import accord.api.Key;
 import accord.api.DataStore;
-import accord.local.CommandStore.RangesForEpoch;
+import accord.api.Key;
 import accord.api.ProgressLog;
+import accord.local.CommandStore.RangesForEpoch;
 import accord.primitives.KeyRanges;
 import accord.topology.Topology;
 import accord.primitives.Keys;
 import org.apache.cassandra.utils.concurrent.Future;
 import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
 
+import accord.messages.TxnRequest;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
-
+import org.apache.cassandra.utils.concurrent.FutureCombiner;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntFunction;
 import java.util.*;
 import java.util.concurrent.ExecutionException;
-import java.util.function.*;
-
-import static java.lang.Boolean.FALSE;
 
 /**
  * Manages the single threaded metadata shards
@@ -52,95 +57,23 @@ public abstract class CommandStores
                              ProgressLog.Factory progressLogFactory);
     }
 
-    interface Fold<I1, I2, O>
+    protected interface Fold<I1, I2, O>
     {
         O fold(CommandStore store, I1 i1, I2 i2, O accumulator);
     }
 
-    interface Select<Scope>
+    protected interface Select<Scope>
     {
         long select(ShardedRanges ranges, Scope scope, long minEpoch, long maxEpoch);
     }
 
-    static class KeysAndEpoch
-    {
-        final Keys keys;
-        final long epoch;
-
-        KeysAndEpoch(Keys keys, long epoch)
-        {
-            this.keys = keys;
-            this.epoch = epoch;
-        }
-    }
-
-    static class KeysAndEpochRange
-    {
-        final Keys keys;
-        final long minEpoch;
-        final long maxEpoch;
-
-        KeysAndEpochRange(Keys keys, long minEpoch, long maxEpoch)
-        {
-            this.keys = keys;
-            this.minEpoch = minEpoch;
-            this.maxEpoch = maxEpoch;
-        }
-    }
-
-    static class KeyAndEpoch
-    {
-        final Key key;
-        final long epoch;
-
-        KeyAndEpoch(Key key, long epoch)
-        {
-            this.key = key;
-            this.epoch = epoch;
-        }
-    }
-
-    private static class Supplier
-    {
-        private final Node node;
-        private final Agent agent;
-        private final DataStore store;
-        private final ProgressLog.Factory progressLogFactory;
-        private final CommandStore.Factory shardFactory;
-        private final int numShards;
-
-        Supplier(Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.Factory shardFactory, int numShards)
-        {
-            this.node = node;
-            this.agent = agent;
-            this.store = store;
-            this.progressLogFactory = progressLogFactory;
-            this.shardFactory = shardFactory;
-            this.numShards = numShards;
-        }
-
-        CommandStore create(int generation, int shardIndex, RangesForEpoch rangesForEpoch)
-        {
-            return shardFactory.create(generation, shardIndex, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
-        }
-
-        ShardedRanges createShardedRanges(int generation, long epoch, KeyRanges ranges, RangesForEpoch rangesForEpoch)
-        {
-            CommandStore[] newStores = new CommandStore[numShards];
-            for (int i=0; i<numShards; i++)
-                newStores[i] = create(generation, i, rangesForEpoch);
-
-            return new ShardedRanges(newStores, epoch, ranges);
-        }
-    }
-
-    static class ShardedRanges
+    protected static class ShardedRanges
     {
         final CommandStore[] shards;
         final long[] epochs;
         final KeyRanges[] ranges;
 
-        public ShardedRanges(CommandStore[] shards, long epoch, KeyRanges ranges)
+        protected ShardedRanges(CommandStore[] shards, long epoch, KeyRanges ranges)
         {
             Preconditions.checkArgument(shards.length <= 64);
             this.shards = shards;
@@ -190,12 +123,12 @@ public abstract class CommandStores
             return i;
         }
 
-        long all()
+        public long all()
         {
             return -1L >>> (64 - shards.length);
         }
 
-        long shards(Keys keys, long minEpoch, long maxEpoch)
+        public long shards(Keys keys, long minEpoch, long maxEpoch)
         {
             long accumulate = 0L;
             for (int i = Math.max(0, indexForEpoch(minEpoch)), maxi = indexForEpoch(maxEpoch); i <= maxi ; ++i)
@@ -205,6 +138,11 @@ public abstract class CommandStores
             return accumulate;
         }
 
+        long shards(TxnRequest request, long minEpoch, long maxEpoch)
+        {
+            return shards(request.scope(), minEpoch, maxEpoch);
+        }
+
         long shard(Key scope, long minEpoch, long maxEpoch)
         {
             long result = 0L;
@@ -253,19 +191,22 @@ public abstract class CommandStores
         }
     }
 
-    final Supplier supplier;
+    private final Node node;
+    private final Agent agent;
+    private final DataStore store;
+    private final ProgressLog.Factory progressLogFactory;
+    private final int numShards;
     volatile Snapshot current;
 
-    private CommandStores(Supplier supplier)
-    {
-        this.supplier = supplier;
-        this.current = new Snapshot(new ShardedRanges[0], Topology.EMPTY, Topology.EMPTY);
-    }
-
     public CommandStores(int num, Node node, Agent agent, DataStore store,
-                         ProgressLog.Factory progressLogFactory, CommandStore.Factory shardFactory)
+                         ProgressLog.Factory progressLogFactory)
     {
-        this(new Supplier(node, agent, store, progressLogFactory, shardFactory, num));
+        this.node = node;
+        this.agent = agent;
+        this.store = store;
+        this.progressLogFactory = progressLogFactory;
+        this.numShards = num;
+        this.current = new Snapshot(new ShardedRanges[0], Topology.EMPTY, Topology.EMPTY);
     }
 
     public Topology local()
@@ -278,6 +219,24 @@ public abstract class CommandStores
         return current.global;
     }
 
+    protected abstract CommandStore createCommandStore(int generation,
+                                                       int index,
+                                                       int numShards,
+                                                       Node node,
+                                                       Agent agent,
+                                                       DataStore store,
+                                                       ProgressLog.Factory progressLogFactory,
+                                                       RangesForEpoch rangesForEpoch);
+
+    private ShardedRanges createShardedRanges(int generation, long epoch, KeyRanges ranges, RangesForEpoch rangesForEpoch)
+    {
+        CommandStore[] newStores = new CommandStore[numShards];
+        for (int i=0; i<numShards; i++)
+            newStores[i] = createCommandStore(generation, i, numShards, node, agent, store, progressLogFactory, rangesForEpoch);
+
+        return new ShardedRanges(newStores, epoch, ranges);
+    }
+
     private Snapshot updateTopology(Snapshot prev, Topology newTopology)
     {
         Preconditions.checkArgument(!newTopology.isSubset(), "Use full topology for CommandStores.updateTopology");
@@ -286,7 +245,7 @@ public abstract class CommandStores
         if (epoch <= prev.global.epoch())
             return prev;
 
-        Topology newLocalTopology = newTopology.forNode(supplier.node.id());
+        Topology newLocalTopology = newTopology.forNode(node.id());
         KeyRanges added = newLocalTopology.ranges().difference(prev.local.ranges());
         KeyRanges subtracted = prev.local.ranges().difference(newLocalTopology.ranges());
 //            for (ShardedRanges range : stores.ranges)
@@ -305,7 +264,7 @@ public abstract class CommandStores
         {
             int newGeneration = prev.ranges.length;
             System.arraycopy(prev.ranges, 0, result, 0, newGeneration);
-            result[newGeneration] = supplier.createShardedRanges(newGeneration, epoch, added, rangesForEpochFunction(newGeneration));
+            result[newGeneration] = createShardedRanges(newGeneration, epoch, added, rangesForEpochFunction(newGeneration));
         }
         else
         {
@@ -318,7 +277,7 @@ public abstract class CommandStores
                 result[i++] = ranges;
             }
             if (i < result.length)
-                result[i] = supplier.createShardedRanges(i, epoch, added, rangesForEpochFunction(i));
+                result[i] = createShardedRanges(i, epoch, added, rangesForEpochFunction(i));
         }
 
         return new Snapshot(result, newTopology, newLocalTopology);
@@ -356,42 +315,115 @@ public abstract class CommandStores
                 commandStore.shutdown();
     }
 
-    protected abstract <S> void forEach(Select<S> select, S scope, long minEpoch, long maxEpoch, Consumer<? super CommandStore> forEach);
-    protected abstract <S, T> T mapReduce(Select<S> select, S scope, long minEpoch, long maxEpoch, Function<? super CommandStore, T> map, BiFunction<T, T, T> reduce);
+    private static <T> Fold<PreLoadContext, Void, List<Future<T>>> mapFold(Function<CommandStore, T> map)
+    {
+        return (store, op, i, t) -> { t.add(store.process(op, map)); return t; };
+    }
 
-    public void forEach(Consumer<CommandStore> forEach)
+    private static <T> T reduce(List<Future<T>> futures, BiFunction<T, T, T> reduce)
     {
-        forEach((s, i, min, max) -> s.all(), null, 0, 0, forEach);
+        T result = null;
+        for (Future<T> future : futures)
+        {
+            try
+            {
+                T next = future.get();
+                result = result == null ? next : reduce.apply(result, next);
+            }
+            catch (InterruptedException e)
+            {
+                throw new UncheckedInterruptedException(e);
+            }
+            catch (ExecutionException e)
+            {
+                throw new RuntimeException(e.getCause());
+            }
+        }
+        return result;
     }
 
-    public void forEach(Keys keys, long epoch, Consumer<CommandStore> forEach)
+    private <F, T> T setup(F f, Fold<F, Void, List<Future<T>>> fold, BiFunction<T, T, T> reduce)
     {
-        forEach(keys, epoch, epoch, forEach);
+        List<Future<T>> futures = foldl((s, i, mn, mx) -> s.all(), null, Long.MIN_VALUE, Long.MAX_VALUE, fold, f, null, ArrayList::new);
+        return reduce(futures, reduce);
     }
 
-    public void forEach(Keys keys, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
+    private  <S, T> T mapReduce(PreLoadContext context, Select<S> select, S scope, long minEpoch, long maxEpoch, Fold<PreLoadContext, Void, List<Future<T>>> fold, BiFunction<T, T, T> reduce)
     {
-        forEach(ShardedRanges::shards, keys, minEpoch, maxEpoch, forEach);
+        List<Future<T>> futures = foldl(select, scope, minEpoch, maxEpoch, fold, context, null, ArrayList::new);
+        if (futures == null)
+            return null;
+        return reduce(futures, reduce);
     }
 
-    public <T> T mapReduce(Keys keys, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    public <T> T mapReduce(PreLoadContext context, Key key, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
     {
-        return mapReduce(keys, epoch, epoch, map, reduce);
+        return mapReduce(context, ShardedRanges::shard, key, minEpoch, maxEpoch, mapFold(map), reduce);
     }
 
-    public <T> T mapReduce(Keys keys, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    public <T> T mapReduce(PreLoadContext context, Key key, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
     {
-        return mapReduce(ShardedRanges::shards, keys, minEpoch, maxEpoch, map, reduce);
+        return mapReduce(context, key, epoch, epoch, map, reduce);
     }
 
-    public <T> T mapReduce(Key key, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    public <T> T mapReduceSince(PreLoadContext context, Key key, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
     {
-        return mapReduce(ShardedRanges::shard, key, epoch, epoch, map, reduce);
+        return mapReduce(context, key, epoch, Long.MAX_VALUE, map, reduce);
     }
 
-    public <T> T mapReduceSince(Key key, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    public <T> T mapReduce(PreLoadContext context, Keys keys, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
     {
-        return mapReduce(ShardedRanges::shard, key, epoch, Long.MAX_VALUE, map, reduce);
+        // probably need to split txnOperation and scope stuff here
+        return mapReduce(context, ShardedRanges::shards, keys, minEpoch, maxEpoch, mapFold(map), reduce);
+    }
+
+    public <T> List<Future<T>> mapAsync(PreLoadContext context, Keys keys, long minEpoch, long maxEpoch, Function<CommandStore, T> map)
+    {
+        return foldl(ShardedRanges::shards, keys, minEpoch, maxEpoch, mapFold(map), context, null, ArrayList::new);
+    }
+
+    public void setup(Consumer<CommandStore> forEach)
+    {
+        setup(forEach, (store, f, i, t) -> { t.add(store.processSetup(f)); return t; }, (Void i1, Void i2) -> null);
+    }
+
+    public <T> T setup(Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    {
+        return setup(map, (store, f, i, t) -> { t.add(store.processSetup(f)); return t; }, reduce);
+    }
+
+    private static Fold<PreLoadContext, Void, List<Future<Void>>> forEachFold(Consumer<CommandStore> forEach)
+    {
+        return (store, op, i, t) -> { t.add(store.process(op, forEach)); return t; };
+    }
+
+    public void forEach(PreLoadContext context, Keys keys, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
+    {
+        mapReduce(context, ShardedRanges::shards, keys, minEpoch, maxEpoch, forEachFold(forEach), (o1, o2) -> null);
+    }
+
+    public void forEach(PreLoadContext context, Consumer<CommandStore> forEach)
+    {
+        mapReduce(context, (s, i, min, max) -> s.all(), null, 0, 0, forEachFold(forEach), (o1, o2) -> null);
+    }
+
+    public void forEach(TxnRequest request, long epoch, Consumer<CommandStore> forEach)
+    {
+        forEach(request, request.scope(), epoch, epoch, forEach);
+    }
+
+    public void forEach(PreLoadContext context, Keys keys, long epoch, Consumer<CommandStore> forEach)
+    {
+        forEach(context, keys, epoch, epoch, forEach);
+    }
+    public void forEachSince(PreLoadContext context, Keys keys, long epoch, Consumer<CommandStore> forEach)
+    {
+        forEach(context, keys, epoch, Long.MAX_VALUE, forEach);
+    }
+
+    public void forEach(TxnRequest request, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
+    {
+        forEach(request, request.scope(), minEpoch, maxEpoch, forEach);
     }
 
     public <T extends Collection<CommandStore>> T collect(Keys keys, long epoch, IntFunction<T> factory)
@@ -445,7 +477,7 @@ public abstract class CommandStores
         return accumulator;
     }
 
-    <S, I1, I2, O> O foldl(Select<S> select, S scope, long minEpoch, long maxEpoch, Fold<? super I1, ? super I2, O> fold, I1 param1, I2 param2, IntFunction<? extends O> factory)
+    protected <S, I1, I2, O> O foldl(Select<S> select, S scope, long minEpoch, long maxEpoch, Fold<? super I1, ? super I2, O> fold, I1 param1, I2 param2, IntFunction<? extends O> factory)
     {
         ShardedRanges[] ranges = current.ranges;
         O accumulator = null;
@@ -500,88 +532,35 @@ public abstract class CommandStores
         throw new IllegalArgumentException();
     }
 
-    public static class Synchronized extends CommandStores
+    public static Future<?> forEachNonBlocking(Collection<CommandStore> commandStores, PreLoadContext scope, Consumer<CommandStore> forEach)
     {
-        public Synchronized(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
-        {
-            super(num, node, agent, store, progressLogFactory, CommandStore.Synchronized::new);
-        }
-
-        public Synchronized(Supplier supplier)
-        {
-            super(supplier);
-        }
-
-        @Override
-        protected <S, T> T mapReduce(Select<S> select, S scope, long minEpoch, long maxEpoch, Function<? super CommandStore, T> map, BiFunction<T, T, T> reduce)
-        {
-            return foldl(select, scope, minEpoch, maxEpoch, (store, f, r, t) -> t == null ? f.apply(store) : r.apply(t, f.apply(store)), map, reduce, ignore -> null);
-        }
-
-        @Override
-        protected <S> void forEach(Select<S> select, S scope, long minEpoch, long maxEpoch, Consumer<? super CommandStore> forEach)
-        {
-            foldl(select, scope, minEpoch, maxEpoch, (store, f, r, t) -> { f.accept(store); return null; }, forEach, null, ignore -> FALSE);
-        }
+        List<Future<Void>> futures = new ArrayList<>(commandStores.size());
+        for (CommandStore commandStore : commandStores)
+            futures.add(commandStore.process(scope, forEach));
+        return FutureCombiner.allOf(futures);
     }
 
-    public static class SingleThread extends CommandStores
+    public static void forEachBlocking(Collection<CommandStore> commandStores, PreLoadContext scope, Consumer<CommandStore> forEach)
     {
-        public SingleThread(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
-        {
-            this(num, node, agent, store, progressLogFactory, CommandStore.SingleThread::new);
-        }
-
-        public SingleThread(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory, CommandStore.Factory shardFactory)
-        {
-            super(num, node, agent, store, progressLogFactory, shardFactory);
-        }
-
-        private <S, F, T> T mapReduce(Select<S> select, S scope, long minEpoch, long maxEpoch, F f, Fold<F, ?, List<Future<T>>> fold, BiFunction<T, T, T> reduce)
+        try
         {
-            List<Future<T>> futures = foldl(select, scope, minEpoch, maxEpoch, fold, f, null, ArrayList::new);
-            if (futures == null)
-                return null;
-
-            T result = null;
-            for (Future<T> future : futures)
-            {
-                try
-                {
-                    T next = future.get();
-                    if (result == null) result = next;
-                    else result = reduce.apply(result, next);
-                }
-                catch (InterruptedException e)
-                {
-                    throw new UncheckedInterruptedException(e);
-                }
-                catch (ExecutionException e)
-                {
-                    throw new RuntimeException(e.getCause());
-                }
-            }
-            return result;
+            forEachNonBlocking(commandStores, scope, forEach).get();
         }
-
-        @Override
-        protected <S, T> T mapReduce(Select<S> select, S scope, long minEpoch, long maxEpoch, Function<? super CommandStore, T> map, BiFunction<T, T, T> reduce)
+        catch (InterruptedException e)
         {
-            return mapReduce(select, scope, minEpoch, maxEpoch, map, (store, f, i, t) -> { t.add(store.process(f)); return t; }, reduce);
+            throw new UncheckedInterruptedException(e);
         }
-
-        protected <S> void forEach(Select<S> select, S scope, long minEpoch, long maxEpoch, Consumer<? super CommandStore> forEach)
+        catch (ExecutionException e)
         {
-            mapReduce(select, scope, minEpoch, maxEpoch, forEach, (store, f, i, t) -> { t.add(store.process(f)); return t; }, (Void i1, Void i2) -> null);
+            throw new RuntimeException(e);
         }
     }
 
-    public static class Debug extends SingleThread
+    public static <T> T mapReduce(Collection<CommandStore> commandStores, PreLoadContext scope, Function<? super CommandStore, T> map, BiFunction<T, T, T> reduce)
     {
-        public Debug(int num, Node node, Agent agent, DataStore store, ProgressLog.Factory progressLogFactory)
-        {
-            super(num, node, agent, store, progressLogFactory, CommandStore.Debug::new);
-        }
+        List<Future<T>> futures = new ArrayList<>(commandStores.size());
+        for (CommandStore commandStore : commandStores)
+            futures.add(commandStore.process(scope, map));
+        return reduce(futures, reduce);
     }
-
 }
diff --git a/accord-core/src/main/java/accord/local/CommandsForKey.java b/accord-core/src/main/java/accord/local/CommandsForKey.java
index 5bf925e..13034b0 100644
--- a/accord-core/src/main/java/accord/local/CommandsForKey.java
+++ b/accord-core/src/main/java/accord/local/CommandsForKey.java
@@ -19,40 +19,77 @@
 package accord.local;
 
 import java.util.Iterator;
-import java.util.NavigableMap;
-import java.util.TreeMap;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
+import accord.api.Key;
 import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
 import com.google.common.collect.Iterators;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-public class CommandsForKey implements Listener, Iterable<Command>
+import static accord.utils.Utils.*;
+
+public abstract class CommandsForKey implements Listener, Iterable<PartialCommand.WithDeps>
 {
-    // TODO: efficiency
-    public final NavigableMap<Timestamp, Command> uncommitted = new TreeMap<>();
-    public final NavigableMap<Timestamp, Command> committedById = new TreeMap<>();
-    public final NavigableMap<Timestamp, Command> committedByExecuteAt = new TreeMap<>();
+    private static final Logger logger = LoggerFactory.getLogger(CommandsForKey.class);
+
+    public interface CommandTimeseries
+    {
+        PartialCommand.WithDeps get(Timestamp timestamp);
+        void add(Timestamp timestamp, Command command);
+        void remove(Timestamp timestamp);
+
+        boolean isEmpty();
+
+        /**
+         * All commands before (exclusive of) the given timestamp
+         */
+        Stream<PartialCommand.WithDeps> before(Timestamp timestamp);
+
+        /**
+         * All commands after (exclusive of) the given timestamp
+         */
+        Stream<PartialCommand.WithDeps> after(Timestamp timestamp);
 
-    private Timestamp max = Timestamp.NONE;
+        /**
+         * All commands between (inclusive of) the given timestamps
+         */
+        Stream<PartialCommand.WithDeps> between(Timestamp min, Timestamp max);
 
-    public Timestamp max()
+        Stream<PartialCommand.WithDeps> all();
+    }
+
+    public abstract Key key();
+    public abstract CommandTimeseries uncommitted();
+    public abstract CommandTimeseries committedById();
+    public abstract CommandTimeseries committedByExecuteAt();
+
+    public abstract Timestamp max();
+    public abstract void updateMax(Timestamp timestamp);
+
+    @Override
+    public PreLoadContext listenerPreLoadContext(TxnId caller)
     {
-        return max;
+        return PreLoadContext.contextFor(caller, listOf(key()));
     }
 
     @Override
     public void onChange(Command command)
     {
-        max = Timestamp.max(max, command.executeAt());
+        logger.trace("cfk[{}]: updating as listener in response to change on {} with status {} ({})",
+                     key(), command.txnId(), command.status(), command);
+        updateMax(command.executeAt());
         switch (command.status())
         {
             case Applied:
             case Executed:
             case Committed:
-                committedById.put(command.txnId(), command);
-                committedByExecuteAt.put(command.executeAt(), command);
+                committedById().add(command.txnId(), command);
+                committedByExecuteAt().add(command.executeAt(), command);
             case Invalidated:
-                uncommitted.remove(command.txnId());
+                uncommitted().remove(command.txnId());
                 command.removeListener(this);
                 break;
         }
@@ -60,28 +97,28 @@ public class CommandsForKey implements Listener, Iterable<Command>
 
     public void register(Command command)
     {
-        max = Timestamp.max(max, command.executeAt());
-        uncommitted.put(command.txnId(), command);
+        updateMax(command.executeAt());
+        uncommitted().add(command.txnId(), command);
         command.addListener(this);
     }
 
-    public void forWitnessed(Timestamp minTs, Timestamp maxTs, Consumer<Command> consumer)
+    public void forWitnessed(Timestamp minTs, Timestamp maxTs, Consumer<PartialCommand> consumer)
     {
-        uncommitted.subMap(minTs, true, maxTs, true).values().stream()
+        uncommitted().between(minTs, maxTs)
                 .filter(cmd -> cmd.hasBeen(Status.PreAccepted)).forEach(consumer);
-        committedById.subMap(minTs, true, maxTs, true).values().forEach(consumer);
-        committedByExecuteAt.subMap(minTs, true, maxTs, true).values().stream()
+        committedById().between(minTs, maxTs).forEach(consumer);
+        committedByExecuteAt().between(minTs, maxTs)
                 .filter(cmd -> cmd.txnId().compareTo(minTs) < 0 || cmd.txnId().compareTo(maxTs) > 0).forEach(consumer);
     }
 
     @Override
-    public Iterator<Command> iterator()
+    public Iterator<PartialCommand.WithDeps> iterator()
     {
-        return Iterators.concat(uncommitted.values().iterator(), committedByExecuteAt.values().iterator());
+        return Iterators.concat(uncommitted().all().iterator(), committedByExecuteAt().all().iterator());
     }
 
     public boolean isEmpty()
     {
-        return uncommitted.isEmpty() && committedById.isEmpty();
+        return uncommitted().isEmpty() && committedById().isEmpty();
     }
 }
diff --git a/accord-core/src/main/java/accord/local/Listener.java b/accord-core/src/main/java/accord/local/Listener.java
index 4846bfc..46ec122 100644
--- a/accord-core/src/main/java/accord/local/Listener.java
+++ b/accord-core/src/main/java/accord/local/Listener.java
@@ -18,7 +18,22 @@
 
 package accord.local;
 
+import accord.primitives.TxnId;
+
 public interface Listener
 {
     void onChange(Command command);
+
+    /**
+     * Scope needed to run onChange
+     */
+    PreLoadContext listenerPreLoadContext(TxnId caller);
+
+    /**
+     * Indicates the command to listener relationship doesn't need to persist across restarts
+     */
+    default boolean isTransient()
+    {
+        return false;
+    }
 }
diff --git a/accord-core/src/main/java/accord/local/Listeners.java b/accord-core/src/main/java/accord/local/Listeners.java
index e1b2e1a..15b02a8 100644
--- a/accord-core/src/main/java/accord/local/Listeners.java
+++ b/accord-core/src/main/java/accord/local/Listeners.java
@@ -20,6 +20,6 @@ package accord.local;
 
 import accord.utils.DeterministicIdentitySet;
 
-class Listeners extends DeterministicIdentitySet<Listener>
+public class Listeners extends DeterministicIdentitySet<Listener>
 {
 }
diff --git a/accord-core/src/main/java/accord/local/Node.java b/accord-core/src/main/java/accord/local/Node.java
index 1435142..375f88a 100644
--- a/accord-core/src/main/java/accord/local/Node.java
+++ b/accord-core/src/main/java/accord/local/Node.java
@@ -28,6 +28,7 @@ import java.util.function.IntFunction;
 import java.util.function.LongSupplier;
 import java.util.function.Supplier;
 
+import accord.messages.*;
 import com.google.common.annotations.VisibleForTesting;
 
 import accord.api.*;
@@ -44,7 +45,6 @@ import accord.api.ProgressLog;
 import accord.api.Scheduler;
 import accord.api.DataStore;
 import accord.coordinate.Recover;
-import accord.messages.Callback;
 import accord.messages.CheckStatus.CheckStatusOk;
 import accord.messages.ReplyContext;
 import accord.messages.Request;
@@ -59,7 +59,9 @@ import accord.primitives.Keys;
 import accord.primitives.Timestamp;
 import accord.txn.Txn;
 import accord.primitives.TxnId;
+import com.google.common.util.concurrent.Futures;
 import org.apache.cassandra.utils.concurrent.Future;
+import org.apache.cassandra.utils.concurrent.FutureCombiner;
 
 public class Node implements ConfigurationService.Listener
 {
@@ -150,6 +152,11 @@ public class Node implements ConfigurationService.Listener
         onTopologyUpdate(topology, false);
     }
 
+    public CommandStores commandStores()
+    {
+        return commandStores;
+    }
+
     public ConfigurationService configService()
     {
         return configService;
@@ -252,74 +259,74 @@ public class Node implements ConfigurationService.Listener
         return nowSupplier.getAsLong();
     }
 
-    public void forEachLocal(Consumer<CommandStore> forEach)
+    public void forEachLocal(TxnRequest request, long epoch, Consumer<CommandStore> forEach)
     {
-        commandStores.forEach(forEach);
+        commandStores.forEach(request, epoch, forEach);
     }
 
-    public void forEachLocal(Keys keys, long epoch, Consumer<CommandStore> forEach)
+    public void forEachLocal(TxnRequest request, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
     {
-        commandStores.forEach(keys, epoch, forEach);
+        commandStores.forEach(request, minEpoch, maxEpoch, forEach);
     }
 
-    public void forEachLocal(Keys keys, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
+    public void forEachLocal(PreLoadContext context, Keys keys, long minEpoch, long maxEpoch, Consumer<CommandStore> forEach)
     {
-        commandStores.forEach(keys, minEpoch, maxEpoch, forEach);
+        commandStores.forEach(context, keys, minEpoch, maxEpoch, forEach);
     }
 
-    public void forEachLocal(Keys keys, Timestamp minAt, Timestamp maxAt, Consumer<CommandStore> forEach)
+    public void forEachLocalSince(PreLoadContext context, Keys keys, Timestamp since, Consumer<CommandStore> forEach)
     {
-        commandStores.forEach(keys, minAt.epoch, maxAt.epoch, forEach);
+        commandStores.forEach(context, keys, since.epoch, Long.MAX_VALUE, forEach);
     }
 
-    public void forEachLocalSince(Keys keys, Timestamp since, Consumer<CommandStore> forEach)
+    public void forEachLocalSince(TxnRequest request, Timestamp since, Consumer<CommandStore> forEach)
     {
-        commandStores.forEach(keys, since.epoch, Long.MAX_VALUE, forEach);
+        commandStores.forEach(request, since.epoch, Long.MAX_VALUE, forEach);
     }
 
-    public void forEachLocalSince(Keys keys, long sinceEpoch, Consumer<CommandStore> forEach)
+    public void forEachLocalSince(PreLoadContext context, Keys keys, long sinceEpoch, Consumer<CommandStore> forEach)
     {
-        commandStores.forEach(keys, sinceEpoch, Long.MAX_VALUE, forEach);
+        commandStores.forEachSince(context, keys, sinceEpoch, forEach);
     }
 
-    public <T> T mapReduceLocal(Keys keys, Timestamp at, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    public <T> T mapReduceLocal(TxnRequest request, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
     {
-        return commandStores.mapReduce(keys, at.epoch, at.epoch, map, reduce);
+        return commandStores.mapReduce(request, request.scope(), minEpoch, maxEpoch, map, reduce);
     }
 
-    public <T> T mapReduceLocal(Keys keys, long epoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    public <T> List<T> mapLocal(TxnRequest request, long minEpoch, long maxEpoch, Function<CommandStore, T> map)
     {
-        return commandStores.mapReduce(keys, epoch, epoch, map, reduce);
+        return Futures.getUnchecked(FutureCombiner.allOf(commandStores.mapAsync(request, request.scope(), minEpoch, maxEpoch, map)));
     }
 
-    public <T> T mapReduceLocal(Keys keys, long minEpoch, long maxEpoch, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    public <T> T mapReduceLocalSince(PreLoadContext context, Keys keys, Timestamp since, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
     {
-        return commandStores.mapReduce(keys, minEpoch, maxEpoch, map, reduce);
+        return commandStores.mapReduce(context, keys, since.epoch, Long.MAX_VALUE, map, reduce);
     }
 
-    public <T> T mapReduceLocalSince(Keys keys, Timestamp since, Function<CommandStore, T> map, BiFunction<T, T, T> reduce)
+    public <T> List<T> mapLocalSince(PreLoadContext context, Keys keys, Timestamp since, Function<CommandStore, T> map)
     {
-        return commandStores.mapReduce(keys, since.epoch, Long.MAX_VALUE, map, reduce);
+        return Futures.getUnchecked(FutureCombiner.allOf(commandStores.mapAsync(context, keys, since.epoch, Long.MAX_VALUE, map)));
     }
 
-    public <T> T ifLocal(Key key, Timestamp at, Function<CommandStore, T> ifLocal)
+    public <T> T ifLocal(PreLoadContext context, Key key, Timestamp at, Function<CommandStore, T> ifLocal)
     {
-        return ifLocal(key, at.epoch, ifLocal);
+        return ifLocal(context, key, at.epoch, ifLocal);
     }
 
-    public <T> T ifLocal(Key key, long epoch, Function<CommandStore, T> ifLocal)
+    public <T> T ifLocal(PreLoadContext context, Key key, long epoch, Function<CommandStore, T> ifLocal)
     {
-        return commandStores.mapReduce(key, epoch, ifLocal, (a, b) -> { throw new IllegalStateException();} );
+        return commandStores.mapReduce(context, key, epoch, ifLocal, (a, b) -> { throw new IllegalStateException();} );
     }
 
-    public <T> T ifLocalSince(Key key, Timestamp since, Function<CommandStore, T> ifLocal)
+    public <T> T ifLocalSince(PreLoadContext context, Key key, Timestamp since, Function<CommandStore, T> ifLocal)
     {
-        return ifLocalSince(key, since.epoch, ifLocal);
+        return ifLocalSince(context, key, since.epoch, ifLocal);
     }
 
-    public <T> T ifLocalSince(Key key, long epoch, Function<CommandStore, T> ifLocal)
+    public <T> T ifLocalSince(PreLoadContext context, Key key, long epoch, Function<CommandStore, T> ifLocal)
     {
-        return commandStores.mapReduceSince(key, epoch, ifLocal, (a, b) -> { throw new IllegalStateException();} );
+        return commandStores.mapReduceSince(context, key, epoch, ifLocal, (a, b) -> { throw new IllegalStateException();} );
     }
 
     public <T extends Collection<CommandStore>> T collectLocal(Keys keys, Timestamp at, IntFunction<T> factory)
@@ -405,7 +412,7 @@ public class Node implements ConfigurationService.Listener
 
     public Future<Result> coordinate(TxnId txnId, Txn txn)
     {
-        // TODO: The combination of updating the epoch of the next timestamp with epochs we don’t have topologies for,
+        // TODO: The combination of updating the epoch of the next timestamp with epochs we don't have topologies for,
         //  and requiring preaccept to talk to its topology epoch means that learning of a new epoch via timestamp
         //  (ie not via config service) will halt any new txns from a node until it receives this topology
         Future<Result> result = withEpoch(txnId.epoch, () -> initiateCoordination(txnId, txn));
@@ -417,11 +424,11 @@ public class Node implements ConfigurationService.Listener
 
     private Future<Result> initiateCoordination(TxnId txnId, Txn txn)
     {
-        Key homeKey = trySelectHomeKey(txnId, txn.keys);
+        Key homeKey = trySelectHomeKey(txnId, txn.keys());
         if (homeKey == null)
         {
             homeKey = selectRandomHomeKey(txnId);
-            txn = new Txn(txn.keys.with(homeKey), txn.read, txn.query, txn.update);
+            txn = new Txn.InMemory(txn.keys().with(homeKey), txn.read(), txn.query(), txn.update());
         }
         return Coordinate.coordinate(this, txnId, txn, homeKey);
     }
diff --git a/accord-core/src/main/java/accord/local/PartialCommand.java b/accord-core/src/main/java/accord/local/PartialCommand.java
new file mode 100644
index 0000000..95efdd0
--- /dev/null
+++ b/accord-core/src/main/java/accord/local/PartialCommand.java
@@ -0,0 +1,47 @@
+package accord.local;
+
+import accord.primitives.Deps;
+import accord.primitives.Keys;
+import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
+import accord.txn.Txn;
+
+/**
+ * A minimal, read only view of a full command. Provided so C* can denormalize command data onto
+ * CommandsForKey and Command implementations. Implementer must guarantee that PartialCommand
+ * implementations behave as an immutable view and are always in sync with the 'real' command;
+ */
+public interface PartialCommand
+{
+    interface WithDeps extends PartialCommand
+    {
+        Deps savedDeps();
+    }
+
+    TxnId txnId();
+    Txn txn();
+
+    default Keys someKeys()
+    {
+        if (txn() != null)
+            return txn().keys();
+
+        return null;
+    }
+
+    Timestamp executeAt();
+
+    Status status();
+
+    default boolean hasBeen(Status status)
+    {
+        return status().hasBeen(status);
+    }
+
+    default boolean is(Status status)
+    {
+        return status() == status;
+    }
+
+    void removeListener(Listener listener);
+}
diff --git a/accord-core/src/main/java/accord/local/PreLoadContext.java b/accord-core/src/main/java/accord/local/PreLoadContext.java
new file mode 100644
index 0000000..65946e5
--- /dev/null
+++ b/accord-core/src/main/java/accord/local/PreLoadContext.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.local;
+
+import accord.api.Key;
+import accord.primitives.TxnId;
+
+import java.util.Collections;
+
+/**
+ * Lists txnids and keys of commands and commands for key that will be needed for an operation. Used
+ * to ensure the necessary state is in memory for an operation before it executes.
+ */
+public interface PreLoadContext
+{
+    /**
+     * @return ids of the {@link Command} objects that need to be loaded into memory before this operation is run
+     */
+    Iterable<TxnId> txnIds();
+
+    /**
+     * @return keys of the {@link CommandsForKey} objects that need to be loaded into memory before this operation is run
+     */
+    Iterable<Key> keys();
+
+    static PreLoadContext contextFor(Iterable<TxnId> txnIds, Iterable<Key> keys)
+    {
+        return new PreLoadContext()
+        {
+            @Override
+            public Iterable<TxnId> txnIds() { return txnIds; }
+
+            @Override
+            public Iterable<Key> keys() { return keys; }
+        };
+    }
+
+    static PreLoadContext contextFor(TxnId txnId, Iterable<Key> keys)
+    {
+        return contextFor(Collections.singleton(txnId), keys);
+    }
+
+    static PreLoadContext contextFor(TxnId txnId)
+    {
+        return contextFor(Collections.singleton(txnId), Collections.emptyList());
+    }
+
+    static PreLoadContext contextFor(Key key)
+    {
+        return contextFor(Collections.emptyList(), Collections.singleton(key));
+    }
+}
diff --git a/accord-core/src/main/java/accord/messages/Accept.java b/accord-core/src/main/java/accord/messages/Accept.java
index 06e7a48..14e347f 100644
--- a/accord-core/src/main/java/accord/messages/Accept.java
+++ b/accord-core/src/main/java/accord/messages/Accept.java
@@ -18,22 +18,32 @@
 
 package accord.messages;
 
+import accord.primitives.*;
+import accord.local.PreLoadContext;
 import accord.messages.TxnRequest.WithUnsynced;
 import accord.local.Node.Id;
-import accord.topology.Topologies;
 import accord.api.Key;
-import accord.primitives.Ballot;
+import accord.local.CommandStore;
+import accord.topology.Topologies;
 import accord.local.Node;
-import accord.primitives.Timestamp;
 import accord.local.Command;
-import accord.primitives.Deps;
 import accord.txn.Txn;
-import accord.primitives.TxnId;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.Collections;
 
 import static accord.messages.PreAccept.calculateDeps;
 
 public class Accept extends WithUnsynced
 {
+    public static class SerializerSupport
+    {
+        public static Accept create(Keys scope, long epoch, TxnId txnId, Ballot ballot, Key homeKey, Txn txn, Timestamp executeAt, Deps deps)
+        {
+            return new Accept(scope, epoch, txnId, ballot, homeKey, txn, executeAt, deps);
+        }
+    }
+
     public final Ballot ballot;
     public final Key homeKey;
     public final Txn txn;
@@ -42,7 +52,7 @@ public class Accept extends WithUnsynced
 
     public Accept(Id to, Topologies topologies, Ballot ballot, TxnId txnId, Key homeKey, Txn txn, Timestamp executeAt, Deps deps)
     {
-        super(to, topologies, txn.keys, txnId);
+        super(to, topologies, txn.keys(), txnId);
         this.ballot = ballot;
         this.homeKey = homeKey;
         this.txn = txn;
@@ -50,18 +60,33 @@ public class Accept extends WithUnsynced
         this.deps = deps;
     }
 
-    public void process(Node node, Node.Id replyToNode, ReplyContext replyContext)
+    protected Accept(Keys scope, long epoch, TxnId txnId, Ballot ballot, Key homeKey, Txn txn, Timestamp executeAt, Deps deps)
+    {
+        super(scope, epoch, txnId);
+        this.ballot = ballot;
+        this.homeKey = homeKey;
+        this.txn = txn;
+        this.executeAt = executeAt;
+        this.deps = deps;
+    }
+
+    @VisibleForTesting
+    public AcceptReply process(CommandStore instance, Key progressKey)
     {
-        Key progressKey = progressKey(node, homeKey);
         // TODO: when we begin expunging old epochs we need to ensure we handle the case where we do not fully handle the keys;
         //       since this will likely imply the transaction has been applied or aborted we can indicate the coordinator
         //       should enquire as to the result
-        node.reply(replyToNode, replyContext, node.mapReduceLocal(scope(), minEpoch, executeAt.epoch, instance -> {
-            Command command = instance.command(txnId);
-            if (!command.accept(ballot, txn, homeKey, progressKey, executeAt, deps))
-                return new AcceptNack(txnId, command.promised());
-            return new AcceptOk(txnId, calculateDeps(instance, txnId, txn, executeAt));
-        }, (r1, r2) -> {
+        Command command = instance.command(txnId);
+        if (!command.accept(ballot, txn, homeKey, progressKey, executeAt, deps))
+            return new AcceptNack(txnId, command.promised());
+        return new AcceptOk(txnId, calculateDeps(instance, txnId, txn, executeAt));
+    }
+
+    public void process(Node node, Node.Id replyToNode, ReplyContext replyContext)
+    {
+        Key progressKey = progressKey(node, homeKey);
+        node.reply(replyToNode, replyContext, node.mapReduceLocal(this, minEpoch, executeAt.epoch, cs -> process(cs, progressKey),
+        (r1, r2) -> {
             if (!r1.isOK()) return r1;
             if (!r2.isOK()) return r2;
             AcceptOk ok1 = (AcceptOk) r1;
@@ -72,13 +97,26 @@ public class Accept extends WithUnsynced
         }));
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Collections.singleton(txnId);
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return txn.keys();
+    }
+
     @Override
     public MessageType type()
     {
         return MessageType.ACCEPT_REQ;
     }
 
-    public static class Invalidate implements EpochRequest
+    // TODO (now): can EpochRequest inherit TxnOperation?
+    public static class Invalidate implements EpochRequest, PreLoadContext
     {
         public final Ballot ballot;
         public final TxnId txnId;
@@ -93,7 +131,7 @@ public class Accept extends WithUnsynced
 
         public void process(Node node, Node.Id replyToNode, ReplyContext replyContext)
         {
-            node.reply(replyToNode, replyContext, node.ifLocal(someKey, txnId.epoch, instance -> {
+            node.reply(replyToNode, replyContext, node.ifLocal(this, someKey, txnId.epoch, instance -> {
                 Command command = instance.command(txnId);
                 if (!command.acceptInvalidate(ballot))
                     return new AcceptNack(txnId, command.promised());
@@ -101,6 +139,18 @@ public class Accept extends WithUnsynced
             }));
         }
 
+        @Override
+        public Iterable<TxnId> txnIds()
+        {
+            return Collections.singleton(txnId);
+        }
+
+        @Override
+        public Iterable<Key> keys()
+        {
+            return Collections.emptyList();
+        }
+
         @Override
         public MessageType type()
         {
diff --git a/accord-core/src/main/java/accord/messages/Apply.java b/accord-core/src/main/java/accord/messages/Apply.java
index abcc10d..f96f983 100644
--- a/accord-core/src/main/java/accord/messages/Apply.java
+++ b/accord-core/src/main/java/accord/messages/Apply.java
@@ -19,6 +19,8 @@
 package accord.messages;
 
 import accord.api.Key;
+import accord.primitives.Keys;
+import accord.utils.ReducingFuture;
 import accord.local.Node;
 import accord.local.Node.Id;
 import accord.api.Result;
@@ -28,15 +30,28 @@ import accord.primitives.Timestamp;
 import accord.txn.Writes;
 import accord.txn.Txn;
 import accord.primitives.TxnId;
+import com.google.common.collect.Iterables;
+import org.apache.cassandra.utils.concurrent.Future;
+
+import java.util.Collections;
+import java.util.List;
 
 import static accord.messages.MessageType.APPLY_REQ;
 import static accord.messages.MessageType.APPLY_RSP;
 
 public class Apply extends TxnRequest
 {
+    public static class SerializationSupport
+    {
+        public static Apply create(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
+        {
+            return new Apply(scope, waitForEpoch, txnId, txn, homeKey, executeAt, deps, writes, result);
+        }
+    }
+
     public final TxnId txnId;
     public final Txn txn;
-    protected final Key homeKey;
+    public final Key homeKey;
     public final Timestamp executeAt;
     public final Deps deps;
     public final Writes writes;
@@ -44,7 +59,7 @@ public class Apply extends TxnRequest
 
     public Apply(Node.Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
     {
-        super(to, topologies, txn.keys);
+        super(to, topologies, txn.keys());
         this.txnId = txnId;
         this.txn = txn;
         this.homeKey = homeKey;
@@ -54,13 +69,41 @@ public class Apply extends TxnRequest
         this.result = result;
     }
 
+    protected Apply(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
+    {
+        super(scope, waitForEpoch);
+        this.txnId = txnId;
+        this.txn = txn;
+        this.homeKey = homeKey;
+        this.executeAt = executeAt;
+        this.deps = deps;
+        this.writes = writes;
+        this.result = result;
+    }
+
     public void process(Node node, Id replyToNode, ReplyContext replyContext)
     {
-        Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey);
-        node.forEachLocalSince(scope(), executeAt,
-                               instance -> instance.command(txnId).apply(txn, homeKey, progressKey, executeAt, deps, writes, result));
-        // note, we do not also commit here if txnId.epoch != executeAt.epoch, as the scope() for a commit would be different
-        node.reply(replyToNode, replyContext, ApplyOk.INSTANCE);
+        Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey);
+        List<Future<Void>> futures = node.mapLocalSince(this, scope(), executeAt, instance -> instance.command(txnId).apply(txn, homeKey, progressKey, executeAt, deps, writes, result));
+
+        ReducingFuture.reduce(futures, (l, r) -> null).addCallback((unused, failure) -> {
+            if (failure == null)
+                // TODO: notify coordinator of failures
+                // note, we do not also commit here if txnId.epoch != executeAt.epoch, as the scope() for a commit would be different
+                node.reply(replyToNode, replyContext, ApplyOk.INSTANCE);
+        });
+    }
+
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Iterables.concat(Collections.singleton(txnId), deps.txnIds());
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return txn.keys();
     }
 
     @Override
diff --git a/accord-core/src/main/java/accord/messages/BeginInvalidate.java b/accord-core/src/main/java/accord/messages/BeginInvalidate.java
index dbd3452..d9954c6 100644
--- a/accord-core/src/main/java/accord/messages/BeginInvalidate.java
+++ b/accord-core/src/main/java/accord/messages/BeginInvalidate.java
@@ -24,6 +24,7 @@ import accord.local.Command;
 import accord.local.Node;
 import accord.local.Node.Id;
 import accord.local.Status;
+import accord.local.PreLoadContext;
 import accord.messages.BeginRecovery.RecoverNack;
 import accord.messages.BeginRecovery.RecoverOk;
 import accord.messages.BeginRecovery.RecoverReply;
@@ -34,11 +35,13 @@ import accord.txn.Txn;
 import accord.primitives.TxnId;
 import accord.txn.Writes;
 
-public class BeginInvalidate implements EpochRequest
+import java.util.Collections;
+
+public class BeginInvalidate implements EpochRequest, PreLoadContext
 {
-    final Ballot ballot;
-    final TxnId txnId;
-    final Key someKey;
+    public final Ballot ballot;
+    public final TxnId txnId;
+    public final Key someKey;
 
     public BeginInvalidate(TxnId txnId, Key someKey, Ballot ballot)
     {
@@ -49,7 +52,7 @@ public class BeginInvalidate implements EpochRequest
 
     public void process(Node node, Id replyToNode, ReplyContext replyContext)
     {
-        RecoverReply reply = node.ifLocal(someKey, txnId, instance -> {
+        RecoverReply reply = node.ifLocal(this, someKey, txnId, instance -> {
             Command command = instance.command(txnId);
 
             if (!command.preAcceptInvalidate(ballot))
@@ -62,6 +65,18 @@ public class BeginInvalidate implements EpochRequest
         node.reply(replyToNode, replyContext, reply);
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Collections.singleton(txnId);
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return Collections.emptyList();
+    }
+
     @Override
     public long waitForEpoch()
     {
@@ -106,6 +121,12 @@ public class BeginInvalidate implements EpochRequest
         {
             return toString("InvalidateOk");
         }
+
+        @Override
+        public MessageType type()
+        {
+            return MessageType.BEGIN_INVALIDATE_RSP;
+        }
     }
 
     public static class InvalidateNack extends RecoverNack
@@ -130,5 +151,11 @@ public class BeginInvalidate implements EpochRequest
         {
             return "InvalidateNack{supersededBy:" + supersededBy + '}';
         }
+
+        @Override
+        public MessageType type()
+        {
+            return MessageType.BEGIN_INVALIDATE_RSP;
+        }
     }
 }
diff --git a/accord-core/src/main/java/accord/messages/BeginRecovery.java b/accord-core/src/main/java/accord/messages/BeginRecovery.java
index 8fb8445..610e622 100644
--- a/accord-core/src/main/java/accord/messages/BeginRecovery.java
+++ b/accord-core/src/main/java/accord/messages/BeginRecovery.java
@@ -18,6 +18,8 @@
 
 package accord.messages;
 
+import accord.api.Key;
+import accord.local.*;
 import accord.api.Result;
 import accord.coordinate.Persist;
 import accord.topology.Topologies;
@@ -25,22 +27,18 @@ import accord.topology.Topologies;
 import java.util.List;
 import java.util.stream.Stream;
 
-import accord.api.Key;
-import accord.local.CommandStore;
-import accord.local.CommandsForKey;
 import accord.primitives.Keys;
 import accord.txn.Writes;
 import accord.primitives.Ballot;
-import accord.local.Node;
 import accord.local.Node.Id;
 import accord.primitives.Timestamp;
-import accord.local.Command;
 import accord.primitives.Deps;
-import accord.local.Status;
 import accord.txn.Txn;
 import accord.primitives.TxnId;
 import com.google.common.base.Preconditions;
 
+import java.util.Collections;
+
 import static accord.local.Status.Accepted;
 import static accord.local.Status.Applied;
 import static accord.local.Status.Committed;
@@ -50,14 +48,23 @@ import static accord.messages.PreAccept.calculateDeps;
 
 public class BeginRecovery extends TxnRequest
 {
-    final TxnId txnId;
-    final Txn txn;
-    final Key homeKey;
-    final Ballot ballot;
+    public final TxnId txnId;
+    public final Txn txn;
+    public final Key homeKey;
+    public final Ballot ballot;
 
     public BeginRecovery(Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Ballot ballot)
     {
-        super(to, topologies, txn.keys);
+        super(to, topologies, txn.keys());
+        this.txnId = txnId;
+        this.txn = txn;
+        this.homeKey = homeKey;
+        this.ballot = ballot;
+    }
+
+    public BeginRecovery(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Key homeKey, Ballot ballot)
+    {
+        super(scope, waitForEpoch);
         this.txnId = txnId;
         this.txn = txn;
         this.homeKey = homeKey;
@@ -66,8 +73,8 @@ public class BeginRecovery extends TxnRequest
 
     public void process(Node node, Id replyToNode, ReplyContext replyContext)
     {
-        Key progressKey = node.selectProgressKey(txnId, txn.keys, homeKey);
-        RecoverReply reply = node.mapReduceLocal(scope(), txnId.epoch, txnId.epoch, instance -> {
+        Key progressKey = node.selectProgressKey(txnId, txn.keys(), homeKey);
+        RecoverReply reply = node.mapReduceLocal(this, txnId.epoch, txnId.epoch, instance -> {
             Command command = instance.command(txnId);
 
             if (!command.recover(txn, homeKey, progressKey, ballot))
@@ -86,19 +93,19 @@ public class BeginRecovery extends TxnRequest
             }
             else
             {
-                rejectsFastPath = uncommittedStartedAfter(instance, txnId, txn.keys)
+                rejectsFastPath = uncommittedStartedAfter(instance, txnId, txn.keys())
                                              .filter(c -> c.hasBeen(Accepted))
                                              .anyMatch(c -> !c.savedDeps().contains(txnId));
                 if (!rejectsFastPath)
-                    rejectsFastPath = committedExecutesAfter(instance, txnId, txn.keys)
+                    rejectsFastPath = committedExecutesAfter(instance, txnId, txn.keys())
                                          .anyMatch(c -> !c.savedDeps().contains(txnId));
 
                 // TODO: introduce some good unit tests for verifying these two functions in a real repair scenario
                 // committed txns with an earlier txnid and have our txnid as a dependency
-                earlierCommittedWitness = committedStartedBeforeAndDidWitness(instance, txnId, txn.keys);
+                earlierCommittedWitness = committedStartedBeforeAndDidWitness(instance, txnId, txn.keys());
 
                 // accepted txns with an earlier txnid that don't have our txnid as a dependency
-                earlierAcceptedNoWitness = acceptedStartedBeforeAndDidNotWitness(instance, txnId, txn.keys);
+                earlierAcceptedNoWitness = acceptedStartedBeforeAndDidNotWitness(instance, txnId, txn.keys());
             }
             return new RecoverOk(txnId, command.status(), command.accepted(), command.executeAt(), deps, earlierCommittedWitness, earlierAcceptedNoWitness, rejectsFastPath, command.writes(), command.result());
         }, (r1, r2) -> { // TODO: reduce function that constructs a list to reduce at the end
@@ -164,6 +171,18 @@ public class BeginRecovery extends TxnRequest
         }
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Collections.singleton(txnId);
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return txn.keys();
+    }
+
     @Override
     public MessageType type()
     {
@@ -303,7 +322,7 @@ public class BeginRecovery extends TxnRequest
 
                 // committed txns with an earlier txnid and have our txnid as a dependency
                 builder.nextKey(key);
-                forKey.uncommitted.headMap(txnId, false).forEach((ts, command) -> {
+                forKey.uncommitted().before(txnId).forEach(command -> {
                     if (command.is(Accepted) && !command.savedDeps().contains(txnId) && command.executeAt().compareTo(txnId) > 0)
                         builder.add(command.txnId());
                 });
@@ -323,7 +342,7 @@ public class BeginRecovery extends TxnRequest
 
                 // committed txns with an earlier txnid and have our txnid as a dependency
                 builder.nextKey(key);
-                forKey.committedById.headMap(txnId, false).forEach((ts, command) -> {
+                forKey.committedById().before(txnId).forEach(command -> {
                     if (command.savedDeps().contains(txnId))
                         builder.add(command.txnId());
                 });
@@ -332,23 +351,23 @@ public class BeginRecovery extends TxnRequest
         }
     }
 
-    private static Stream<Command> uncommittedStartedAfter(CommandStore commandStore, TxnId startedAfter, Keys keys)
+    private static Stream<PartialCommand.WithDeps> uncommittedStartedAfter(CommandStore commandStore, TxnId startedAfter, Keys keys)
     {
         return keys.stream().flatMap(key -> {
             CommandsForKey forKey = commandStore.maybeCommandsForKey(key);
             if (forKey == null)
                 return Stream.of();
-            return forKey.uncommitted.tailMap(startedAfter, false).values().stream();
+            return forKey.uncommitted().after(startedAfter);
         });
     }
 
-    private static Stream<Command> committedExecutesAfter(CommandStore commandStore, TxnId startedAfter, Keys keys)
+    private static Stream<PartialCommand.WithDeps> committedExecutesAfter(CommandStore commandStore, TxnId startedAfter, Keys keys)
     {
         return keys.stream().flatMap(key -> {
             CommandsForKey forKey = commandStore.maybeCommandsForKey(key);
             if (forKey == null)
                 return Stream.of();
-            return forKey.committedByExecuteAt.tailMap(startedAfter, false).values().stream();
+            return forKey.committedByExecuteAt().after(startedAfter);
         });
     }
 }
diff --git a/accord-core/src/main/java/accord/messages/CheckStatus.java b/accord-core/src/main/java/accord/messages/CheckStatus.java
index 984297d..9bc94fa 100644
--- a/accord-core/src/main/java/accord/messages/CheckStatus.java
+++ b/accord-core/src/main/java/accord/messages/CheckStatus.java
@@ -27,11 +27,14 @@ import accord.local.Status;
 import accord.primitives.Ballot;
 import accord.primitives.Deps;
 import accord.primitives.Timestamp;
+import accord.local.PreLoadContext;
 import accord.txn.Txn;
 import accord.primitives.TxnId;
 import accord.txn.Writes;
 
-public class CheckStatus implements Request
+import java.util.Collections;
+
+public class CheckStatus implements Request, PreLoadContext
 {
     // order is important
     public enum IncludeInfo
@@ -49,10 +52,10 @@ public class CheckStatus implements Request
         }
     }
 
-    final TxnId txnId;
-    final Key key; // the key's commandStore to consult - not necessarily the homeKey
-    final long epoch;
-    final IncludeInfo includeInfo;
+    public final TxnId txnId;
+    public final Key key; // the key's commandStore to consult - not necessarily the homeKey
+    public final long epoch;
+    public final IncludeInfo includeInfo;
 
     public CheckStatus(TxnId txnId, Key key, long epoch, IncludeInfo includeInfo)
     {
@@ -62,10 +65,22 @@ public class CheckStatus implements Request
         this.includeInfo = includeInfo;
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Collections.singleton(txnId);
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return Collections.emptyList();
+    }
+
     public void process(Node node, Id replyToNode, ReplyContext replyContext)
     {
 
-        Reply reply = node.ifLocal(key, epoch, instance -> {
+        Reply reply = node.ifLocal(this, key, epoch, instance -> {
             Command command = instance.command(txnId);
             boolean includeInfo = this.includeInfo.include(command.status());
             if (includeInfo)
@@ -107,7 +122,7 @@ public class CheckStatus implements Request
         public final boolean isCoordinating;
         public final boolean hasExecutedOnAllShards;
 
-        CheckStatusOk(Status status, Ballot promised, Ballot accepted, boolean isCoordinating, boolean hasExecutedOnAllShards)
+        public CheckStatusOk(Status status, Ballot promised, Ballot accepted, boolean isCoordinating, boolean hasExecutedOnAllShards)
         {
             this.status = status;
             this.promised = promised;
@@ -187,7 +202,7 @@ public class CheckStatus implements Request
         public final Writes writes;
         public final Result result;
 
-        CheckStatusOkFull(Status status, Ballot promised, Ballot accepted, boolean isCoordinating, boolean hasExecutedOnAllShards,
+        public CheckStatusOkFull(Status status, Ballot promised, Ballot accepted, boolean isCoordinating, boolean hasExecutedOnAllShards,
                           Txn txn, Key homeKey, Timestamp executeAt, Deps deps, Writes writes, Result result)
         {
             super(status, promised, accepted, isCoordinating, hasExecutedOnAllShards);
@@ -253,7 +268,7 @@ public class CheckStatus implements Request
             return MessageType.CHECK_STATUS_RSP;
         }
 
-        static CheckStatusNack nack()
+        public static CheckStatusNack nack()
         {
             return instance;
         }
diff --git a/accord-core/src/main/java/accord/messages/Commit.java b/accord-core/src/main/java/accord/messages/Commit.java
index 004d683..4daab5b 100644
--- a/accord-core/src/main/java/accord/messages/Commit.java
+++ b/accord-core/src/main/java/accord/messages/Commit.java
@@ -19,9 +19,11 @@
 package accord.messages;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 import accord.api.Key;
+import accord.utils.ReducingFuture;
 import accord.local.Node;
 import accord.local.Node.Id;
 import accord.topology.Topologies;
@@ -30,17 +32,31 @@ import accord.primitives.Timestamp;
 import accord.primitives.Deps;
 import accord.txn.Txn;
 import accord.primitives.TxnId;
+import com.google.common.collect.Iterables;
+import org.apache.cassandra.utils.concurrent.Future;
 
 // TODO: CommitOk responses, so we can send again if no reply received? Or leave to recovery?
 public class Commit extends ReadData
 {
-    public final Deps deps;
+    public static class SerializerSupport
+    {
+        public static Commit create(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt, boolean read)
+        {
+            return new Commit(scope, waitForEpoch, txnId, txn, deps, homeKey, executeAt, read);
+        }
+    }
+
     public final boolean read;
 
     public Commit(Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt, Deps deps, boolean read)
     {
-        super(to, topologies, txnId, txn, homeKey, executeAt);
-        this.deps = deps;
+        super(to, topologies, txnId, txn, deps, homeKey, executeAt);
+        this.read = read;
+    }
+
+    public Commit(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt, boolean read)
+    {
+        super(scope, waitForEpoch, txnId, txn, deps, homeKey, executeAt);
         this.read = read;
     }
 
@@ -99,19 +115,39 @@ public class Commit extends ReadData
     {
         for (Node.Id to : commitTo.nodes())
         {
-            Invalidate send = new Invalidate(to, commitTo, txnId, someKeys);
+            // TODO (now) confirm somekeys == txnkeys
+            Invalidate send = new Invalidate(to, commitTo, txnId, someKeys, someKeys);
             node.send(to, send);
         }
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Iterables.concat(Collections.singleton(txnId), deps.txnIds());
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return txn.keys();
+    }
+
     public void process(Node node, Id from, ReplyContext replyContext)
     {
-        Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey);
-        node.forEachLocal(scope(), txnId.epoch, executeAt.epoch,
-                          instance -> instance.command(txnId).commit(txn, homeKey, progressKey, executeAt, deps));
+        Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey);
+        List<Future<Void>> futures = node.mapLocal(this, txnId.epoch, executeAt.epoch,
+                                                   instance -> instance.command(txnId).commitAndBeginExecution(txn, homeKey, progressKey, executeAt, deps));
 
         if (read)
-            super.process(node, from, replyContext);
+        {
+            ReducingFuture.reduce(futures, (l, r) -> null).addCallback((unused, throwable) -> {
+                if (throwable == null)
+                    super.process(node, from, replyContext);
+                else
+                    node.reply(from, replyContext, new ReadNack());
+            });
+        }
     }
 
     @Override
@@ -132,17 +168,46 @@ public class Commit extends ReadData
 
     public static class Invalidate extends TxnRequest
     {
-        final TxnId txnId;
+        public static class SerializerSupport
+        {
+            public static Invalidate create(Keys scope, long waitForEpoch, TxnId txnId, Keys txnKeys)
+            {
+                return new Invalidate(scope, waitForEpoch, txnId, txnKeys);
+            }
+        }
+
+        public final TxnId txnId;
+        public final Keys txnKeys;
 
-        public Invalidate(Id to, Topologies topologies, TxnId txnId, Keys someKeys)
+        public Invalidate(Id to, Topologies topologies, TxnId txnId, Keys txnKeys, Keys someKeys)
         {
             super(to, topologies, someKeys);
             this.txnId = txnId;
+            this.txnKeys = txnKeys;
+        }
+
+        protected Invalidate(Keys scope, long waitForEpoch, TxnId txnId, Keys txnKeys)
+        {
+            super(scope, waitForEpoch);
+            this.txnId = txnId;
+            this.txnKeys = txnKeys;
+        }
+
+        @Override
+        public Iterable<TxnId> txnIds()
+        {
+            return Collections.singleton(txnId);
+        }
+
+        @Override
+        public Iterable<Key> keys()
+        {
+            return Collections.emptyList();
         }
 
         public void process(Node node, Id from, ReplyContext replyContext)
         {
-            node.forEachLocal(scope(), txnId.epoch, instance -> instance.command(txnId).commitInvalidate());
+            node.forEachLocal(this, txnId.epoch, instance -> instance.command(txnId).commitInvalidate());
         }
 
         @Override
diff --git a/accord-core/src/main/java/accord/messages/InformOfPersistence.java b/accord-core/src/main/java/accord/messages/InformOfPersistence.java
index 9cd02c5..c9f44d4 100644
--- a/accord-core/src/main/java/accord/messages/InformOfPersistence.java
+++ b/accord-core/src/main/java/accord/messages/InformOfPersistence.java
@@ -18,23 +18,26 @@
 
 package accord.messages;
 
+import java.util.Collections;
 import java.util.Set;
 
 import accord.api.Key;
+import accord.local.Command;
 import accord.local.Node;
 import accord.local.Node.Id;
 import accord.primitives.Timestamp;
 import accord.primitives.TxnId;
+import accord.local.PreLoadContext;
 
 import static accord.messages.InformOfTxn.InformOfTxnNack.nack;
 import static accord.messages.InformOfTxn.InformOfTxnOk.ok;
 
-public class InformOfPersistence implements Request
+public class InformOfPersistence implements Request, PreLoadContext
 {
-    final TxnId txnId;
-    final Key homeKey;
-    final Timestamp executeAt;
-    final Set<Id> persistedOn;
+    public final TxnId txnId;
+    public final Key homeKey;
+    public final Timestamp executeAt;
+    public final Set<Id> persistedOn;
 
     public InformOfPersistence(TxnId txnId, Key homeKey, Timestamp executeAt, Set<Id> persistedOn)
     {
@@ -46,9 +49,10 @@ public class InformOfPersistence implements Request
 
     public void process(Node node, Id replyToNode, ReplyContext replyContext)
     {
-        Reply reply = node.ifLocal(homeKey, txnId, instance -> {
-            instance.command(txnId).setGloballyPersistent(homeKey, executeAt);
-            instance.progressLog().executedOnAllShards(txnId, persistedOn);
+        Reply reply = node.ifLocal(this, homeKey, txnId, instance -> {
+            Command command = instance.command(txnId);
+            command.setGloballyPersistent(homeKey, executeAt);
+            instance.progressLog().executedOnAllShards(command, persistedOn);
             return ok();
         });
 
@@ -58,6 +62,18 @@ public class InformOfPersistence implements Request
         node.reply(replyToNode, replyContext, reply);
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Collections.singleton(txnId);
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return Collections.emptyList();
+    }
+
     @Override
     public String toString()
     {
diff --git a/accord-core/src/main/java/accord/messages/InformOfTxn.java b/accord-core/src/main/java/accord/messages/InformOfTxn.java
index d49ed14..7fe8e00 100644
--- a/accord-core/src/main/java/accord/messages/InformOfTxn.java
+++ b/accord-core/src/main/java/accord/messages/InformOfTxn.java
@@ -21,17 +21,20 @@ package accord.messages;
 import accord.api.Key;
 import accord.local.Node;
 import accord.local.Node.Id;
+import accord.local.PreLoadContext;
 import accord.txn.Txn;
 import accord.primitives.TxnId;
 
+import java.util.Collections;
+
 import static accord.messages.InformOfTxn.InformOfTxnNack.nack;
 import static accord.messages.InformOfTxn.InformOfTxnOk.ok;
 
-public class InformOfTxn implements EpochRequest
+public class InformOfTxn implements EpochRequest, PreLoadContext
 {
-    final TxnId txnId;
-    final Key homeKey;
-    final Txn txn;
+    public final TxnId txnId;
+    public final Key homeKey;
+    public final Txn txn;
 
     public InformOfTxn(TxnId txnId, Key homeKey, Txn txn)
     {
@@ -40,10 +43,23 @@ public class InformOfTxn implements EpochRequest
         this.txn = txn;
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Collections.singleton(txnId);
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return txn.keys();
+    }
+
+    // TODO (now): audit all messages to ensure requests passed to command store
     public void process(Node node, Id replyToNode, ReplyContext replyContext)
     {
-        Key progressKey = node.selectProgressKey(txnId, txn.keys, homeKey);
-        Reply reply = node.ifLocal(homeKey, txnId, instance -> {
+        Key progressKey = node.selectProgressKey(txnId, txn.keys(), homeKey);
+        Reply reply = node.ifLocal(this, homeKey, txnId, instance -> {
             instance.command(txnId).preaccept(txn, homeKey, progressKey);
             return ok();
         });
@@ -78,7 +94,7 @@ public class InformOfTxn implements EpochRequest
             return MessageType.INFORM_RSP;
         }
 
-        static InformOfTxnReply ok()
+        public static InformOfTxnReply ok()
         {
             return instance;
         }
@@ -108,7 +124,7 @@ public class InformOfTxn implements EpochRequest
             return MessageType.INFORM_RSP;
         }
 
-        static InformOfTxnReply nack()
+        public static InformOfTxnReply nack()
         {
             return instance;
         }
diff --git a/accord-core/src/main/java/accord/messages/PreAccept.java b/accord-core/src/main/java/accord/messages/PreAccept.java
index 4e7d6df..ba1db7e 100644
--- a/accord-core/src/main/java/accord/messages/PreAccept.java
+++ b/accord-core/src/main/java/accord/messages/PreAccept.java
@@ -18,40 +18,45 @@
 
 package accord.messages;
 
-import java.util.*;
-import java.util.function.BiConsumer;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.function.Consumer;
 
+import accord.local.*;
 import com.google.common.annotations.VisibleForTesting;
 
 import accord.api.Key;
-import accord.local.CommandStore;
-import accord.local.CommandsForKey;
-import accord.local.Node;
 import accord.local.Node.Id;
 import accord.messages.TxnRequest.WithUnsynced;
 import accord.topology.Topologies;
 import accord.primitives.Keys;
 import accord.primitives.Timestamp;
-import accord.local.Command;
 import accord.primitives.Deps;
 import accord.txn.Txn;
 import accord.primitives.TxnId;
 
 public class PreAccept extends WithUnsynced
 {
+    public static class SerializerSupport
+    {
+        public static PreAccept create(Keys scope, long epoch, TxnId txnId, Txn txn, Key homeKey)
+        {
+            return new PreAccept(scope, epoch, txnId, txn, homeKey);
+        }
+    }
+
     public final Key homeKey;
     public final Txn txn;
     public final long maxEpoch;
 
     public PreAccept(Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey)
     {
-        super(to, topologies, txn.keys, txnId);
+        super(to, topologies, txn.keys(), txnId);
         this.homeKey = homeKey;
         this.txn = txn;
         this.maxEpoch = topologies.currentEpoch();
     }
 
-    @VisibleForTesting
     public PreAccept(Keys scope, long epoch, TxnId txnId, Txn txn, Key homeKey)
     {
         super(scope, epoch, txnId);
@@ -60,19 +65,36 @@ public class PreAccept extends WithUnsynced
         this.maxEpoch = epoch;
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Collections.singleton(txnId);
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return txn.keys();
+    }
+
+    @VisibleForTesting
+    public PreAcceptReply process(CommandStore instance, Key progressKey)
+    {
+        // note: this diverges from the paper, in that instead of waiting for JoinShard,
+        //       we PreAccept to both old and new topologies and require quorums in both.
+        //       This necessitates sending to ALL replicas of old topology, not only electorate (as fast path may be unreachable).
+        Command command = instance.command(txnId);
+        if (!command.preaccept(txn, homeKey, progressKey))
+            return PreAcceptNack.INSTANCE;
+        return new PreAcceptOk(txnId, command.executeAt(), calculateDeps(instance, txnId, txn, txnId));
+    }
+
     public void process(Node node, Id from, ReplyContext replyContext)
     {
         // TODO: verify we handle all of the scope() keys
         Key progressKey = progressKey(node, homeKey);
-        node.reply(from, replyContext, node.mapReduceLocal(scope(), minEpoch, maxEpoch, instance -> {
-            // note: this diverges from the paper, in that instead of waiting for JoinShard,
-            //       we PreAccept to both old and new topologies and require quorums in both.
-            //       This necessitates sending to ALL replicas of old topology, not only electorate (as fast path may be unreachable).
-            Command command = instance.command(txnId);
-            if (!command.preaccept(txn, homeKey, progressKey))
-                return PreAcceptNack.INSTANCE;
-            return new PreAcceptOk(txnId, command.executeAt(), calculateDeps(instance, txnId, txn, txnId));
-        }, (r1, r2) -> {
+        node.reply(from, replyContext, node.mapReduceLocal(this, minEpoch, maxEpoch, cs -> process(cs, progressKey),
+        (r1, r2) -> {
             if (!r1.isOK()) return r1;
             if (!r2.isOK()) return r2;
             PreAcceptOk ok1 = (PreAcceptOk) r1;
@@ -170,14 +192,14 @@ public class PreAccept extends WithUnsynced
     {
         try (Deps.OrderedBuilder builder = Deps.orderedBuilder(false);)
         {
-            txn.keys.forEach(key -> {
+            txn.keys().forEach(key -> {
                 CommandsForKey forKey = commandStore.maybeCommandsForKey(key);
                 if (forKey == null)
                     return;
 
                 builder.nextKey(key);
-                forKey.uncommitted.headMap(executeAt, false).forEach(conflicts(txnId, txn.isWrite(), builder));
-                forKey.committedByExecuteAt.headMap(executeAt, false).forEach(conflicts(txnId, txn.isWrite(), builder));
+                forKey.uncommitted().before(executeAt).forEach(conflicts(txnId, txn.isWrite(), builder));
+                forKey.committedByExecuteAt().before(executeAt).forEach(conflicts(txnId, txn.isWrite(), builder));
             });
 
             return builder.build();
@@ -194,9 +216,9 @@ public class PreAccept extends WithUnsynced
                '}';
     }
 
-    private static BiConsumer<Timestamp, Command> conflicts(TxnId txnId, boolean isWrite, Deps.OrderedBuilder builder)
+    private static Consumer<PartialCommand> conflicts(TxnId txnId, boolean isWrite, Deps.OrderedBuilder builder)
     {
-        return (ts, command) -> {
+        return command -> {
             if (!txnId.equals(command.txnId()) && (isWrite || command.txn().isWrite()))
                 builder.add(command.txnId());
         };
diff --git a/accord-core/src/main/java/accord/messages/ReadData.java b/accord-core/src/main/java/accord/messages/ReadData.java
index 6a5a951..fe2ac69 100644
--- a/accord-core/src/main/java/accord/messages/ReadData.java
+++ b/accord-core/src/main/java/accord/messages/ReadData.java
@@ -18,8 +18,11 @@
 
 package accord.messages;
 
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Set;
 
+import accord.primitives.Deps;
 import com.google.common.base.Preconditions;
 
 import accord.api.Key;
@@ -32,34 +35,72 @@ import accord.primitives.Timestamp;
 import accord.txn.Txn;
 import accord.primitives.TxnId;
 import accord.utils.DeterministicIdentitySet;
+import com.google.common.collect.Iterables;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class ReadData extends TxnRequest
 {
-    static class LocalRead implements Listener
+    private static final Logger logger = LoggerFactory.getLogger(ReadData.class);
+
+    static class LocalRead implements Listener, PreLoadContext
     {
         final TxnId txnId;
+        final Deps deps;
         final Node node;
         final Node.Id replyToNode;
-        final Keys readKeys;
+        final Keys scope;
+        final Keys txnKeys;
         final ReplyContext replyContext;
 
         Data data;
         boolean isObsolete; // TODO: respond with the Executed result we have stored?
         Set<CommandStore> waitingOn;
 
-        LocalRead(TxnId txnId, Node node, Id replyToNode, Keys readKeys, ReplyContext replyContext)
+        LocalRead(TxnId txnId, Deps deps, Node node, Id replyToNode, Keys scope, Keys txnKeys, ReplyContext replyContext)
         {
-            Preconditions.checkArgument(!readKeys.isEmpty());
+            Preconditions.checkArgument(!scope.isEmpty());
             this.txnId = txnId;
+            this.deps = deps;
             this.node = node;
             this.replyToNode = replyToNode;
-            this.readKeys = readKeys;
+            this.scope = scope;
+            this.txnKeys = txnKeys;  // TODO (now): is this needed? Does the read update commands per key?
             this.replyContext = replyContext;
         }
 
+        @Override
+        public Iterable<TxnId> txnIds()
+        {
+            return Iterables.concat(Collections.singleton(txnId), deps.txnIds());
+        }
+
+        @Override
+        public Iterable<Key> keys()
+        {
+            return txnKeys;
+        }
+
+        @Override
+        public PreLoadContext listenerPreLoadContext(TxnId caller)
+        {
+            Set<TxnId> ids = new HashSet<>(deps.txnIds());
+            ids.add(txnId);
+            ids.add(caller);
+            return PreLoadContext.contextFor(ids, keys());
+        }
+
+        @Override
+        public String toString()
+        {
+            return "ReadData$LocalRead{" + txnId + '}';
+        }
+
         @Override
         public synchronized void onChange(Command command)
         {
+            logger.trace("{}: updating as listener in response to change on {} with status {} ({})",
+                         this, command.txnId(), command.status(), command);
             switch (command.status())
             {
                 default: throw new IllegalStateException();
@@ -82,17 +123,36 @@ public class ReadData extends TxnRequest
                 read(command);
         }
 
-        private void read(Command command)
+        @Override
+        public boolean isTransient()
         {
-            // TODO: threading/futures (don't want to perform expensive reads within this mutually exclusive context)
-            Data next = command.txn().read(command, readKeys);
-            data = data == null ? next : data.merge(next);
+            return true;
+        }
+
+        private synchronized void readComplete(CommandStore commandStore, Data result)
+        {
+            logger.trace("{}: read completed on {}", txnId, commandStore);
+            data = data == null ? result : data.merge(result);
 
-            waitingOn.remove(command.commandStore);
+            waitingOn.remove(commandStore);
             if (waitingOn.isEmpty())
                 node.reply(replyToNode, replyContext, new ReadOk(data));
         }
 
+        private void read(Command command)
+        {
+            logger.trace("{}: executing read", command.txnId());
+            command.read(scope).addCallback((next, throwable) -> {
+                if (throwable != null)
+                {
+                    logger.trace("{}: read failed for {}: {}", txnId, command.commandStore(), throwable);
+                    node.reply(replyToNode, replyContext, new ReadNack());
+                }
+                else
+                    readComplete(command.commandStore(), next);
+            });
+        }
+
         void obsolete()
         {
             if (!isObsolete)
@@ -104,12 +164,13 @@ public class ReadData extends TxnRequest
 
         synchronized void setup(TxnId txnId, Txn txn, Key homeKey, Keys keys, Timestamp executeAt)
         {
-            Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey);
+            Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey);
             waitingOn = node.collectLocal(keys, executeAt, DeterministicIdentitySet::new);
-            // FIXME: fix/check thread safety
-            CommandStore.onEach(waitingOn, instance -> {
+            CommandStores.forEachNonBlocking(waitingOn, this, instance -> {
                 Command command = instance.command(txnId);
                 command.preaccept(txn, homeKey, progressKey); // ensure pre-accepted
+                logger.trace("{}: setting up read with status {} on {}", command.txnId(), command.status(), instance);
+
                 switch (command.status())
                 {
                     default:
@@ -137,26 +198,58 @@ public class ReadData extends TxnRequest
         }
     }
 
+    public static class SerializerSupport
+    {
+        public static ReadData create(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt)
+        {
+            return new ReadData(scope, waitForEpoch, txnId, txn, deps, homeKey, executeAt);
+        }
+    }
+
     public final TxnId txnId;
     public final Txn txn;
-    final Key homeKey;
+    public final Deps deps;
+    public final Key homeKey;
     public final Timestamp executeAt;
 
-    public ReadData(Node.Id to, Topologies topologies, TxnId txnId, Txn txn, Key homeKey, Timestamp executeAt)
+    public ReadData(Node.Id to, Topologies topologies, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt)
+    {
+        super(to, topologies, txn.keys());
+        this.txnId = txnId;
+        this.txn = txn;
+        this.deps = deps;
+        this.homeKey = homeKey;
+        this.executeAt = executeAt;
+    }
+
+    protected ReadData(Keys scope, long waitForEpoch, TxnId txnId, Txn txn, Deps deps, Key homeKey, Timestamp executeAt)
     {
-        super(to, topologies, txn.keys);
+        super(scope, waitForEpoch);
         this.txnId = txnId;
         this.txn = txn;
+        this.deps = deps;
         this.homeKey = homeKey;
         this.executeAt = executeAt;
     }
 
     public void process(Node node, Node.Id from, ReplyContext replyContext)
     {
-        new LocalRead(txnId, node, from, txn.read.keys().intersect(scope()), replyContext)
+        new LocalRead(txnId, deps, node, from, txn.read().keys().intersect(scope()), txn.keys(), replyContext)
             .setup(txnId, txn, homeKey, scope(), executeAt);
     }
 
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Iterables.concat(Collections.singleton(txnId), deps.txnIds());
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return Collections.emptyList();
+    }
+
     @Override
     public MessageType type()
     {
@@ -195,6 +288,7 @@ public class ReadData extends TxnRequest
     public static class ReadOk extends ReadReply
     {
         public final Data data;
+
         public ReadOk(Data data)
         {
             this.data = data;
diff --git a/accord-core/src/main/java/accord/messages/Reply.java b/accord-core/src/main/java/accord/messages/Reply.java
index e82c4f9..d87e196 100644
--- a/accord-core/src/main/java/accord/messages/Reply.java
+++ b/accord-core/src/main/java/accord/messages/Reply.java
@@ -20,5 +20,4 @@ package accord.messages;
 
 public interface Reply extends Message
 {
-    default boolean isFinal() { return true; }
 }
diff --git a/accord-core/src/main/java/accord/messages/TxnRequest.java b/accord-core/src/main/java/accord/messages/TxnRequest.java
index bf98d83..eb05fba 100644
--- a/accord-core/src/main/java/accord/messages/TxnRequest.java
+++ b/accord-core/src/main/java/accord/messages/TxnRequest.java
@@ -25,6 +25,7 @@ import accord.api.Key;
 import accord.local.Node;
 import accord.local.Node.Id;
 import accord.primitives.KeyRanges;
+import accord.local.PreLoadContext;
 import accord.topology.Topologies;
 import accord.topology.Topology;
 import accord.primitives.Keys;
@@ -32,7 +33,7 @@ import accord.primitives.TxnId;
 
 import static java.lang.Long.min;
 
-public abstract class TxnRequest implements EpochRequest
+public abstract class TxnRequest implements EpochRequest, PreLoadContext
 {
     public static abstract class WithUnsynced extends TxnRequest
     {
diff --git a/accord-core/src/main/java/accord/messages/WaitOnCommit.java b/accord-core/src/main/java/accord/messages/WaitOnCommit.java
index f49c03d..47ef220 100644
--- a/accord-core/src/main/java/accord/messages/WaitOnCommit.java
+++ b/accord-core/src/main/java/accord/messages/WaitOnCommit.java
@@ -18,19 +18,33 @@
 
 package accord.messages;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import accord.api.Key;
 import accord.local.*;
 import accord.local.Node.Id;
 import accord.topology.Topologies;
 import accord.primitives.TxnId;
 import accord.primitives.Keys;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static accord.utils.Utils.listOf;
 
 public class WaitOnCommit extends TxnRequest
 {
-    static class LocalWait implements Listener
+    private static final Logger logger = LoggerFactory.getLogger(WaitOnCommit.class);
+
+    public static class SerializerSupport
+    {
+        public static WaitOnCommit create(Keys scope, long waitForEpoch, TxnId txnId)
+        {
+            return new WaitOnCommit(scope, waitForEpoch, txnId);
+        }
+    }
+
+    static class LocalWait implements Listener, PreLoadContext
     {
         final Node node;
         final Id replyToNode;
@@ -47,9 +61,35 @@ public class WaitOnCommit extends TxnRequest
             this.replyContext = replyContext;
         }
 
+        @Override
+        public Iterable<TxnId> txnIds()
+        {
+            return Collections.singleton(txnId);
+        }
+
+        @Override
+        public Iterable<Key> keys()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public PreLoadContext listenerPreLoadContext(TxnId caller)
+        {
+            return PreLoadContext.contextFor(listOf(txnId, caller), keys());
+        }
+
+        @Override
+        public String toString()
+        {
+            return "WaitOnCommit$LocalWait{" + txnId + '}';
+        }
+
         @Override
         public synchronized void onChange(Command command)
         {
+            logger.trace("{}: updating as listener in response to change on {} with status {} ({})",
+                         this, command.txnId(), command.status(), command);
             switch (command.status())
             {
                 default: throw new IllegalStateException();
@@ -70,6 +110,12 @@ public class WaitOnCommit extends TxnRequest
             ack();
         }
 
+        @Override
+        public boolean isTransient()
+        {
+            return true;
+        }
+
         private void ack()
         {
             if (waitingOn.decrementAndGet() == 0)
@@ -86,7 +132,7 @@ public class WaitOnCommit extends TxnRequest
                 case Accepted:
                 case AcceptedInvalidate:
                     command.addListener(this);
-                    instance.progressLog().waiting(txnId, keys);
+                    instance.progressLog().waiting(command, keys);
                     break;
 
                 case Committed:
@@ -102,7 +148,7 @@ public class WaitOnCommit extends TxnRequest
         {
             List<CommandStore> instances = node.collectLocal(keys, txnId, ArrayList::new);
             waitingOn.set(instances.size());
-            instances.forEach(instance -> instance.processBlocking(ignore -> setup(keys, instance)));
+            CommandStore.onEach(this, instances, instance -> setup(keys, instance));
         }
     }
 
@@ -114,6 +160,24 @@ public class WaitOnCommit extends TxnRequest
         this.txnId = txnId;
     }
 
+    protected WaitOnCommit(Keys scope, long waitForEpoch, TxnId txnId)
+    {
+        super(scope, waitForEpoch);
+        this.txnId = txnId;
+    }
+
+    @Override
+    public Iterable<TxnId> txnIds()
+    {
+        return Collections.singleton(txnId);
+    }
+
+    @Override
+    public Iterable<Key> keys()
+    {
+        return scope();
+    }
+
     public void process(Node node, Id replyToNode, ReplyContext replyContext)
     {
         new LocalWait(node, replyToNode, txnId, replyContext).setup(scope());
diff --git a/accord-core/src/main/java/accord/primitives/Deps.java b/accord-core/src/main/java/accord/primitives/Deps.java
index 6941f87..d42ceb5 100644
--- a/accord-core/src/main/java/accord/primitives/Deps.java
+++ b/accord-core/src/main/java/accord/primitives/Deps.java
@@ -32,6 +32,7 @@ import com.google.common.base.Preconditions;
 
 import static accord.utils.ArrayBuffers.*;
 import static accord.utils.SortedArrays.*;
+import static accord.utils.Utils.listOf;
 
 /**
  * A collection of dependencies for a transaction, organised by the key the dependency is adopted via.
@@ -52,6 +53,26 @@ public class Deps implements Iterable<Map.Entry<Key, TxnId>>
         return new Deps(keys, NO_TXNIDS, keysToTxnId);
     }
 
+    public static class SerializerSupport
+    {
+        private SerializerSupport() {}
+
+        public static int keyToTxnIdCount(Deps deps)
+        {
+            return deps.keyToTxnId.length;
+        }
+
+        public static int keyToTxnId(Deps deps, int idx)
+        {
+            return deps.keyToTxnId[idx];
+        }
+
+        public static Deps create(Keys keys, TxnId[] txnIds, int[] keyToTxnId)
+        {
+            return new Deps(keys, txnIds, keyToTxnId);
+        }
+    }
+
     /**
      * Expects Command to be provided in TxnId order
      */
@@ -520,6 +541,30 @@ public class Deps implements Iterable<Map.Entry<Key, TxnId>>
         T construct(K[] keys, int keysLength, V[] values, int valuesLength, int[] out, int outLength);
     }
 
+    private static boolean arraysEqual(int[] left, int[] right, int length)
+    {
+        if (left.length < length || right.length < length)
+            return false;
+
+        for (int i=0; i<length; i++)
+            if (left[i] !=right[i])
+                return false;
+
+        return true;
+    }
+
+    private static <T> boolean arraysEqual(T[] left, T[] right, int length)
+    {
+        if (left.length < length || right.length < length)
+            return false;
+
+        for (int i=0; i<length; i++)
+            if (!Objects.equals(left[i], right[i]))
+                return false;
+
+        return true;
+    }
+
     // TODO: this method supports merging keyToTxnId OR txnIdToKey; we can perhaps save time and effort when constructing
     //       Deps on remote hosts by only producing txnIdToKey with OrderedCollector and serializing only this,
     //       and merging on the recipient before inverting, so that we only have to invert the final assembled deps
@@ -544,9 +589,9 @@ public class Deps implements Iterable<Map.Entry<Key, TxnId>>
             remapLeft = remapToSuperset(leftValues, leftValuesLength, outValues, outTxnIdsLength, intBuffers);
             remapRight = remapToSuperset(rightValues, rightValuesLength, outValues, outTxnIdsLength, intBuffers);
 
-            if (remapLeft == null && remapRight == null && leftKeysLength == rightKeysLength
-                    && Arrays.equals(left, 0, leftLength, right, 0, rightLength)
-                    && Arrays.equals(leftKeys, 0, leftKeysLength, rightKeys, 0, rightKeysLength)
+            if (remapLeft == null && remapRight == null && leftLength == rightLength && leftKeysLength == rightKeysLength
+                    && arraysEqual(left, right, rightLength)
+                    && arraysEqual(leftKeys, rightKeys, rightKeysLength)
                 )
             {
                 return constructor.construct(leftKeys, leftKeysLength, leftValues, leftValuesLength, left, leftLength);
@@ -1024,13 +1069,13 @@ public class Deps implements Iterable<Map.Entry<Key, TxnId>>
 
     public Collection<TxnId> txnIds()
     {
-        return List.of(txnIds);
+        return listOf(txnIds);
     }
 
     @Override
     public Iterator<Map.Entry<Key, TxnId>> iterator()
     {
-        return new Iterator<>()
+        return new Iterator<Map.Entry<Key, TxnId>>()
         {
             int i = keys.size(), k = 0;
 
diff --git a/accord-core/src/main/java/accord/primitives/KeyRanges.java b/accord-core/src/main/java/accord/primitives/KeyRanges.java
index 80244a0..0c80a1c 100644
--- a/accord-core/src/main/java/accord/primitives/KeyRanges.java
+++ b/accord-core/src/main/java/accord/primitives/KeyRanges.java
@@ -27,6 +27,7 @@ import com.google.common.collect.Iterators;
 import java.util.*;
 
 import static accord.utils.SortedArrays.Search.FAST;
+import static accord.utils.Utils.toArray;
 
 public class KeyRanges implements Iterable<KeyRange>
 {
@@ -42,7 +43,7 @@ public class KeyRanges implements Iterable<KeyRange>
 
     public KeyRanges(List<KeyRange> ranges)
     {
-        this(ranges.toArray(KeyRange[]::new));
+        this(toArray(ranges, KeyRange[]::new));
     }
 
     @Override
@@ -205,7 +206,7 @@ public class KeyRanges implements Iterable<KeyRange>
             if (thisRange != null)
                 result.add(thisRange);
         }
-        return new KeyRanges(result.toArray(KeyRange[]::new));
+        return new KeyRanges(toArray(result, KeyRange[]::new));
     }
 
     /**
diff --git a/accord-core/src/main/java/accord/primitives/Keys.java b/accord-core/src/main/java/accord/primitives/Keys.java
index a6d6fcd..38a82f5 100644
--- a/accord-core/src/main/java/accord/primitives/Keys.java
+++ b/accord-core/src/main/java/accord/primitives/Keys.java
@@ -25,7 +25,7 @@ import java.util.stream.Stream;
 
 import accord.api.Key;
 import accord.utils.*;
-import org.apache.cassandra.utils.concurrent.Inline;
+import net.nicoulaj.compilecommand.annotations.Inline;
 
 import static accord.utils.ArrayBuffers.cachedKeys;
 
@@ -38,14 +38,35 @@ public class Keys implements Iterable<Key>
 
     final Key[] keys;
 
+    private static Key[] toKeyArray(SortedSet<? extends Key> keys)
+    {
+        Key[] akeys = new Key[keys.size()];
+        int idx=0;
+        for (Key key : keys)
+            akeys[idx++] = key;
+
+        return akeys;
+    }
+
+    private static Key[] toKeyArray(Collection<? extends Key> keys)
+    {
+        Key[] akeys = new Key[keys.size()];
+        int idx=0;
+        for (Key key : keys)
+            akeys[idx++] = key;
+
+        Arrays.sort(akeys);
+        return akeys;
+    }
+
     public Keys(SortedSet<? extends Key> keys)
     {
-        this.keys = keys.toArray(Key[]::new);
+        this.keys = toKeyArray(keys);
     }
 
     public Keys(Collection<? extends Key> keys)
     {
-        this.keys = keys.toArray(Key[]::new);
+        this.keys = toKeyArray(keys);
         Arrays.sort(this.keys);
     }
 
@@ -153,7 +174,7 @@ public class Keys implements Iterable<Key>
     @Override
     public Iterator<Key> iterator()
     {
-        return new Iterator<>()
+        return new Iterator<Key>()
         {
             int i = 0;
             @Override
@@ -176,6 +197,15 @@ public class Keys implements Iterable<Key>
         return new Keys(keys);
     }
 
+    public static Keys of(List<Key> keys)
+    {
+        Key[] akeys = new Key[keys.size()];
+        for (int i=0; i<akeys.length; i++)
+            akeys[i] = keys.get(i);
+
+        return of(akeys);
+    }
+
     public static Keys ofSorted(Key ... keys)
     {
         for (int i = 1 ; i < keys.length ; ++i)
diff --git a/accord-core/src/main/java/accord/primitives/Timestamp.java b/accord-core/src/main/java/accord/primitives/Timestamp.java
index be0d5de..0a02fab 100644
--- a/accord-core/src/main/java/accord/primitives/Timestamp.java
+++ b/accord-core/src/main/java/accord/primitives/Timestamp.java
@@ -59,6 +59,7 @@ public class Timestamp implements Comparable<Timestamp>
     @Override
     public int compareTo(Timestamp that)
     {
+        if (this == that) return 0;
         int c = Long.compare(this.epoch, that.epoch);
         if (c == 0) c = Long.compare(this.real, that.real);
         if (c == 0) c = Integer.compare(this.logical, that.logical);
diff --git a/accord-core/src/main/java/accord/topology/Topologies.java b/accord-core/src/main/java/accord/topology/Topologies.java
index df22406..95acfcf 100644
--- a/accord-core/src/main/java/accord/topology/Topologies.java
+++ b/accord-core/src/main/java/accord/topology/Topologies.java
@@ -70,7 +70,7 @@ public interface Topologies
         }
     }
 
-    private static boolean equals(Topologies t, Object o)
+    static boolean equals(Topologies t, Object o)
     {
         if (o == t)
             return true;
@@ -90,7 +90,7 @@ public interface Topologies
         return true;
     }
 
-    private static int hashCode(Topologies t)
+    static int hashCode(Topologies t)
     {
         int hashCode = 1;
         for (int i=0, mi=t.size(); i<mi; i++) {
@@ -99,7 +99,7 @@ public interface Topologies
         return hashCode;
     }
 
-    private static String toString(Topologies t)
+    static String toString(Topologies t)
     {
         StringBuilder sb = new StringBuilder("[");
         for (int i=0, mi=t.size(); i<mi; i++)
@@ -154,7 +154,7 @@ public interface Topologies
         public Topology get(int i)
         {
             if (i != 0)
-                throw new IndexOutOfBoundsException(i);
+                throw new IndexOutOfBoundsException(Integer.toString(i));
             return topology;
         }
 
diff --git a/accord-core/src/main/java/accord/topology/Topology.java b/accord-core/src/main/java/accord/topology/Topology.java
index aa67a37..08f5378 100644
--- a/accord-core/src/main/java/accord/topology/Topology.java
+++ b/accord-core/src/main/java/accord/topology/Topology.java
@@ -346,23 +346,6 @@ public class Topology extends AbstractCollection<Shard>
             consumer.accept(i, shards[supersetRangeIndexes[i]]);
     }
 
-    public <T> T[] select(Keys select, T[] indexedByShard, IntFunction<T[]> constructor)
-    {
-        List<T> selection = new ArrayList<>();
-        for (int i = 0, j = 0 ; i < select.size() && j < supersetRangeIndexes.length ;)
-        {
-            Key k = select.get(i);
-            Shard shard = shards[supersetRangeIndexes[j]];
-
-            int c = shard.range.compareKey(k);
-            if (c < 0) ++i;
-            else if (c == 0) { selection.add(indexedByShard[j++]); i++; }
-            else j++;
-        }
-
-        return selection.toArray(constructor);
-    }
-
     @Override
     public Iterator<Shard> iterator()
     {
diff --git a/accord-core/src/main/java/accord/txn/Txn.java b/accord-core/src/main/java/accord/txn/Txn.java
index 7d44429..5c4d3f0 100644
--- a/accord-core/src/main/java/accord/txn/Txn.java
+++ b/accord-core/src/main/java/accord/txn/Txn.java
@@ -18,40 +18,86 @@
 
 package accord.txn;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 import accord.api.*;
 import accord.local.*;
 import accord.primitives.Keys;
 import accord.primitives.Timestamp;
+import accord.utils.ReducingFuture;
+import org.apache.cassandra.utils.concurrent.Future;
 
-public class Txn
-{
-    enum Kind { READ, WRITE }
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
-    final Kind kind;
-    public final Keys keys;
-    public final Read read;
-    public final Query query;
-    public final Update update;
+public abstract class Txn
+{
+    public enum Kind { READ, WRITE }
 
-    public Txn(Keys keys, Read read, Query query)
+    public static class InMemory extends Txn
     {
-        this.kind = Kind.READ;
-        this.keys = keys;
-        this.read = read;
-        this.query = query;
-        this.update = null;
+        private final Kind kind;
+        private final Keys keys;
+        private final Read read;
+        private final Query query;
+        private final Update update;
+
+        public InMemory(@Nonnull Keys keys, @Nonnull Read read, @Nonnull Query query)
+        {
+            this.kind = Kind.READ;
+            this.keys = keys;
+            this.read = read;
+            this.query = query;
+            this.update = null;
+        }
+
+        public InMemory(@Nonnull Keys keys, @Nonnull Read read, @Nonnull Query query, @Nullable Update update)
+        {
+            this.kind = Kind.WRITE;
+            this.keys = keys;
+            this.read = read;
+            this.update = update;
+            this.query = query;
+        }
+
+        @Override
+        public Kind kind()
+        {
+            return kind;
+        }
+
+        @Override
+        public Keys keys()
+        {
+            return keys;
+        }
+
+        @Override
+        public Read read()
+        {
+            return read;
+        }
+
+        @Override
+        public Query query()
+        {
+            return query;
+        }
+
+        @Override
+        public Update update()
+        {
+            return update;
+        }
     }
 
-    public Txn(Keys keys, Read read, Query query, Update update)
-    {
-        this.kind = Kind.WRITE;
-        this.keys = keys;
-        this.read = read;
-        this.update = update;
-        this.query = query;
-    }
+    public abstract @Nonnull Kind kind();
+    public abstract @Nonnull Keys keys();
+    public abstract @Nonnull Read read();
+    public abstract @Nonnull Query query();
+    public abstract @Nullable Update update();
 
     @Override
     public boolean equals(Object o)
@@ -59,52 +105,53 @@ public class Txn
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         Txn txn = (Txn) o;
-        return kind == txn.kind && keys.equals(txn.keys) && read.equals(txn.read) && query.equals(txn.query) && Objects.equals(update, txn.update);
+        return kind() == txn.kind()
+                && keys().equals(txn.keys())
+                && read().equals(txn.read())
+                && query().equals(txn.query())
+                && Objects.equals(update(), txn.update());
     }
 
     @Override
     public int hashCode()
     {
-        return Objects.hash(kind, keys, read, query, update);
+        return Objects.hash(kind(), keys(), read(), query(), update());
     }
 
     public boolean isWrite()
     {
-        return kind == Kind.WRITE;
+        return kind() == Kind.WRITE;
     }
 
     public Result result(Data data)
     {
-        return query.compute(data, read, update);
+        return query().compute(data, read(), update());
     }
 
     public Writes execute(Timestamp executeAt, Data data)
     {
-        if (update == null)
-            return new Writes(executeAt, keys, null);
+        if (update() == null)
+            return new Writes(executeAt, keys(), null);
 
-        return new Writes(executeAt, keys, update.apply(data));
-    }
-
-    public Keys keys()
-    {
-        return keys;
+        return new Writes(executeAt, keys(), update().apply(data));
     }
 
     public String toString()
     {
-        return "{read:" + read.toString() + (update != null ? ", update:" + update : "") + '}';
+        return "{read:" + read().toString() + (update() != null ? ", update:" + update() : "") + '}';
     }
 
-    public Data read(Command command, Keys keys)
+    public Future<Data> read(Command command)
     {
-        return keys.foldl(command.commandStore.ranges().at(command.executeAt().epoch), (index, key, accumulate) -> {
-            CommandStore commandStore = command.commandStore;
+        List<Future<Data>> futures = keys().foldl(command.commandStore().ranges().at(command.executeAt().epoch), (index, key, accumulate) -> {
+            CommandStore commandStore = command.commandStore();
             if (!commandStore.hashIntersects(key))
                 return accumulate;
 
-            Data result = read.read(key, command.executeAt(), commandStore.store());
-            return accumulate != null ? accumulate.merge(result) : result;
-        }, null);
+            Future<Data> result = read().read(key, isWrite(), command.commandStore(), command.executeAt(), commandStore.store());
+            accumulate.add(result);
+            return accumulate;
+        }, new ArrayList<>());
+        return ReducingFuture.reduce(futures, Data::merge);
     }
 }
diff --git a/accord-core/src/main/java/accord/txn/Writes.java b/accord-core/src/main/java/accord/txn/Writes.java
index 527e399..77ad829 100644
--- a/accord-core/src/main/java/accord/txn/Writes.java
+++ b/accord-core/src/main/java/accord/txn/Writes.java
@@ -23,9 +23,18 @@ import accord.local.CommandStore;
 import accord.primitives.Keys;
 import accord.primitives.Timestamp;
 import accord.primitives.KeyRanges;
+import accord.utils.ReducingFuture;
+import com.google.common.base.Preconditions;
+import org.apache.cassandra.utils.concurrent.Future;
+import org.apache.cassandra.utils.concurrent.ImmediateFuture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 
 public class Writes
 {
+    public static final Future<Void> SUCCESS = ImmediateFuture.success(null);
     public final Timestamp executeAt;
     public final Keys keys;
     public final Write write;
@@ -37,20 +46,36 @@ public class Writes
         this.write = write;
     }
 
-    public void apply(CommandStore commandStore)
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Writes writes = (Writes) o;
+        return executeAt.equals(writes.executeAt) && keys.equals(writes.keys) && write.equals(writes.write);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash(executeAt, keys, write);
+    }
+
+    public Future<Void> apply(CommandStore commandStore)
     {
         if (write == null)
-            return;
+            return SUCCESS;
 
         KeyRanges ranges = commandStore.ranges().since(executeAt.epoch);
         if (ranges == null)
-            return;
+            return SUCCESS;
 
-        keys.foldl(ranges, (index, key, accumulate) -> {
+        List<Future<Void>> futures = keys.foldl(ranges, (index, key, accumulate) -> {
             if (commandStore.hashIntersects(key))
-                write.apply(key, executeAt, commandStore.store());
+                accumulate.add(write.apply(key, commandStore, executeAt, commandStore.store()));
             return accumulate;
-        }, null);
+        }, new ArrayList<>());
+        return ReducingFuture.reduce(futures, (l, r) -> null);
     }
 
     @Override
diff --git a/accord-core/src/main/java/accord/utils/DeterministicIdentitySet.java b/accord-core/src/main/java/accord/utils/DeterministicIdentitySet.java
index a5c6ac2..8bafc79 100644
--- a/accord-core/src/main/java/accord/utils/DeterministicIdentitySet.java
+++ b/accord-core/src/main/java/accord/utils/DeterministicIdentitySet.java
@@ -18,10 +18,9 @@
 
 package accord.utils;
 
-import java.util.AbstractSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
+import com.google.common.collect.Iterables;
+
+import java.util.*;
 import java.util.function.Consumer;
 
 public class DeterministicIdentitySet<T> extends AbstractSet<T>
@@ -77,6 +76,27 @@ public class DeterministicIdentitySet<T> extends AbstractSet<T>
         };
     }
 
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o) return true;
+        if (!(o instanceof Iterable))
+            return false;
+
+        return Iterables.elementsEqual(this, (Iterable<?>) o);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = 1;
+
+        for (T element : this)
+            result = 31 * result + (element == null ? 0 : element.hashCode());
+
+        return result;
+    }
+
     @Override
     public int size()
     {
diff --git a/accord-core/src/main/java/accord/utils/ReducingFuture.java b/accord-core/src/main/java/accord/utils/ReducingFuture.java
new file mode 100644
index 0000000..4e02cba
--- /dev/null
+++ b/accord-core/src/main/java/accord/utils/ReducingFuture.java
@@ -0,0 +1,54 @@
+package accord.utils;
+
+import com.google.common.base.Preconditions;
+import org.apache.cassandra.utils.concurrent.AsyncPromise;
+import org.apache.cassandra.utils.concurrent.Future;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.function.BiFunction;
+
+public class ReducingFuture<V> extends AsyncPromise<V>
+{
+    private static final AtomicIntegerFieldUpdater<ReducingFuture> PENDING_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ReducingFuture.class, "pending");
+    private final List<? extends Future<V>> futures;
+    private final BiFunction<V, V, V> reducer;
+    private volatile int pending;
+
+    private ReducingFuture(List<? extends Future<V>> futures, BiFunction<V, V, V> reducer)
+    {
+        this.futures = futures;
+        this.reducer = reducer;
+        this.pending = futures.size();
+        futures.forEach(f -> f.addListener(this::operationComplete));
+    }
+
+    private <F extends io.netty.util.concurrent.Future<?>> void operationComplete(F future) throws Exception
+    {
+        if (isDone())
+            return;
+
+        if (!future.isSuccess())
+        {
+            tryFailure(future.cause());
+        }
+        else if (PENDING_UPDATER.decrementAndGet(this) == 0)
+        {
+            V result = futures.get(0).getNow();
+            for (int i=1, mi=futures.size(); i<mi; i++)
+                result = reducer.apply(result, futures.get(i).getNow());
+
+            trySuccess(result);
+        }
+    }
+
+    public static <T> Future<T> reduce(List<? extends Future<T>> futures, BiFunction<T, T, T> reducer)
+    {
+        Preconditions.checkArgument(!futures.isEmpty(), "future list is empty");
+
+        if (futures.size() == 1)
+            return futures.get(0);
+
+        return new ReducingFuture<>(futures, reducer);
+    }
+}
diff --git a/accord-core/src/main/java/accord/utils/SortedArrays.java b/accord-core/src/main/java/accord/utils/SortedArrays.java
index 0be50fc..a4864a7 100644
--- a/accord-core/src/main/java/accord/utils/SortedArrays.java
+++ b/accord-core/src/main/java/accord/utils/SortedArrays.java
@@ -23,7 +23,7 @@ import java.util.function.IntFunction;
 
 import accord.utils.ArrayBuffers.ObjectBuffers;
 import accord.utils.ArrayBuffers.IntBufferAllocator;
-import org.apache.cassandra.utils.concurrent.Inline;
+import net.nicoulaj.compilecommand.annotations.Inline;
 
 import javax.annotation.Nullable;
 
diff --git a/accord-core/src/test/java/accord/impl/TopologyFactory.java b/accord-core/src/main/java/accord/utils/Utils.java
similarity index 50%
copy from accord-core/src/test/java/accord/impl/TopologyFactory.java
copy to accord-core/src/main/java/accord/utils/Utils.java
index 3deabce..6995c7d 100644
--- a/accord-core/src/test/java/accord/impl/TopologyFactory.java
+++ b/accord-core/src/main/java/accord/utils/Utils.java
@@ -16,41 +16,39 @@
  * limitations under the License.
  */
 
-package accord.impl;
-
-
-import accord.local.Node;
-import accord.api.Key;
-import accord.primitives.KeyRange;
-import accord.primitives.KeyRanges;
-import accord.topology.Topology;
+package accord.utils;
 
 import java.util.*;
+import java.util.function.IntFunction;
 
-public class TopologyFactory
+// TODO: remove when jdk8 support is dropped
+public class Utils
 {
-    public final int rf;
-    // TODO: convert to KeyRanges
-    final KeyRange[] ranges;
-
-    public TopologyFactory(int rf, KeyRange... ranges)
-    {
-        this.rf = rf;
-        this.ranges = ranges;
-    }
-
-    public Topology toTopology(Node.Id[] cluster)
+    // reimplements Collection#toArray
+    public static <T> T[] toArray(Collection<T> src, IntFunction<T[]> factory)
     {
-        return TopologyUtils.initialTopology(cluster, KeyRanges.ofSortedAndDeoverlapped(ranges), rf);
+        T[] dst = factory.apply(src.size());
+        int i = 0;
+        for (T item : src)
+            dst[i++] = item;
+        return dst;
     }
 
-    public Topology toTopology(List<Node.Id> cluster)
+    // reimplements Collection#toArray
+    public static <T> T[] toArray(List<T> src, IntFunction<T[]> factory)
     {
-        return toTopology(cluster.toArray(Node.Id[]::new));
+        T[] dst = factory.apply(src.size());
+        for (int i=0; i<dst.length; i++)
+            dst[i] = src.get(i);
+        return dst;
     }
 
-    public static Topology toTopology(List<Node.Id> cluster, int rf, KeyRange... ranges)
+    // reimplements List#of
+    public static <T> List<T> listOf(T... src)
     {
-        return new TopologyFactory(rf, ranges).toTopology(cluster);
+        List<T> dst = new ArrayList<>(src.length);
+        for (int i=0; i<src.length; i++)
+            dst.add(src[i]);
+        return dst;
     }
 }
diff --git a/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutionFailure.java b/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutionFailure.java
deleted file mode 100644
index a38820f..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutionFailure.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.concurrent;
-
-//import org.apache.cassandra.db.compaction.CompactionInterruptedException;
-//import org.apache.cassandra.utils.Closeable;
-//import org.apache.cassandra.utils.JVMStabilityInspector;
-//import org.apache.cassandra.utils.WithResources;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.Future;
-
-///**
-// * Standardised handling of failures during execution - mostly this involves invoking a thread's
-// * {@link Thread.UncaughtExceptionHandler} or
-// * {@link JVMStabilityInspector#uncaughtException(Thread, Throwable)},
-// * with special handling for {@link CompactionInterruptedException}.
-// * This class also provides wrappers for {@link WithResources} with {@link Runnable} and {@link Callable}.
-// */
-public class ExecutionFailure
-{
-    private static final Logger logger = LoggerFactory.getLogger(ExecutionFailure.class);
-
-//    /**
-//     * Invoke the relevant {@link Thread.UncaughtExceptionHandler},
-//     * ignoring (except for logging) any {@link CompactionInterruptedException}
-//     */
-    public static void handle(Throwable t)
-    {
-        try
-        {
-//            if (t instanceof CompactionInterruptedException)
-//            {
-//                // TODO: should we check to see there aren't nested CompactionInterruptedException?
-//                logger.info(t.getMessage());
-//                if (t.getSuppressed() != null && t.getSuppressed().length > 0)
-//                    logger.warn("Interruption of compaction encountered exceptions:", t);
-//                else
-//                    logger.trace("Full interruption stack trace:", t);
-//            }
-//            else
-//            {
-//                Thread thread = Thread.currentThread();
-//                Thread.UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler();
-//                if (handler == null || handler.equals(thread.getThreadGroup()))
-//                    handler = JVMStabilityInspector::uncaughtException;
-//                handler.uncaughtException(thread, t);
-//            }
-            Thread thread = Thread.currentThread();
-            thread.getUncaughtExceptionHandler().uncaughtException(thread, t);
-        }
-        catch (Throwable shouldNeverHappen)
-        {
-            logger.error("Unexpected error while handling unexpected error", shouldNeverHappen);
-        }
-    }
-
-//    /**
-//     * See {@link #propagating(WithResources, Runnable)}
-//     */
-//    static Runnable propagating(Runnable wrap)
-//    {
-//        return wrap instanceof FutureTask<?> ? wrap : propagating(WithResources.none(), wrap);
-//    }
-//
-//    /**
-//     * In the case of plain executions, we want to handle exceptions without the full {@link FutureTask} machinery
-//     * while still propagating the exception to the encapsulating Future
-//     */
-//    static Runnable propagating(WithResources withResources, Runnable wrap)
-//    {
-//        return enforceOptions(withResources, wrap, true);
-//    }
-//
-//    /**
-//     * See {@link #suppressing(WithResources, Runnable)}
-//     */
-//    static Runnable suppressing(Runnable wrap)
-//    {
-//        return wrap instanceof FutureTask<?> ? wrap : suppressing(WithResources.none(), wrap);
-//    }
-//
-//    /**
-//     * In the case of scheduled periodic tasks, we don't want exceptions propagating to cancel the recurring execution.
-//     */
-//    static Runnable suppressing(WithResources withResources, Runnable wrap)
-//    {
-//        return enforceOptions(withResources, wrap, false);
-//    }
-//
-//    /**
-//     * Encapsulate the execution, propagating or suppressing any exceptions as requested.
-//     *
-//     * note that if {@code wrap} is a {@link Future} its exceptions may not be captured,
-//     * however the codebase should be using our internal {@link Future} variants which handle exceptions in the
-//     * desired way.
-//     */
-//    private static Runnable enforceOptions(WithResources withResources, Runnable wrap, boolean propagate)
-//    {
-//        return new Runnable()
-//        {
-//            @Override
-//            public void run()
-//            {
-//                try (Closeable close = withResources.get())
-//                {
-//                    wrap.run();
-//                }
-//                catch (Throwable t)
-//                {
-//                    handle(t);
-//                    if (propagate)
-//                        throw t;
-//                }
-//            }
-//
-//            @Override
-//            public String toString()
-//            {
-//                return wrap.toString();
-//            }
-//        };
-//    }
-//
-//    /**
-//     * See {@link #enforceOptions(WithResources, Callable)}
-//     */
-//    static <V> Callable<V> propagating(Callable<V> wrap)
-//    {
-//        return enforceOptions(WithResources.none(), wrap);
-//    }
-//
-//    /**
-//     * In the case of non-recurring scheduled tasks, we want to handle exceptions without the full {@link FutureTask}
-//     * machinery, while still propagating the exception to the encapsulating Future
-//     */
-//    static <V> Callable<V> enforceOptions(WithResources withResources, Callable<V> wrap)
-//    {
-//        return new Callable<V>()
-//        {
-//            @Override
-//            public V call() throws Exception
-//            {
-//                try (Closeable close = withResources.get())
-//                {
-//                    return wrap.call();
-//                }
-//                catch (Throwable t)
-//                {
-//                    handle(t);
-//                    throw t;
-//                }
-//            }
-//
-//            @Override
-//            public String toString()
-//            {
-//                return wrap.toString();
-//            }
-//        };
-//    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutorPlus.java b/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutorPlus.java
deleted file mode 100644
index 4ed759c..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/concurrent/ExecutorPlus.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.concurrent;
-
-import org.apache.cassandra.utils.Shared;
-//import org.apache.cassandra.utils.WithResources;
-import org.apache.cassandra.utils.concurrent.Future;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.*;
-
-import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-///**
-// * Cassandra's extension of {@link ExecutorService}, using our own {@link Future}, supporting
-// * {@link #inExecutor()}, and execution with associated resources {@link #execute(WithResources, Runnable)}
-// * (which is primarily used for encapsulating {@link ExecutorLocals} without leaking implementing classes).
-// */
-@Shared(scope = SIMULATION, inner = INTERFACES)
-//public interface ExecutorPlus extends ExecutorService, ResizableThreadPool
-public interface ExecutorPlus extends ExecutorService
-{
-    interface MaximumPoolSizeListener
-    {
-        /**
-         * Listener to follow changes to the maximum pool size
-         */
-        void onUpdateMaximumPoolSize(int maximumPoolSize);
-    }
-
-    /**
-     * MAY execute {@code task} immediately, if the calling thread is permitted to do so.
-     */
-    default void maybeExecuteImmediately(Runnable task)
-    {
-        execute(task);
-    }
-
-    /**
-     * Overrides {@link ExecutorService#submit(Callable)} to return a Cassandra {@link Future}
-     */
-    @Override
-    <T> Future<T> submit(Callable<T> task);
-
-    /**
-     * Overrides {@link ExecutorService#submit(Runnable, Object)} to return a Cassandra {@link Future}
-     */
-    @Override
-    <T> Future<T> submit(Runnable task, T result);
-
-    /**
-     * Overrides {@link ExecutorService#submit(Runnable)} to return a Cassandra {@link Future}
-     */
-    @Override
-    Future<?> submit(Runnable task);
-//
-//    /*
-//     * ==============================================
-//     * WithResources variants of submit and execute.
-//     *
-//     * (We need a way to inject a TraceState directly into the Executor context without going through
-//     * the global Tracing sessions; see CASSANDRA-5668)
-//     * ==============================================
-//     */
-//
-//    /**
-//     * Invoke {@code task}. The invoking thread will first instantiate the resources provided before
-//     * invoking {@code task}, so that thread state may be modified and cleaned up.
-//     *
-//     * The invoking thread will execute something semantically equivlent to:
-//     *
-//     * <code>
-//     *     try (Closeable close = withResources.get())
-//     *     {
-//     *         task.run();
-//     *     }
-//     * </code>
-//     *
-//     * @param withResources the resources to create and hold while executing {@code task}
-//     * @param task the task to execute
-//     */
-//    void execute(WithResources withResources, Runnable task);
-//
-//    /**
-//     * Invoke {@code task}, returning a future representing this computation.
-//     * The invoking thread will first instantiate the resources provided before
-//     * invoking {@code task}, so that thread state may be modified and cleaned up.
-//     *
-//     * The invoking thread will execute something semantically equivlent to:
-//     *
-//     * <code>
-//     *     try (Closeable close = withResources.get())
-//     *     {
-//     *         return task.call();
-//     *     }
-//     * </code>
-//     *
-//     * @param withResources the resources to create and hold while executing {@code task}
-//     * @param task the task to execute
-//     */
-//    <T> Future<T> submit(WithResources withResources, Callable<T> task);
-//
-//    /**
-//     * Invoke {@code task}, returning a future yielding {@code null} if successful,
-//     * or the abnormal termination of {@code task} otherwise.
-//     *
-//     * The invoking thread will first instantiate the resources provided before
-//     * invoking {@code task}, so that thread state may be modified and cleaned up
-//     *
-//     * <code>
-//     *     try (Closeable close = withResources.get())
-//     *     {
-//     *         task.run();
-//     *         return null;
-//     *     }
-//     * </code>
-//     *
-//     * @param withResources the resources to create and hold while executing {@code task}
-//     * @param task the task to execute
-//     */
-//    Future<?> submit(WithResources withResources, Runnable task);
-//
-//    /**
-//     * Invoke {@code task}, returning a future yielding {@code result} if successful,
-//     * or the abnormal termination of {@code task} otherwise.
-//     *
-//     * The invoking thread will first instantiate the resources provided before
-//     * invoking {@code task}, so that thread state may be modified and cleaned up.
-//     *
-//     * The invoking thread will execute something semantically equivlent to:
-//     *
-//     * <code>
-//     *     try (Closeable close = withResources.get())
-//     *     {
-//     *         task.run();
-//     *         return result;
-//     *     }
-//     * </code>
-//     *
-//     * @param withResources the resources to create and hold while executing {@code task}
-//     * @param task the task to execute
-//     * @param result the result if successful
-//     */
-//    <T> Future<T> submit(WithResources withResources, Runnable task, T result);
-
-    /**
-     * @return true iff the caller is a worker thread actively serving this executor
-     */
-    boolean inExecutor();
-
-    default <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException
-    {
-        throw new UnsupportedOperationException();
-    }
-    default <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException
-    {
-        throw new UnsupportedOperationException();
-    }
-    default <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException
-    {
-        throw new UnsupportedOperationException();
-    }
-    default <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
-    {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/concurrent/ImmediateExecutor.java b/accord-core/src/main/java/org/apache/cassandra/concurrent/ImmediateExecutor.java
deleted file mode 100644
index 703dbdf..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/concurrent/ImmediateExecutor.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.concurrent;
-
-//import org.apache.cassandra.utils.Closeable;
-//import org.apache.cassandra.utils.WithResources;
-//import org.apache.cassandra.utils.concurrent.Future;
-//import org.apache.cassandra.utils.concurrent.ImmediateFuture;
-//
-//import java.util.Collections;
-//import java.util.List;
-//import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-//import java.util.concurrent.TimeUnit;
-
-//public class ImmediateExecutor implements LocalAwareExecutorPlus
-public class ImmediateExecutor implements Executor
-{
-    public static final ImmediateExecutor INSTANCE = new ImmediateExecutor();
-
-    private ImmediateExecutor() {}
-
-    @Override
-    public void execute(Runnable command)
-    {
-        command.run();
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/Clock.java b/accord-core/src/main/java/org/apache/cassandra/utils/Clock.java
deleted file mode 100644
index d1c8da6..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/Clock.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.cassandra.utils;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.TimeUnit;
-
-//import static org.apache.cassandra.config.CassandrandraRelevantProperties.CLOCK_GLOBAL;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * Wrapper around time related functions that are either implemented by using the default JVM calls
- * or by using a custom implementation for testing purposes.
- *
- * See {@link Global#instance} for how to use a custom implementation.
- *
- * Please note that {@link java.time.Clock} wasn't used, as it would not be possible to provide an
- * implementation for {@link #nanoTime()} with the exact same properties of {@link System#nanoTime()}.
- */
-@Shared(scope = SIMULATION)
-public interface Clock
-{
-    static final Logger logger = LoggerFactory.getLogger(Clock.class);
-
-    public static class Global
-    {
-        /**
-         * Static singleton object that will be instantiated by default with a system clock
-         * implementation. Set <code>cassandra.clock</code> system property to a FQCN to use a
-         * different implementation instead.
-         */
-        private static final Clock instance;
-
-        static
-        {
-//            String classname = CLOCK_GLOBAL.getString();
-            String classname = System.getProperty("cassandra.clock");
-            Clock clock = new Default();
-            if (classname != null)
-            {
-                try
-                {
-                    logger.debug("Using custom clock implementation: {}", classname);
-                    clock = (Clock) Class.forName(classname).newInstance();
-                }
-                catch (Exception e)
-                {
-                    logger.error("Failed to load clock implementation {}", classname, e);
-                }
-            }
-            instance = clock;
-        }
-
-        /**
-         * Semantically equivalent to {@link System#nanoTime()}
-         */
-        public static long nanoTime()
-        {
-            return instance.nanoTime();
-        }
-
-        /**
-         * Semantically equivalent to {@link System#currentTimeMillis()}
-         */
-        public static long currentTimeMillis()
-        {
-            return instance.currentTimeMillis();
-        }
-    }
-
-    public static class Default implements Clock
-    {
-        /**
-         * {@link System#nanoTime()}
-         */
-        public long nanoTime()
-        {
-            return System.nanoTime(); // checkstyle: permit system clock
-        }
-
-        /**
-         * {@link System#currentTimeMillis()}
-         */
-        public long currentTimeMillis()
-        {
-            return System.currentTimeMillis(); // checkstyle: permit system clock
-        }
-    }
-
-    /**
-     * Semantically equivalent to {@link System#nanoTime()}
-     */
-    public long nanoTime();
-
-    /**
-     * Semantically equivalent to {@link System#currentTimeMillis()}
-     */
-    public long currentTimeMillis();
-
-    @Intercept
-    public static void waitUntil(long deadlineNanos) throws InterruptedException
-    {
-        long waitNanos = Global.nanoTime() - deadlineNanos;
-        if (waitNanos > 0)
-            TimeUnit.NANOSECONDS.sleep(waitNanos);
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/Intercept.java b/accord-core/src/main/java/org/apache/cassandra/utils/Intercept.java
deleted file mode 100644
index b81947b..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/Intercept.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-// a marker to indicate that the method is intercepted by the Simulator
-@Retention(RetentionPolicy.SOURCE)
-@Target({ ElementType.METHOD })
-public @interface Intercept
-{
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/Shared.java b/accord-core/src/main/java/org/apache/cassandra/utils/Shared.java
deleted file mode 100644
index abc8ad2..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/Shared.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-///**
-// * Tells jvm-dtest that a class should be shared accross all {@link ClassLoader}s.
-// *
-// * Jvm-dtest relies on classloader isolation to run multiple cassandra instances in the same JVM, this makes it
-// * so some classes do not get shared (outside a blesssed set of classes/packages). When the default behavior
-// * is not desirable, this annotation will tell jvm-dtest to share the class accross all class loaders.
-// *
-// * This is the oposite of {@link Isolated}.
-// */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.TYPE })
-public @interface Shared
-{
-    enum Scope { ANY, SIMULATION }
-    enum Recursive { NONE, INTERFACES /*(and enums and exceptions) */, ALL }
-    Scope[] scope() default Scope.ANY;
-    Recursive inner() default Recursive.NONE;
-    Recursive ancestors() default Recursive.NONE;
-    Recursive members() default Recursive.NONE;
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java
deleted file mode 100644
index e203d0e..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AbstractFuture.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.ListenableFuture;
-import io.netty.util.concurrent.GenericFutureListener;
-import io.netty.util.internal.ThrowableUtil;
-import org.apache.cassandra.utils.concurrent.ListenerList.*;
-
-import javax.annotation.Nullable;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
-import static org.apache.cassandra.utils.concurrent.ListenerList.*;
-
-/**
- * Our default {@link Future} implementation, with all state being managed without locks (except those used by the JVM).
- *
- * Some implementation comments versus Netty's default promise:
- *  - We permit efficient initial state declaration, avoiding unnecessary CAS or lock acquisitions when mutating
- *    a Promise we are ourselves constructing (and can easily add more; only those we use have been added)
- *  - We guarantee the order of invocation of listeners (and callbacks etc, and with respect to each other)
- *  - We save some space when registering listeners, especially if there is only one listener, as we perform no
- *    extra allocations in this case.
- *  - We implement our invocation list as a concurrent stack, that is cleared on notification
- *  - We handle special values slightly differently.
- *    - We do not use a special value for null, instead using a special value to indicate the result has not been set.
- *      This means that once isSuccess() holds, the result must be a correctly typed object (modulo generics pitfalls).
- *    - All special values are also instances of FailureHolder, which simplifies a number of the logical conditions.
- */
-@SuppressWarnings({ "rawtypes", "unchecked" })
-public abstract class AbstractFuture<V> implements Future<V>
-{
-    protected static final FailureHolder UNSET = new FailureHolder(null);
-    protected static final FailureHolder UNCANCELLABLE = new FailureHolder(null);
-    protected static final FailureHolder CANCELLED = new FailureHolder(ThrowableUtil.unknownStackTrace(new CancellationException(), AbstractFuture.class, "cancel(...)"));
-
-    static class FailureHolder
-    {
-        final Throwable cause;
-        FailureHolder(Throwable cause)
-        {
-            this.cause = cause;
-        }
-    }
-
-    private static Throwable cause(Object result)
-    {
-        return result instanceof FailureHolder ? ((FailureHolder) result).cause : null;
-    }
-    static boolean isSuccess(Object result)
-    {
-        return !(result instanceof FailureHolder);
-    }
-    static boolean isCancelled(Object result)
-    {
-        return result == CANCELLED;
-    }
-    static boolean isDone(Object result)
-    {
-        return result != UNSET && result != UNCANCELLABLE;
-    }
-
-    volatile Object result;
-    volatile ListenerList<V> listeners; // either a ListenerList or GenericFutureListener (or null)
-    static final AtomicReferenceFieldUpdater<AbstractFuture, Object> resultUpdater = newUpdater(AbstractFuture.class, Object.class, "result");
-    static final AtomicReferenceFieldUpdater<AbstractFuture, ListenerList> listenersUpdater = newUpdater(AbstractFuture.class, ListenerList.class, "listeners");
-
-    protected AbstractFuture(FailureHolder initialState)
-    {
-        // TODO: document visibility of constructor (i.e. must be safe published)
-        resultUpdater.lazySet(this, initialState);
-    }
-
-    public AbstractFuture()
-    {
-        this(UNSET);
-    }
-
-    protected AbstractFuture(V immediateSuccess)
-    {
-        resultUpdater.lazySet(this, immediateSuccess);
-    }
-
-    protected AbstractFuture(Throwable immediateFailure)
-    {
-       this(new FailureHolder(immediateFailure));
-    }
-
-    protected AbstractFuture(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        this();
-        listenersUpdater.lazySet(this, new GenericFutureListenerList(listener));
-    }
-
-    protected AbstractFuture(FailureHolder initialState, GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        this(initialState);
-        listenersUpdater.lazySet(this, new GenericFutureListenerList(listener));
-    }
-
-    public Executor notifyExecutor()
-    {
-        return null;
-    }
-
-    protected boolean trySuccess(V v)
-    {
-        return trySet(v);
-    }
-
-    protected boolean tryFailure(Throwable throwable)
-    {
-        return trySet(new FailureHolder(throwable));
-    }
-
-    protected boolean setUncancellable()
-    {
-        if (trySet(UNCANCELLABLE))
-            return true;
-        return isUncancellable();
-    }
-
-    protected boolean setUncancellableExclusive()
-    {
-        return trySet(UNCANCELLABLE);
-    }
-
-    protected boolean isUncancellable()
-    {
-        Object result = this.result;
-        return result == UNCANCELLABLE || (isDone(result) && !isCancelled(result));
-    }
-
-    public boolean cancel(boolean b)
-    {
-        return trySet(CANCELLED);
-    }
-
-    /**
-     * Shared implementation of various promise completion methods.
-     * Updates the result if it is possible to do so, returning success/failure.
-     *
-     * If the promise is UNSET the new value will succeed;
-     *          if it is UNCANCELLABLE it will succeed only if the new value is not CANCELLED
-     *          otherwise it will fail, as isDone() is implied
-     *
-     * If the update succeeds, and the new state implies isDone(), any listeners and waiters will be notified
-     */
-    abstract boolean trySet(Object v);
-
-    @Override
-    public boolean isSuccess()
-    {
-        return isSuccess(result);
-    }
-
-    @Override
-    public boolean isCancelled()
-    {
-        return isCancelled(result);
-    }
-
-    @Override
-    public boolean isDone()
-    {
-        return isDone(result);
-    }
-
-    @Override
-    public boolean isCancellable()
-    {
-        return result == UNSET;
-    }
-
-    @Override
-    public Throwable cause()
-    {
-        return cause(result);
-    }
-
-    /**
-     * if isSuccess(), returns the value, otherwise returns null
-     */
-    @Override
-    public V getNow()
-    {
-        Object result = this.result;
-        if (isSuccess(result))
-            return (V) result;
-        return null;
-    }
-
-    /**
-     * Shared implementation of get() after suitable await(); assumes isDone(), and returns
-     * either the success result or throws the suitable exception under failure
-     */
-    protected V getWhenDone() throws ExecutionException
-    {
-        Object result = this.result;
-        if (isSuccess(result))
-            return (V) result;
-        if (result == CANCELLED)
-            throw new CancellationException();
-        throw new ExecutionException(((FailureHolder) result).cause);
-    }
-
-    @Override
-    public V get() throws InterruptedException, ExecutionException
-    {
-        await();
-        return getWhenDone();
-    }
-
-    @Override
-    public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
-    {
-        if (!await(timeout, unit))
-            throw new TimeoutException();
-        return getWhenDone();
-    }
-
-    /**
-     * Logically append {@code newListener} to {@link #listeners}
-     * (at this stage it is a stack, so we actually prepend)
-     */
-    abstract void appendListener(ListenerList<V> newListener);
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    @Override
-    public AbstractFuture<V> addCallback(FutureCallback<? super V> callback)
-    {
-        appendListener(new CallbackListener<>(this, callback));
-        return this;
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    @Override
-    public AbstractFuture<V> addCallback(BiConsumer<? super V, Throwable> callback)
-    {
-        appendListener(new CallbackBiConsumerListener<>(this, callback, null));
-        return this;
-    }
-
-    @Override
-    public Future<V> addCallback(BiConsumer<? super V, Throwable> callback, Executor executor)
-    {
-        appendListener(new CallbackBiConsumerListener<>(this, callback, executor));
-        return this;
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    @Override
-    public AbstractFuture<V> addCallback(FutureCallback<? super V> callback, Executor executor)
-    {
-        Preconditions.checkNotNull(executor);
-        appendListener(new CallbackListenerWithExecutor<>(this, callback, executor));
-        return this;
-    }
-
-    /**
-     * Support more fluid version of {@link com.google.common.util.concurrent.Futures#addCallback}
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    @Override
-    public AbstractFuture<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure)
-    {
-        appendListener(new CallbackLambdaListener<>(this, onSuccess, onFailure, null));
-        return this;
-    }
-
-    /**
-     * Support more fluid version of {@link com.google.common.util.concurrent.Futures#addCallback}
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    @Override
-    public AbstractFuture<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure, Executor executor)
-    {
-        appendListener(new CallbackLambdaListener<>(this, onSuccess, onFailure, executor));
-        return this;
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} natively
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    protected <T> Future<T> map(AbstractFuture<T> result, Function<? super V, ? extends T> mapper, @Nullable Executor executor)
-    {
-        addListener(() -> {
-            try
-            {
-                if (isSuccess()) result.trySet(mapper.apply(getNow()));
-                else result.tryFailure(cause());
-            }
-            catch (Throwable t)
-            {
-                result.tryFailure(t);
-                throw t;
-            }
-        }, executor);
-        return result;
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    protected <T> Future<T> flatMap(AbstractFuture<T> result, Function<? super V, ? extends Future<T>> flatMapper, @Nullable Executor executor)
-    {
-        addListener(() -> {
-            try
-            {
-                if (isSuccess()) flatMapper.apply(getNow()).addListener(propagate(result));
-                else result.tryFailure(cause());
-            }
-            catch (Throwable t)
-            {
-                result.tryFailure(t);
-                throw t;
-            }
-        }, executor);
-        return result;
-    }
-
-    /**
-     * Add a listener to be invoked once this future completes.
-     * Listeners are submitted to {@link #notifyExecutor} in the order they are added (or the specified executor
-     * in the case of {@link #addListener(Runnable, Executor)}.
-     * if {@link #notifyExecutor} is unset, they are invoked in the order they are added.
-     * The ordering holds across all variants of this method.
-     */
-    public Future<V> addListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        appendListener(new GenericFutureListenerList(listener));
-        return this;
-    }
-
-    /**
-     * Add a listener to be invoked once this future completes.
-     * Listeners are submitted to their {@code #executor} (or {@link #notifyExecutor}) in the order they are added;
-     * if {@link #notifyExecutor} is unset, they are invoked in the order they are added.
-     * The ordering holds across all variants of this method.
-     */
-    public void addListener(Runnable task, @Nullable Executor executor)
-    {
-        appendListener(new RunnableWithExecutor(task, executor));
-    }
-
-    /**
-     * Add a listener to be invoked once this future completes.
-     * Listeners are submitted to {@link #notifyExecutor} in the order they are added (or the specified executor
-     * in the case of {@link #addListener(Runnable, Executor)}.
-     * if {@link #notifyExecutor} is unset, they are invoked in the order they are added.
-     * The ordering holds across all variants of this method.
-     */
-    public void addListener(Runnable task)
-    {
-        appendListener(new RunnableWithNotifyExecutor(task));
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public Future<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... listeners)
-    {
-        // this could be more efficient if we cared, but we do not
-        return addListener(future -> {
-            for (GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener : listeners)
-                notifyListener((GenericFutureListener<io.netty.util.concurrent.Future<? super V>>)listener, future);
-        });
-    }
-
-    @Override
-    public Future<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public Future<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... listeners)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean await(long timeout, TimeUnit unit) throws InterruptedException
-    {
-        return Awaitable.await(this, timeout, unit);
-    }
-
-    @Override
-    public boolean awaitThrowUncheckedOnInterrupt(long time, TimeUnit units) throws UncheckedInterruptedException
-    {
-        return Awaitable.awaitThrowUncheckedOnInterrupt(this, time, units);
-    }
-
-    @Override
-    public boolean awaitUninterruptibly(long timeout, TimeUnit unit)
-    {
-        return Awaitable.awaitUninterruptibly(this, timeout, unit);
-    }
-
-    @Override
-    public boolean awaitUntilThrowUncheckedOnInterrupt(long nanoTimeDeadline) throws UncheckedInterruptedException
-    {
-        return Awaitable.awaitUntilThrowUncheckedOnInterrupt(this, nanoTimeDeadline);
-    }
-
-    @Override
-    public boolean awaitUntilUninterruptibly(long nanoTimeDeadline)
-    {
-        return Awaitable.awaitUntilUninterruptibly(this, nanoTimeDeadline);
-    }
-
-    /**
-     * Wait for this future to complete {@link Awaitable#awaitUninterruptibly()}
-     */
-    @Override
-    public Future<V> awaitUninterruptibly()
-    {
-        return Awaitable.awaitUninterruptibly(this);
-    }
-
-    /**
-     * Wait for this future to complete {@link Awaitable#awaitThrowUncheckedOnInterrupt()}
-     */
-    @Override
-    public Future<V> awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException
-    {
-        return Awaitable.awaitThrowUncheckedOnInterrupt(this);
-    }
-
-    public String toString()
-    {
-        String description = description();
-        String state = state();
-        return description == null ? state : (state + ' ' + description);
-    }
-
-    private String state()
-    {
-        Object result = this.result;
-        if (isSuccess(result))
-            return "(success: " + result + ')';
-        if (result == UNCANCELLABLE)
-            return "(uncancellable)";
-        if (result == CANCELLED)
-            return "(cancelled)";
-        if (isDone(result))
-            return "(failure: " + ((FailureHolder) result).cause + ')';
-        return "(incomplete)";
-    }
-
-    protected String description()
-    {
-        return null;
-    }
-
-    /**
-     * @return a listener that will propagate to {@code to} the result of the future it is invoked with
-     */
-    private static <V> GenericFutureListener<? extends Future<V>> propagate(AbstractFuture<? super V> to)
-    {
-        return from -> {
-            if (from.isSuccess()) to.trySuccess(from.getNow());
-            else to.tryFailure(from.cause());
-        };
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java
deleted file mode 100644
index bace7bd..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncFuture.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.ListenableFuture;
-import io.netty.util.concurrent.GenericFutureListener;
-
-import javax.annotation.Nullable;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.Function;
-
-/**
- * Our default {@link Future} implementation, with all state being managed without locks (except those used by the JVM).
- *
- * Some implementation comments versus Netty's default promise:
- *  - We permit efficient initial state declaration, avoiding unnecessary CAS or lock acquisitions when mutating
- *    a Promise we are ourselves constructing (and can easily add more; only those we use have been added)
- *  - We guarantee the order of invocation of listeners (and callbacks etc, and with respect to each other)
- *  - We save some space when registering listeners, especially if there is only one listener, as we perform no
- *    extra allocations in this case.
- *  - We implement our invocation list as a concurrent stack, that is cleared on notification
- *  - We handle special values slightly differently.
- *    - We do not use a special value for null, instead using a special value to indicate the result has not been set.
- *      This means that once isSuccess() holds, the result must be a correctly typed object (modulo generics pitfalls).
- *    - All special values are also instances of FailureHolder, which simplifies a number of the logical conditions.
- */
-public class AsyncFuture<V> extends AbstractFuture<V>
-{
-    @SuppressWarnings({ "rawtypes" })
-    private static final AtomicReferenceFieldUpdater<AsyncFuture, WaitQueue> waitingUpdater = AtomicReferenceFieldUpdater.newUpdater(AsyncFuture.class, WaitQueue.class, "waiting");
-    @SuppressWarnings({ "unused" })
-    private volatile WaitQueue waiting;
-
-    public AsyncFuture()
-    {
-        super();
-    }
-
-    protected AsyncFuture(V immediateSuccess)
-    {
-        super(immediateSuccess);
-    }
-
-    protected AsyncFuture(Throwable immediateFailure)
-    {
-        super(immediateFailure);
-    }
-
-    protected AsyncFuture(FailureHolder initialState)
-    {
-        super(initialState);
-    }
-
-    protected AsyncFuture(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        super(listener);
-    }
-
-    protected AsyncFuture(FailureHolder initialState, GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        super(initialState, listener);
-    }
-
-    /**
-     * Shared implementation of various promise completion methods.
-     * Updates the result if it is possible to do so, returning success/failure.
-     *
-     * If the promise is UNSET the new value will succeed;
-     *          if it is UNCANCELLABLE it will succeed only if the new value is not CANCELLED
-     *          otherwise it will fail, as isDone() is implied
-     *
-     * If the update succeeds, and the new state implies isDone(), any listeners and waiters will be notified
-     */
-    boolean trySet(Object v)
-    {
-        while (true)
-        {
-            Object current = result;
-            if (isDone(current) || (current == UNCANCELLABLE && (v == CANCELLED || v == UNCANCELLABLE)))
-                return false;
-            if (resultUpdater.compareAndSet(this, current, v))
-            {
-                if (v != UNCANCELLABLE)
-                {
-                    ListenerList.notify(listenersUpdater, this);
-                    AsyncAwaitable.signalAll(waitingUpdater, this);
-                }
-                return true;
-            }
-        }
-    }
-
-    /**
-     * Logically append {@code newListener} to {@link #listeners}
-     * (at this stage it is a stack, so we actually prepend)
-     *
-     * @param newListener must be either a {@link ListenerList} or {@link GenericFutureListener}
-     */
-    void appendListener(ListenerList<V> newListener)
-    {
-        ListenerList.push(listenersUpdater, this, newListener);
-        if (isDone())
-            ListenerList.notify(listenersUpdater, this);
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} natively
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    @Override
-    public <T> Future<T> map(Function<? super V, ? extends T> mapper, Executor executor)
-    {
-        return map(new AsyncFuture<>(), mapper, executor);
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively
-     *
-     * See {@link #addListener(GenericFutureListener)} for ordering semantics.
-     */
-    @Override
-    public <T> Future<T> flatMap(Function<? super V, ? extends Future<T>> flatMapper, @Nullable Executor executor)
-    {
-        return flatMap(new AsyncFuture<>(), flatMapper, executor);
-    }
-
-    /**
-     * Wait for this future to complete {@link Awaitable#await()}
-     */
-    @Override
-    public AsyncFuture<V> await() throws InterruptedException
-    {
-        //noinspection unchecked
-        return AsyncAwaitable.await(waitingUpdater, Future::isDone, this);
-    }
-
-    @Override
-    public boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException
-    {
-        return AsyncAwaitable.awaitUntil(waitingUpdater, Future::isDone, this, nanoTimeDeadline);
-    }
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java
deleted file mode 100644
index b97c3a2..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/AsyncPromise.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import com.google.common.util.concurrent.FutureCallback;
-import io.netty.util.concurrent.Future;
-import io.netty.util.concurrent.GenericFutureListener;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Extends {@link AsyncFuture} to implement the {@link Promise} interface.
- */
-public class AsyncPromise<V> extends AsyncFuture<V> implements Promise<V>
-{
-    public static class WithExecutor<V> extends AsyncPromise<V>
-    {
-        final Executor notifyExecutor;
-        protected WithExecutor(Executor notifyExecutor)
-        {
-            this.notifyExecutor = notifyExecutor;
-        }
-
-        protected WithExecutor(Executor notifyExecutor, FailureHolder initialState)
-        {
-            super(initialState);
-            this.notifyExecutor = notifyExecutor;
-        }
-
-        protected WithExecutor(Executor notifyExecutor, GenericFutureListener<? extends Future<? super V>> listener)
-        {
-            super(listener);
-            this.notifyExecutor = notifyExecutor;
-        }
-
-        @Override
-        public Executor notifyExecutor()
-        {
-            return notifyExecutor;
-        }
-    }
-
-    public AsyncPromise() {}
-
-    AsyncPromise(FailureHolder initialState)
-    {
-        super(initialState);
-    }
-
-    public AsyncPromise(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        super(listener);
-    }
-
-    AsyncPromise(FailureHolder initialState, GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        super(initialState, listener);
-    }
-
-    public static <V> AsyncPromise<V> withExecutor(Executor executor)
-    {
-        return new WithExecutor<>(executor);
-    }
-
-    public static <V> AsyncPromise<V> uncancellable()
-    {
-        return new AsyncPromise<>(UNCANCELLABLE);
-    }
-
-    public static <V> AsyncPromise<V> uncancellable(Executor executor)
-    {
-        return new WithExecutor<>(executor, UNCANCELLABLE);
-    }
-
-    public static <V> AsyncPromise<V> uncancellable(GenericFutureListener<? extends Future<? super V>> listener)
-    {
-        return new AsyncPromise<>(UNCANCELLABLE, listener);
-    }
-
-    /**
-     * Complete the promise successfully if not already complete
-     * @throws IllegalStateException if already set
-     */
-    @Override
-    public Promise<V> setSuccess(V v)
-    {
-        if (!trySuccess(v))
-            throw new IllegalStateException("complete already: " + this);
-        return this;
-    }
-
-    /**
-     * Complete the promise successfully if not already complete
-     * @return true iff completed promise
-     */
-    @Override
-    public boolean trySuccess(V v)
-    {
-        return super.trySuccess(v);
-    }
-
-    /**
-     * Complete the promise abnormally if not already complete
-     * @throws IllegalStateException if already set
-     */
-    @Override
-    public Promise<V> setFailure(Throwable throwable)
-    {
-        if (!tryFailure(throwable))
-            throw new IllegalStateException("complete already: " + this);
-        return this;
-    }
-
-    /**
-     * Complete the promise abnormally if not already complete
-     * @return true iff completed promise
-     */
-    @Override
-    public boolean tryFailure(Throwable throwable)
-    {
-        return super.tryFailure(throwable);
-    }
-
-    /**
-     * Prevent a future caller from cancelling this promise
-     * @return true if the promise is now uncancellable (whether or not we did this)
-     */
-    @Override
-    public boolean setUncancellable()
-    {
-        return super.setUncancellable();
-    }
-
-    /**
-     * Prevent a future caller from cancelling this promise
-     * @return true iff this invocation set it to uncancellable, whether or not now uncancellable
-     */
-    @Override
-    public boolean setUncancellableExclusive()
-    {
-        return super.setUncancellableExclusive();
-    }
-
-    @Override
-    public boolean isUncancellable()
-    {
-        return super.isUncancellable();
-    }
-
-    /**
-     * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
-     * so may cause problems for reporting stack traces
-     */
-    @Override
-    public Promise<V> sync() throws InterruptedException
-    {
-        super.sync();
-        return this;
-    }
-
-    /**
-     * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
-     * so may cause problems for reporting stack traces
-     */
-    @Override
-    public Promise<V> syncUninterruptibly()
-    {
-        super.syncUninterruptibly();
-        return this;
-    }
-
-    @Override
-    public AsyncPromise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener)
-    {
-        super.addListener(listener);
-        return this;
-    }
-
-    @Override
-    public AsyncPromise<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... listeners)
-    {
-        super.addListeners(listeners);
-        return this;
-    }
-
-    @Override
-    public AsyncPromise<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> listener)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public AsyncPromise<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... listeners)
-    {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public AsyncPromise<V> addCallback(FutureCallback<? super V> callback)
-    {
-        super.addCallback(callback);
-        return this;
-    }
-
-    @Override
-    public AsyncPromise<V> addCallback(FutureCallback<? super V> callback, Executor executor)
-    {
-        super.addCallback(callback, executor);
-        return this;
-    }
-
-    @Override
-    public AsyncPromise<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure)
-    {
-        super.addCallback(onSuccess, onFailure);
-        return this;
-    }
-
-    /**
-     * Wait for this promise to complete
-     * @throws InterruptedException if interrupted
-     */
-    @Override
-    public AsyncPromise<V> await() throws InterruptedException
-    {
-        super.await();
-        return this;
-    }
-
-    /**
-     * Wait uninterruptibly for this promise to complete
-     */
-    @Override
-    public AsyncPromise<V> awaitUninterruptibly()
-    {
-        super.awaitUninterruptibly();
-        return this;
-    }
-
-    /**
-     * Wait for this promise to complete, throwing any interrupt as an UncheckedInterruptedException
-     * @throws UncheckedInterruptedException if interrupted
-     */
-    @Override
-    public AsyncPromise<V> awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException
-    {
-        super.awaitThrowUncheckedOnInterrupt();
-        return this;
-    }
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Awaitable.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Awaitable.java
deleted file mode 100644
index f75582d..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Awaitable.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.Predicate;
-
-//import net.nicoulaj.compilecommand.annotations.Inline;
-import org.apache.cassandra.utils.Shared;
-
-import org.apache.cassandra.utils.Intercept;
-
-import static org.apache.cassandra.utils.Clock.Global.nanoTime;
-import static org.apache.cassandra.utils.concurrent.WaitQueue.newWaitQueue;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * A generic signal consumer, supporting all of the typical patterns used in Cassandra.
- * All of the methods defined in {@link Awaitable} may be waited on without a loop,
- * as this interface declares that there are no spurious wake-ups.
- */
-@Shared(scope = SIMULATION)
-public interface Awaitable
-{
-    /**
-     * Await until the deadline (in nanoTime), throwing any interrupt.
-     * No spurious wakeups.
-     * @return true if we were signalled, false if the deadline elapsed
-     * @throws InterruptedException if interrupted
-     */
-    boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException;
-
-    /**
-     * Await until the deadline (in nanoTime), throwing any interrupt as an unchecked exception.
-     * No spurious wakeups.
-     * @return true if we were signalled, false if the deadline elapsed
-     * @throws UncheckedInterruptedException if interrupted
-     */
-    boolean awaitUntilThrowUncheckedOnInterrupt(long nanoTimeDeadline) throws UncheckedInterruptedException;
-
-    /**
-     * Await until the deadline (in nanoTime), ignoring interrupts (but maintaining the interrupt flag on exit).
-     * No spurious wakeups.
-     * @return true if we were signalled, false if the deadline elapsed
-     */
-    boolean awaitUntilUninterruptibly(long nanoTimeDeadline);
-
-    /**
-     * Await for the specified period, throwing any interrupt.
-     * No spurious wakeups.
-     * @return true if we were signalled, false if the timeout elapses
-     * @throws InterruptedException if interrupted
-     */
-    boolean await(long time, TimeUnit units) throws InterruptedException;
-
-    /**
-     * Await for the specified period, throwing any interrupt as an unchecked exception.
-     * No spurious wakeups.
-     * @return true if we were signalled, false if the timeout elapses
-     * @throws UncheckedInterruptedException if interrupted
-     */
-    boolean awaitThrowUncheckedOnInterrupt(long time, TimeUnit units) throws UncheckedInterruptedException;
-
-    /**
-     * Await until the deadline (in nanoTime), ignoring interrupts (but maintaining the interrupt flag on exit).
-     * No spurious wakeups.
-     * @return true if we were signalled, false if the timeout elapses
-     */
-    boolean awaitUninterruptibly(long time, TimeUnit units);
-
-    /**
-     * Await indefinitely, throwing any interrupt.
-     * No spurious wakeups.
-     * @throws InterruptedException if interrupted
-     */
-    Awaitable await() throws InterruptedException;
-
-    /**
-     * Await indefinitely, throwing any interrupt as an unchecked exception.
-     * No spurious wakeups.
-     * @throws UncheckedInterruptedException if interrupted
-     */
-    Awaitable awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException;
-
-    /**
-     * Await indefinitely, ignoring interrupts (but maintaining the interrupt flag on exit).
-     * No spurious wakeups.
-     */
-    Awaitable awaitUninterruptibly();
-
-    public static boolean await(Awaitable await, long time, TimeUnit unit) throws InterruptedException
-    {
-        return await.awaitUntil(nanoTime() + unit.toNanos(time));
-    }
-
-    public static boolean awaitThrowUncheckedOnInterrupt(Awaitable await, long time, TimeUnit units) throws UncheckedInterruptedException
-    {
-        return awaitUntilThrowUncheckedOnInterrupt(await, nanoTime() + units.toNanos(time));
-    }
-
-    public static boolean awaitUninterruptibly(Awaitable await, long time, TimeUnit units)
-    {
-        return awaitUntilUninterruptibly(await, nanoTime() + units.toNanos(time));
-    }
-
-    public static <A extends Awaitable> A awaitThrowUncheckedOnInterrupt(A await) throws UncheckedInterruptedException
-    {
-        try
-        {
-            await.await();
-        }
-        catch (InterruptedException e)
-        {
-            throw new UncheckedInterruptedException();
-        }
-        return await;
-    }
-
-    public static boolean awaitUntilThrowUncheckedOnInterrupt(Awaitable await, long nanoTimeDeadline) throws UncheckedInterruptedException
-    {
-        try
-        {
-            return await.awaitUntil(nanoTimeDeadline);
-        }
-        catch (InterruptedException e)
-        {
-            throw new UncheckedInterruptedException();
-        }
-    }
-
-    /**
-     * {@link Awaitable#awaitUntilUninterruptibly(long)}
-     */
-    public static boolean awaitUntilUninterruptibly(Awaitable await, long nanoTimeDeadline)
-    {
-        boolean interrupted = false;
-        boolean result;
-        while (true)
-        {
-            try
-            {
-                result = await.awaitUntil(nanoTimeDeadline);
-                break;
-            }
-            catch (InterruptedException e)
-            {
-                interrupted = true;
-            }
-        }
-        if (interrupted)
-            Thread.currentThread().interrupt();
-        return result;
-    }
-
-    /**
-     * {@link Awaitable#awaitUninterruptibly()}
-     */
-    public static <A extends Awaitable> A awaitUninterruptibly(A await)
-    {
-        boolean interrupted = false;
-        while (true)
-        {
-            try
-            {
-                await.await();
-                break;
-            }
-            catch (InterruptedException e)
-            {
-                interrupted = true;
-            }
-        }
-        if (interrupted)
-            Thread.currentThread().interrupt();
-        return await;
-    }
-
-    abstract class AbstractAwaitable implements Awaitable
-    {
-        protected AbstractAwaitable() {}
-
-        /**
-         * {@link Awaitable#await(long, TimeUnit)}
-         */
-        @Override
-        public boolean await(long time, TimeUnit unit) throws InterruptedException
-        {
-            return Awaitable.await(this, time, unit);
-        }
-
-        /**
-         * {@link Awaitable#awaitThrowUncheckedOnInterrupt(long, TimeUnit)}
-         */
-        @Override
-        public boolean awaitThrowUncheckedOnInterrupt(long time, TimeUnit units) throws UncheckedInterruptedException
-        {
-            return Awaitable.awaitThrowUncheckedOnInterrupt(this, time, units);
-        }
-
-        /**
-         * {@link Awaitable#awaitUninterruptibly(long, TimeUnit)}
-         */
-        public boolean awaitUninterruptibly(long time, TimeUnit units)
-        {
-            return awaitUntilUninterruptibly(nanoTime() + units.toNanos(time));
-        }
-
-        /**
-         * {@link Awaitable#awaitThrowUncheckedOnInterrupt()}
-         */
-        public Awaitable awaitThrowUncheckedOnInterrupt() throws UncheckedInterruptedException
-        {
-            return Awaitable.awaitThrowUncheckedOnInterrupt(this);
-        }
-
-        /**
-         * {@link Awaitable#awaitUntilThrowUncheckedOnInterrupt(long)}
-         */
-        public boolean awaitUntilThrowUncheckedOnInterrupt(long nanoTimeDeadline) throws UncheckedInterruptedException
-        {
-            return Awaitable.awaitUntilThrowUncheckedOnInterrupt(this, nanoTimeDeadline);
-        }
-
-        /**
-         * {@link Awaitable#awaitUntilUninterruptibly(long)}
-         */
-        public boolean awaitUntilUninterruptibly(long nanoTimeDeadline)
-        {
-            return Awaitable.awaitUntilUninterruptibly(this, nanoTimeDeadline);
-        }
-
-        /**
-         * {@link Awaitable#awaitUninterruptibly()}
-         */
-        public Awaitable awaitUninterruptibly()
-        {
-            return Awaitable.awaitUninterruptibly(this);
-        }
-    }
-
-    /**
-     * A barebones asynchronous {@link Awaitable}.
-     * If your state is minimal, or can be updated concurrently, extend this class.
-     */
-    abstract class AsyncAwaitable extends AbstractAwaitable
-    {
-        /**
-         * Maintain an internal variable containing a lazily-initialized wait queue
-         * @return null if is done
-         */
-        @Inline
-        private static <A extends Awaitable> WaitQueue.Signal register(AtomicReferenceFieldUpdater<A, WaitQueue> waitingUpdater, Predicate<A> isDone, A awaitable)
-        {
-            if (isDone.test(awaitable))
-                return null;
-
-            WaitQueue waiting = waitingUpdater.get(awaitable);
-            if (waiting == null)
-            {
-                if (!waitingUpdater.compareAndSet(awaitable, null, waiting = newWaitQueue()))
-                {
-                    waiting = waitingUpdater.get(awaitable);
-                    if (waiting == null)
-                    {
-                        assert isDone.test(awaitable);
-                        return null;
-                    }
-                }
-            }
-
-            WaitQueue.Signal s = waiting.register();
-            if (!isDone.test(awaitable))
-                return s;
-
-            s.cancel();
-            return null;
-        }
-
-        @Inline
-        static <A extends Awaitable> A await(AtomicReferenceFieldUpdater<A, WaitQueue> waitingUpdater, Predicate<A> isDone, A awaitable) throws InterruptedException
-        {
-            WaitQueue.Signal s = register(waitingUpdater, isDone, awaitable);
-            if (s != null)
-                s.await();
-            return awaitable;
-        }
-
-        @Inline
-        static <A extends Awaitable> boolean awaitUntil(AtomicReferenceFieldUpdater<A, WaitQueue> waitingUpdater, Predicate<A> isDone, A awaitable, long nanoTimeDeadline) throws InterruptedException
-        {
-            WaitQueue.Signal s = register(waitingUpdater, isDone, awaitable);
-            return s == null || s.awaitUntil(nanoTimeDeadline) || isDone.test(awaitable);
-        }
-
-        @Inline
-        static <A extends Awaitable> void signalAll(AtomicReferenceFieldUpdater<A, WaitQueue> waitingUpdater, A awaitable)
-        {
-            WaitQueue waiting = waitingUpdater.get(awaitable);
-            if (waiting == null)
-                return;
-
-            waiting.signalAll();
-            waitingUpdater.lazySet(awaitable, null);
-        }
-
-        private static final AtomicReferenceFieldUpdater<AsyncAwaitable, WaitQueue> waitingUpdater = AtomicReferenceFieldUpdater.newUpdater(AsyncAwaitable.class, WaitQueue.class, "waiting");
-        private volatile WaitQueue waiting;
-
-        protected AsyncAwaitable() {}
-
-        /**
-         * {@link Awaitable#await()}
-         */
-        public Awaitable await() throws InterruptedException
-        {
-            return await(waitingUpdater, AsyncAwaitable::isSignalled, this);
-        }
-
-        /**
-         * {@link Awaitable#awaitUntil(long)}
-         */
-        public boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException
-        {
-            return awaitUntil(waitingUpdater, AsyncAwaitable::isSignalled, this, nanoTimeDeadline);
-        }
-
-        /**
-         * Signal any waiting threads; {@link #isSignalled()} must return {@code true} before this method is invoked.
-         */
-        protected void signal()
-        {
-            signalAll(waitingUpdater, this);
-        }
-
-        /**
-         * Return true once signalled. Unidirectional; once true, must never again be false.
-         */
-        protected abstract boolean isSignalled();
-    }
-
-    /**
-     * A barebones {@link Awaitable} that uses mutual exclusion.
-     * If your state will be updated while holding the object monitor, extend this class.
-     */
-    abstract class SyncAwaitable extends AbstractAwaitable
-    {
-        protected SyncAwaitable() {}
-
-        /**
-         * {@link Awaitable#await()}
-         */
-        public synchronized Awaitable await() throws InterruptedException
-        {
-            while (!isSignalled())
-                wait();
-            return this;
-        }
-
-        /**
-         * {@link Awaitable#awaitUntil(long)}
-         */
-        public synchronized boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException
-        {
-            while (true)
-            {
-                if (isSignalled()) return true;
-                if (!waitUntil(this, nanoTimeDeadline)) return false;
-            }
-        }
-
-        /**
-         * Return true once signalled. Unidirectional; once true, must never again be false.
-         */
-        protected abstract boolean isSignalled();
-
-        @Intercept
-        public static boolean waitUntil(Object monitor, long deadlineNanos) throws InterruptedException
-        {
-            long wait = deadlineNanos - nanoTime();
-            if (wait <= 0)
-                return false;
-
-            monitor.wait((wait + 999999) / 1000000);
-            return true;
-        }
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Condition.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Condition.java
deleted file mode 100644
index eb97848..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Condition.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import org.apache.cassandra.utils.Intercept;
-import org.apache.cassandra.utils.Shared;
-
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * Simpler API than java.util.concurrent.Condition; would be nice to extend it, but also nice
- * to share API with Future, for which Netty's API is incompatible with java.util.concurrent.Condition
- *
- * {@link Awaitable} for explicit external signals.
- */
-@Shared(scope = SIMULATION)
-public interface Condition extends Awaitable
-{
-    /**
-     * Returns true once signalled. Unidirectional; once true, will never again be false.
-     */
-    boolean isSignalled();
-
-    /**
-     * Signal the condition as met, and wake all waiting threads.
-     */
-    void signal();
-
-    /**
-     * Signal the condition as met, and wake all waiting threads.
-     */
-    default void signalAll() { signal(); }
-
-    /**
-     * Factory method used to capture and redirect instantiations for simulation
-     */
-    @Intercept
-    static Condition newOneTimeCondition()
-    {
-        return new Async();
-    }
-
-    /**
-     * An asynchronous {@link Condition}. Typically lower overhead than {@link Sync}.
-     */
-    public static class Async extends AsyncAwaitable implements Condition
-    {
-        private volatile boolean signaled = false;
-
-        // WARNING: if extending this class, consider simulator interactions
-        protected Async() {}
-
-        public boolean isSignalled()
-        {
-            return signaled;
-        }
-
-        public void signal()
-        {
-            signaled = true;
-            super.signal();
-        }
-    }
-
-    /**
-     * A {@link Condition} based on its object monitor.
-     * WARNING: lengthy operations performed while holding the lock may prevent timely notification of waiting threads
-     * that a deadline has passed.
-     */
-    public static class Sync extends SyncAwaitable implements Condition
-    {
-        private boolean signaled = false;
-
-        // this can be instantiated directly, as we intercept monitors directly with byte weaving
-        public Sync() {}
-
-        public synchronized boolean isSignalled()
-        {
-            return signaled;
-        }
-
-        public synchronized void signal()
-        {
-            signaled = true;
-            notifyAll();
-        }
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Future.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Future.java
deleted file mode 100644
index 69dc83d..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Future.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import java.util.concurrent.Executor;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import com.google.common.util.concurrent.AsyncFunction;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.ListenableFuture; // checkstyle: permit this import
-
-import io.netty.util.concurrent.GenericFutureListener;
-
-import io.netty.util.internal.PlatformDependent;
-import org.apache.cassandra.utils.Shared;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * A Future that integrates several different (but equivalent) APIs used within Cassandra into a single concept,
- * integrating also with our {@link Awaitable} abstraction, to overall improve coherency and clarity in the codebase.
- */
-@Shared(scope = SIMULATION, ancestors = INTERFACES)
-public interface Future<V> extends io.netty.util.concurrent.Future<V>, ListenableFuture<V>, Awaitable
-{
-    /**
-     * Wait indefinitely for this future to complete, throwing any interrupt
-     * @throws InterruptedException if interrupted
-     */
-    @Override
-    Future<V> await() throws InterruptedException;
-
-    /**
-     * Wait indefinitely for this future to complete
-     */
-    @Override
-    Future<V> awaitUninterruptibly();
-
-    /**
-     * Wait indefinitely for this promise to complete, throwing any interrupt as an UncheckedInterruptedException
-     * @throws UncheckedInterruptedException if interrupted
-     */
-    @Override
-    Future<V> awaitThrowUncheckedOnInterrupt();
-
-    default void rethrowIfFailed()
-    {
-        Throwable cause = this.cause();
-        if (cause != null)
-        {
-            PlatformDependent.throwException(cause);
-        }
-    }
-
-    /**
-     * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
-     * so may cause problems for reporting stack traces
-     */
-    @Override
-    default Future<V> sync() throws InterruptedException
-    {
-        await();
-        rethrowIfFailed();
-        return this;
-    }
-
-    /**
-     * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
-     * so may cause problems for reporting stack traces
-     */
-    @Override
-    default Future<V> syncUninterruptibly()
-    {
-        awaitUninterruptibly();
-        rethrowIfFailed();
-        return this;
-    }
-
-    @Deprecated
-    @Override
-    default boolean await(long l) throws InterruptedException
-    {
-        return await(l, MILLISECONDS);
-    }
-
-    @Deprecated
-    @Override
-    default boolean awaitUninterruptibly(long l)
-    {
-        return awaitUninterruptibly(l, MILLISECONDS);
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     */
-    Future<V> addCallback(BiConsumer<? super V, Throwable> callback);
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     */
-    Future<V> addCallback(BiConsumer<? super V, Throwable> callback, Executor executor);
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     */
-    Future<V> addCallback(FutureCallback<? super V> callback);
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     */
-    Future<V> addCallback(FutureCallback<? super V> callback, Executor executor);
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     */
-    Future<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure);
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#addCallback} natively
-     */
-    Future<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure, Executor executor);
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} natively
-     */
-    default <T> Future<T> map(Function<? super V, ? extends T> mapper)
-    {
-        return map(mapper, null);
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} natively
-     */
-    <T> Future<T> map(Function<? super V, ? extends T> mapper, Executor executor);
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively
-     */
-    default <T> Future<T> flatMap(Function<? super V, ? extends Future<T>> flatMapper)
-    {
-        return flatMap(flatMapper, null);
-    }
-
-    /**
-     * Support {@link com.google.common.util.concurrent.Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} natively
-     */
-    <T> Future<T> flatMap(Function<? super V, ? extends Future<T>> flatMapper, Executor executor);
-
-    /**
-     * Invoke {@code runnable} on completion, using {@code executor}.
-     *
-     * Tasks are submitted to their executors in the order they were added to this Future.
-     */
-    @Override
-    void addListener(Runnable runnable, Executor executor);
-
-    /**
-     * Invoke {@code runnable} on completion. Depending on the implementation and its configuration, this
-     * may be executed immediately by the notifying/completing thread, or asynchronously by an executor.
-     * Tasks are executed, or submitted to the executor, in the order they were added to this Future.
-     */
-    void addListener(Runnable runnable);
-
-    Executor notifyExecutor();
-
-    @Override Future<V> addListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> genericFutureListener);
-    @Override Future<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... genericFutureListeners);
-    @Override Future<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> genericFutureListener);
-    @Override Future<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... genericFutureListeners);
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java
deleted file mode 100644
index 159d580..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ImmediateFuture.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-public class ImmediateFuture<V> extends AsyncFuture<V>
-{
-    private ImmediateFuture(V value)
-    {
-        super(value);
-    }
-
-    private ImmediateFuture(Throwable cause)
-    {
-        super(cause);
-    }
-
-    private ImmediateFuture(FailureHolder failure)
-    {
-        super(failure);
-    }
-
-    public static <V> ImmediateFuture<V> success(V value)
-    {
-        return new ImmediateFuture<>(value);
-    }
-
-    public static <V> ImmediateFuture<V> failure(Throwable cause)
-    {
-        return new ImmediateFuture<>(cause);
-    }
-
-    public static <V> ImmediateFuture<V> cancelled()
-    {
-        return new ImmediateFuture<>(CANCELLED);
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Inline.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Inline.java
deleted file mode 100644
index 1602bb7..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Inline.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-public @interface Inline
-{
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java
deleted file mode 100644
index 7ff7387..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/IntrusiveStack.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-//import net.nicoulaj.compilecommand.annotations.Inline;
-
-import java.util.Iterator;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-
-/**
- * An efficient stack/list that is expected to be ordinarily either empty or close to, and for which
- * we need concurrent insertions and do not need to support removal - i.e. the list is semi immutable.
- *
- * This is an intrusive stack, and for simplicity we treat all
- *
- * @param <T>
- */
-public class IntrusiveStack<T extends IntrusiveStack<T>> implements Iterable<T>
-{
-    static class Itr<T extends IntrusiveStack<T>> implements Iterator<T>
-    {
-        private T next;
-
-        Itr(T next)
-        {
-            this.next = next;
-        }
-
-        @Override
-        public boolean hasNext()
-        {
-            return next != null;
-        }
-
-        @Override
-        public T next()
-        {
-            T result = next;
-            next = result.next;
-            return result;
-        }
-    }
-
-    T next;
-
-    @Inline
-    protected static <O, T extends IntrusiveStack<T>> void push(AtomicReferenceFieldUpdater<? super O, T> headUpdater, O owner, T prepend)
-    {
-        push(headUpdater, owner, prepend, (prev, next) -> {
-            next.next = prev;
-            return next;
-        });
-    }
-
-    protected static <O, T extends IntrusiveStack<T>> void push(AtomicReferenceFieldUpdater<O, T> headUpdater, O owner, T prepend, BiFunction<T, T, T> combine)
-    {
-        while (true)
-        {
-            T head = headUpdater.get(owner);
-            if (headUpdater.compareAndSet(owner, head, combine.apply(head, prepend)))
-                return;
-        }
-    }
-
-    protected static <O, T extends IntrusiveStack<T>> void pushExclusive(AtomicReferenceFieldUpdater<O, T> headUpdater, O owner, T prepend, BiFunction<T, T, T> combine)
-    {
-        T head = headUpdater.get(owner);
-        headUpdater.lazySet(owner, combine.apply(head, prepend));
-    }
-
-    protected static <T extends IntrusiveStack<T>, O> Iterable<T> iterable(AtomicReferenceFieldUpdater<O, T> headUpdater, O owner)
-    {
-        return iterable(headUpdater.get(owner));
-    }
-
-    protected static <T extends IntrusiveStack<T>> Iterable<T> iterable(T list)
-    {
-        return list == null ? () -> iterator(null) : list;
-    }
-
-    protected static <T extends IntrusiveStack<T>> Iterator<T> iterator(T list)
-    {
-        return new Itr<>(list);
-    }
-
-    protected static int size(IntrusiveStack<?> list)
-    {
-        int size = 0;
-        while (list != null)
-        {
-            ++size;
-            list = list.next;
-        }
-        return size;
-    }
-
-    // requires exclusive ownership (incl. with readers)
-    protected T reverse()
-    {
-        return reverse((T) this);
-    }
-
-    // requires exclusive ownership (incl. with readers)
-    protected static <T extends IntrusiveStack<T>> T reverse(T list)
-    {
-        T prev = null;
-        T cur = list;
-        while (cur != null)
-        {
-            T next = cur.next;
-            cur.next = prev;
-            prev = cur;
-            cur = next;
-        }
-        return prev;
-    }
-
-    @Override
-    public void forEach(Consumer<? super T> forEach)
-    {
-        forEach((T)this, forEach);
-    }
-
-    protected static <T extends IntrusiveStack<T>> void forEach(T list, Consumer<? super T> forEach)
-    {
-        while (list != null)
-        {
-            forEach.accept(list);
-            list = list.next;
-        }
-    }
-
-    @Override
-    public Iterator<T> iterator()
-    {
-        return new Itr<>((T) this);
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ListenerList.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ListenerList.java
deleted file mode 100644
index c82b80c..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/ListenerList.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import com.google.common.util.concurrent.FutureCallback;
-import io.netty.util.concurrent.EventExecutor;
-import io.netty.util.concurrent.GenericFutureListener;
-//import net.nicoulaj.compilecommand.annotations.Inline;
-import org.apache.cassandra.concurrent.ExecutionFailure;
-import org.apache.cassandra.concurrent.ExecutorPlus;
-import org.apache.cassandra.concurrent.ImmediateExecutor;
-
-import javax.annotation.Nullable;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
-import static org.apache.cassandra.utils.concurrent.ListenerList.Notifying.NOTIFYING;
-
-/**
- * Encapsulate one or more items in a linked-list that is immutable whilst shared, forming a prepend-only list (or stack).
- * Once the list is ready to consume, exclusive ownership is taken by clearing the shared variable containing it, after
- * which the list may be invoked using {@link #notify}, which reverses the list before invoking the work it contains.
- */
-abstract class ListenerList<V> extends IntrusiveStack<ListenerList<V>>
-{
-    abstract void notifySelf(Executor notifyExecutor, Future<V> future);
-
-    static ListenerList pushHead(ListenerList prev, ListenerList next)
-    {
-        if (prev instanceof Notifying<?>)
-        {
-            Notifying result = new Notifying();
-            result.next = next;
-            next.next = prev == NOTIFYING ? null : prev;
-            return result;
-        }
-        next.next = prev;
-        return next;
-    }
-
-    /**
-     * Logically append {@code newListener} to {@link #listeners}
-     * (at this stage it is a stack, so we actually prepend)
-     *
-     * @param newListener must be either a {@link ListenerList} or {@link GenericFutureListener}
-     */
-    @Inline
-    static <T> void push(AtomicReferenceFieldUpdater<? super T, ListenerList> updater, T in, ListenerList newListener)
-    {
-        IntrusiveStack.push(updater, in, newListener, ListenerList::pushHead);
-    }
-
-    /**
-     * Logically append {@code newListener} to {@link #listeners}
-     * (at this stage it is a stack, so we actually prepend)
-     *
-     * @param newListener must be either a {@link ListenerList} or {@link GenericFutureListener}
-     */
-    @Inline
-    static <T> void pushExclusive(AtomicReferenceFieldUpdater<? super T, ListenerList> updater, T in, ListenerList newListener)
-    {
-        IntrusiveStack.pushExclusive(updater, in, newListener, ListenerList::pushHead);
-    }
-
-    static <V, T extends Future<V>> void notify(AtomicReferenceFieldUpdater<? super T, ListenerList> updater, T in)
-    {
-        while (true)
-        {
-            ListenerList<V> listeners = updater.get(in);
-            if (listeners == null || listeners instanceof Notifying)
-                return; // either no listeners, or we are already notifying listeners, so we'll get to the new one when ready
-
-            if (updater.compareAndSet(in, listeners, NOTIFYING))
-            {
-                while (true)
-                {
-                    notify(listeners, in);
-                    if (updater.compareAndSet(in, NOTIFYING, null))
-                        return;
-
-                    listeners = updater.getAndSet(in, NOTIFYING);
-                }
-            }
-        }
-    }
-
-    /**
-     * Requires exclusive ownership of {@code head}.
-     *
-     * Task submission occurs in the order the operations were submitted; if all of the executors
-     * are immediate or unspecified this guarantees execution order.
-     * Tasks are submitted to the executor individually, as this simplifies semantics and
-     * we anticipate few listeners in practice, and even fewer with indirect executors.
-     *
-     * @param head must be either a {@link ListenerList} or {@link GenericFutureListener}
-     */
-    static <T> void notify(ListenerList<T> head, Future<T> future)
-    {
-        Executor notifyExecutor = future.notifyExecutor();
-        if (inExecutor(notifyExecutor))
-            notifyExecutor = null;
-
-        notify(head, notifyExecutor, future);
-    }
-
-    private static <T> void notify(ListenerList<T> head, Executor notifyExecutor, Future<T> future)
-    {
-        head = reverse(head);
-        forEach(head, i -> i.notifySelf(notifyExecutor, future));
-    }
-
-    /**
-     * Notify {@code listener} on the invoking thread, handling any exceptions
-     */
-    static <F extends io.netty.util.concurrent.Future<?>> void notifyListener(GenericFutureListener<F> listener, F future)
-    {
-        try
-        {
-            listener.operationComplete(future);
-        }
-        catch (Throwable t)
-        {
-            // TODO: suboptimal package interdependency - move FutureTask etc here?
-            ExecutionFailure.handle(t);
-        }
-    }
-
-    /**
-     * Notify {@code listener} using {@code notifyExecutor} if set, and handling exceptions otherwise
-     */
-    static <F extends io.netty.util.concurrent.Future<?>> void notifyListener(Executor notifyExecutor, GenericFutureListener<F> listener, F future)
-    {
-        if (notifyExecutor == null) notifyListener(listener, future);
-        else safeExecute(notifyExecutor, () -> notifyListener(listener, future));
-    }
-
-    /**
-     * Notify {@code listener} using {@code notifyExecutor} if set, and handling exceptions otherwise
-     */
-    static void notifyListener(@Nullable Executor notifyExecutor, Runnable listener)
-    {
-        safeExecute(notifyExecutor, listener);
-    }
-
-    private static void safeExecute(@Nullable Executor notifyExecutor, Runnable runnable)
-    {
-        if (notifyExecutor == null)
-            notifyExecutor = ImmediateExecutor.INSTANCE;
-        try
-        {
-            notifyExecutor.execute(runnable);
-        }
-        catch (Exception | Error e)
-        {
-            // TODO: suboptimal package interdependency - move FutureTask etc here?
-            ExecutionFailure.handle(e);
-        }
-    }
-
-    /**
-     * Encapsulate a regular listener in a linked list
-     */
-    static class GenericFutureListenerList<V> extends ListenerList<V>
-    {
-        final GenericFutureListener listener;
-
-        GenericFutureListenerList(GenericFutureListener listener)
-        {
-            this.listener = listener;
-        }
-
-        @Override
-        void notifySelf(Executor notifyExecutor, Future<V> future)
-        {
-            notifyListener(notifyExecutor, listener, future);
-        }
-    }
-
-    /**
-     * Encapsulates the invocation of a callback with everything needed to submit for execution
-     * without incurring significant further overhead as a list
-     */
-    static class CallbackListener<V> extends ListenerList<V> implements Runnable
-    {
-        final Future<V> future;
-        final FutureCallback<? super V> callback;
-
-        CallbackListener(Future<V> future, FutureCallback<? super V> callback)
-        {
-            this.future = future;
-            this.callback = callback;
-        }
-
-        @Override
-        public void run()
-        {
-            if (future.isSuccess()) callback.onSuccess(future.getNow());
-            else callback.onFailure(future.cause());
-        }
-
-        @Override
-        void notifySelf(Executor notifyExecutor, Future<V> future)
-        {
-            notifyListener(notifyExecutor, this);
-        }
-    }
-
-    /**
-     * Encapsulates the invocation of a callback with everything needed to submit for execution
-     * without incurring significant further overhead as a list
-     */
-    static class CallbackBiConsumerListener<V> extends ListenerList<V> implements Runnable
-    {
-        final Future<V> future;
-        final BiConsumer<? super V, Throwable> callback;
-        final Executor executor;
-
-        CallbackBiConsumerListener(Future<V> future, BiConsumer<? super V, Throwable> callback, Executor executor)
-        {
-            this.future = future;
-            this.callback = callback;
-            this.executor = executor;
-        }
-
-        @Override
-        public void run()
-        {
-            if (future.isSuccess()) callback.accept(future.getNow(), null);
-            else callback.accept(null, future.cause());
-        }
-
-        @Override
-        void notifySelf(Executor notifyExecutor, Future<V> future)
-        {
-            notifyListener(executor == null ? notifyExecutor : executor, this);
-        }
-    }
-
-    /**
-     * Encapsulates the invocation of a callback with everything needed to submit for execution
-     * without incurring significant further overhead as a list
-     */
-    static class CallbackListenerWithExecutor<V> extends CallbackListener<V>
-    {
-        final Executor executor;
-        CallbackListenerWithExecutor(Future<V> future, FutureCallback<? super V> callback, Executor executor)
-        {
-            super(future, callback);
-            this.executor = executor;
-        }
-
-        @Override
-        void notifySelf(Executor notifyExecutor, Future<V> future)
-        {
-            notifyListener(executor, this);
-        }
-    }
-
-    /**
-     * Encapsulates the invocation of a callback with everything needed to submit for execution
-     * without incurring significant further overhead as a list
-     */
-    static class CallbackLambdaListener<V> extends ListenerList<V> implements Runnable
-    {
-        final Future<V> future;
-        final Consumer<? super V> onSuccess;
-        final Consumer<? super Throwable> onFailure;
-        final Executor executor;
-
-        CallbackLambdaListener(Future<V> future, Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure, Executor executor)
-        {
-            this.future = future;
-            this.onSuccess = onSuccess;
-            this.onFailure = onFailure;
-            this.executor = executor;
-        }
-
-        @Override
-        public void run()
-        {
-            if (future.isSuccess()) onSuccess.accept(future.getNow());
-            else onFailure.accept(future.cause());
-        }
-
-        @Override
-        void notifySelf(Executor notifyExecutor, Future future)
-        {
-            notifyListener(executor == null ? notifyExecutor : executor, this);
-        }
-    }
-
-    /**
-     * Encapsulate a task, executable on completion by {@link Future#notifyExecutor}, in a linked list for storing in
-     * {@link #listeners}, either as a listener on its own (since we need to encapsulate it anyway), or alongside
-     * other listeners in a list
-     */
-    static class RunnableWithNotifyExecutor<V> extends ListenerList<V>
-    {
-        final Runnable task;
-        RunnableWithNotifyExecutor(Runnable task)
-        {
-            this.task = task;
-        }
-
-        @Override
-        void notifySelf(Executor notifyExecutor, Future<V> future)
-        {
-            notifyListener(notifyExecutor, task);
-        }
-    }
-
-    /**
-     * Encapsulate a task executable on completion in a linked list for storing in {@link #listeners},
-     * either as a listener on its own (since we need to encapsulate it anyway), or alongside other listeners
-     * in a list
-     */
-    static class RunnableWithExecutor<V> extends ListenerList<V>
-    {
-        final Runnable task;
-        @Nullable final Executor executor;
-        RunnableWithExecutor(Runnable task, @Nullable Executor executor)
-        {
-            this.task = task;
-            this.executor = executor;
-        }
-
-        @Override
-        void notifySelf(Executor notifyExecutor, Future<V> future)
-        {
-            notifyListener(inExecutor(executor) ? null : executor, task);
-        }
-    }
-
-    /**
-     * Dummy that indicates listeners are already being notified after the future was completed,
-     * so we cannot notify them ourselves whilst maintaining the guaranteed invocation order.
-     * The invocation of the list can be left to the thread already notifying listeners.
-     */
-    static class Notifying<V> extends ListenerList<V>
-    {
-        static final Notifying NOTIFYING = new Notifying();
-
-        @Override
-        void notifySelf(Executor notifyExecutor, Future<V> future)
-        {
-        }
-    }
-
-    /**
-     * @return true iff the invoking thread is executing {@code executor}
-     */
-    static boolean inExecutor(Executor executor)
-    {
-        return (executor instanceof EventExecutor && ((EventExecutor) executor).inEventLoop())
-               || (executor instanceof ExecutorPlus && ((ExecutorPlus) executor).inExecutor());
-    }
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Promise.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Promise.java
deleted file mode 100644
index 2e84d33..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/Promise.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import com.google.common.util.concurrent.FutureCallback;
-import io.netty.util.concurrent.GenericFutureListener;
-import org.apache.cassandra.utils.Shared;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
-
-import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * A Promise that integrates {@link io.netty.util.concurrent.Promise} with our {@link Future} API
- * to improve clarity and coherence in the codebase.
- */
-@Shared(scope = SIMULATION, ancestors = INTERFACES)
-public interface Promise<V> extends io.netty.util.concurrent.Promise<V>, Future<V>
-{
-    public static <V> GenericFutureListener<? extends Future<V>> listener(FutureCallback<V> callback)
-    {
-        return future -> {
-            if (future.isSuccess()) callback.onSuccess(future.getNow());
-            else callback.onFailure(future.cause());
-        };
-    }
-
-    public static <V> GenericFutureListener<? extends Future<V>> listener(ExecutorService executor, FutureCallback<V> callback)
-    {
-        return future -> executor.execute(() -> {
-            if (future.isSuccess()) callback.onSuccess(future.getNow());
-            else callback.onFailure(future.cause());
-        });
-    }
-
-    @Override
-    Promise<V> addCallback(FutureCallback<? super V> callback);
-
-    Promise<V> addCallback(FutureCallback<? super V> callback, Executor executor);
-
-    Promise<V> addCallback(Consumer<? super V> onSuccess, Consumer<? super Throwable> onFailure);
-
-    @Override
-    Promise<V> addListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> var1);
-
-    @Override
-    Promise<V> addListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... var1);
-
-    @Override
-    Promise<V> removeListener(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>> var1);
-
-    @Override
-    Promise<V> removeListeners(GenericFutureListener<? extends io.netty.util.concurrent.Future<? super V>>... var1);
-
-    /**
-     * Complete the promise successfully if not already complete
-     * @throws IllegalStateException if already set
-     */
-    @Override
-    Promise<V> setSuccess(V v) throws IllegalStateException;
-
-    /**
-     * Complete the promise abnormally if not already complete
-     * @throws IllegalStateException if already set
-     */
-    @Override
-    Promise<V> setFailure(Throwable throwable) throws IllegalStateException;
-
-    /**
-     * Prevent a future caller from cancelling this promise
-     * @return true iff this invocation set it to uncancellable, whether or not now uncancellable
-     */
-    boolean setUncancellableExclusive();
-
-    /**
-     * Wait indefinitely for this promise to complete, throwing any interrupt
-     * @throws InterruptedException if interrupted
-     */
-    @Override
-    Promise<V> await() throws InterruptedException;
-
-    /**
-     * Wait indefinitely for this promise to complete
-     */
-    @Override
-    Promise<V> awaitUninterruptibly();
-
-    /**
-     * Wait indefinitely for this promise to complete, throwing any interrupt as an UncheckedInterruptedException
-     * @throws UncheckedInterruptedException if interrupted
-     */
-    @Override
-    Promise<V> awaitThrowUncheckedOnInterrupt();
-
-    /**
-     * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
-     * so may cause problems for reporting stack traces
-     */
-    @Override
-    Promise<V> sync() throws InterruptedException;
-
-    /**
-     * waits for completion; in case of failure rethrows the original exception without a new wrapping exception
-     * so may cause problems for reporting stack traces
-     */
-    @Override
-    Promise<V> syncUninterruptibly();
-}
-
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/UncheckedInterruptedException.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/UncheckedInterruptedException.java
deleted file mode 100644
index d7248e8..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/UncheckedInterruptedException.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.utils.concurrent;
-
-import org.apache.cassandra.utils.Shared;
-
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * Unchecked {@link InterruptedException}, to be thrown in places where an interrupt is unexpected
- */
-@Shared(scope = SIMULATION)
-public class UncheckedInterruptedException extends RuntimeException
-{
-    public UncheckedInterruptedException()
-    {
-    }
-    public UncheckedInterruptedException(InterruptedException cause)
-    {
-        super(cause);
-    }
-}
diff --git a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/WaitQueue.java b/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/WaitQueue.java
deleted file mode 100644
index d2a841d..0000000
--- a/accord-core/src/main/java/org/apache/cassandra/utils/concurrent/WaitQueue.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.cassandra.utils.concurrent;
-
-import java.util.Iterator;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
-import java.util.concurrent.locks.LockSupport;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
-
-import org.apache.cassandra.utils.Intercept;
-import org.apache.cassandra.utils.Shared;
-import org.apache.cassandra.utils.concurrent.Awaitable.AbstractAwaitable;
-
-import static org.apache.cassandra.utils.Clock.Global.nanoTime;
-import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES;
-import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
-
-/**
- * <p>A relatively easy to use utility for general purpose thread signalling.</p>
- * <p>Usage on a thread awaiting a state change using a WaitQueue q is:</p>
- * <pre>
- * {@code
- *      while (!conditionMet())
- *          Signal s = q.register();
- *              if (!conditionMet())    // or, perhaps more correctly, !conditionChanged()
- *                  s.await();
- *              else
- *                  s.cancel();
- * }
- * </pre>
- * A signalling thread, AFTER changing the state, then calls q.signal() to wake up one, or q.signalAll()
- * to wake up all, waiting threads.
- * <p>To understand intuitively how this class works, the idea is simply that a thread, once it considers itself
- * incapable of making progress, registers to be awoken once that changes. Since this could have changed between
- * checking and registering (in which case the thread that made this change would have been unable to signal it),
- * it checks the condition again, sleeping only if it hasn't changed/still is not met.</p>
- * <p>This thread synchronisation scheme has some advantages over Condition objects and Object.wait/notify in that no monitor
- * acquisition is necessary and, in fact, besides the actual waiting on a signal, all operations are non-blocking.
- * As a result consumers can never block producers, nor each other, or vice versa, from making progress.
- * Threads that are signalled are also put into a RUNNABLE state almost simultaneously, so they can all immediately make
- * progress without having to serially acquire the monitor/lock, reducing scheduler delay incurred.</p>
- *
- * <p>A few notes on utilisation:</p>
- * <p>1. A thread will only exit await() when it has been signalled, but this does not guarantee the condition has not
- * been altered since it was signalled, and depending on your design it is likely the outer condition will need to be
- * checked in a loop, though this is not always the case.</p>
- * <p>2. Each signal is single use, so must be re-registered after each await(). This is true even if it times out.</p>
- * <p>3. If you choose not to wait on the signal (because the condition has been met before you waited on it)
- * you must cancel() the signal if the signalling thread uses signal() to awake waiters; otherwise signals will be
- * lost. If signalAll() is used but infrequent, and register() is frequent, cancel() should still be used to prevent the
- * queue growing unboundedly. Similarly, if you provide a TimerContext, cancel should be used to ensure it is not erroneously
- * counted towards wait time.</p>
- * <p>4. Care must be taken when selecting conditionMet() to ensure we are waiting on the condition that actually
- * indicates progress is possible. In some complex cases it may be tempting to wait on a condition that is only indicative
- * of local progress, not progress on the task we are aiming to complete, and a race may leave us waiting for a condition
- * to be met that we no longer need.
- * <p>5. This scheme is not fair</p>
- * <p>6. Only the thread that calls register() may call await()</p>
- *
- * TODO: this class should not be backed by CLQ (should use an intrusive linked-list with lower overhead)
- */
-@Shared(scope = SIMULATION, inner = INTERFACES)
-public interface WaitQueue
-{
-    /**
-     * A Signal is a one-time-use mechanism for a thread to wait for notification that some condition
-     * state has transitioned that it may be interested in (and hence should check if it is).
-     * It is potentially transient, i.e. the state can change in the meantime, it only indicates
-     * that it should be checked, not necessarily anything about what the expected state should be.
-     *
-     * Signal implementations should never wake up spuriously, they are always woken up by a
-     * signal() or signalAll().
-     *
-     * This abstract definition of Signal does not need to be tied to a WaitQueue.
-     * Whilst RegisteredSignal is the main building block of Signals, this abstract
-     * definition allows us to compose Signals in useful ways. The Signal is 'owned' by the
-     * thread that registered itself with WaitQueue(s) to obtain the underlying RegisteredSignal(s);
-     * only the owning thread should use a Signal.
-     */
-    public static interface Signal extends Condition
-    {
-        /**
-         * @return true if cancelled; once cancelled, must be discarded by the owning thread.
-         */
-        public boolean isCancelled();
-
-        /**
-         * @return isSignalled() || isCancelled(). Once true, the state is fixed and the Signal should be discarded
-         * by the owning thread.
-         */
-        public boolean isSet();
-
-        /**
-         * atomically: cancels the Signal if !isSet(), or returns true if isSignalled()
-         *
-         * @return true if isSignalled()
-         */
-        public boolean checkAndClear();
-
-        /**
-         * Should only be called by the owning thread. Indicates the signal can be retired,
-         * and if signalled propagates the signal to another waiting thread
-         */
-        public abstract void cancel();
-    }
-
-    /**
-     * The calling thread MUST be the thread that uses the signal
-     */
-    public Signal register();
-
-    /**
-     * The calling thread MUST be the thread that uses the signal.
-     * If the Signal is waited on, context.stop() will be called when the wait times out, the Signal is signalled,
-     * or the waiting thread is interrupted.
-     */
-    public <V> Signal register(V supplyOnDone, Consumer<V> receiveOnDone);
-
-    /**
-     * Signal one waiting thread
-     */
-    public boolean signal();
-
-    /**
-     * Signal all waiting threads
-     */
-    public void signalAll();
-
-//    /** getWaiting() > 0 */
-    public boolean hasWaiters();
-
-    /** Return how many threads are waiting */
-    public int getWaiting();
-
-    /**
-     * Factory method used to capture and redirect instantiations for simulation
-     */
-    @Intercept
-    public static WaitQueue newWaitQueue()
-    {
-        return new Standard();
-    }
-
-    class Standard implements WaitQueue
-    {
-        private static final int CANCELLED = -1;
-        private static final int SIGNALLED = 1;
-        private static final int NOT_SET = 0;
-
-        private static final AtomicIntegerFieldUpdater<RegisteredSignal> signalledUpdater = AtomicIntegerFieldUpdater.newUpdater(RegisteredSignal.class, "state");
-
-        // the waiting signals
-        private final ConcurrentLinkedQueue<RegisteredSignal> queue = new ConcurrentLinkedQueue<>();
-
-        protected Standard() {}
-
-        /**
-         * The calling thread MUST be the thread that uses the signal
-         * @return                                x
-         */
-        public Signal register()
-        {
-            RegisteredSignal signal = new RegisteredSignal();
-            queue.add(signal);
-            return signal;
-        }
-
-        /**
-         * The calling thread MUST be the thread that uses the signal.
-         * If the Signal is waited on, context.stop() will be called when the wait times out, the Signal is signalled,
-         * or the waiting thread is interrupted.
-         */
-        public <V> Signal register(V supplyOnDone, Consumer<V> receiveOnDone)
-        {
-            RegisteredSignal signal = new SignalWithListener<>(supplyOnDone, receiveOnDone);
-            queue.add(signal);
-            return signal;
-        }
-
-        /**
-         * Signal one waiting thread
-         */
-        public boolean signal()
-        {
-            while (true)
-            {
-                RegisteredSignal s = queue.poll();
-                if (s == null || s.doSignal() != null)
-                    return s != null;
-            }
-        }
-
-        /**
-         * Signal all waiting threads
-         */
-        public void signalAll()
-        {
-            if (!hasWaiters())
-                return;
-
-            // to avoid a race where the condition is not met and the woken thread managed to wait on the queue before
-            // we finish signalling it all, we pick a random thread we have woken-up and hold onto it, so that if we encounter
-            // it again we know we're looping. We reselect a random thread periodically, progressively less often.
-            // the "correct" solution to this problem is to use a queue that permits snapshot iteration, but this solution is sufficient
-            // TODO: this is only necessary because we use CLQ - which is only for historical any-NIH reasons
-            int i = 0, s = 5;
-            Thread randomThread = null;
-            Iterator<RegisteredSignal> iter = queue.iterator();
-            while (iter.hasNext())
-            {
-                RegisteredSignal signal = iter.next();
-                Thread signalled = signal.doSignal();
-
-                if (signalled != null)
-                {
-                    if (signalled == randomThread)
-                        break;
-
-                    if (++i == s)
-                    {
-                        randomThread = signalled;
-                        s <<= 1;
-                    }
-                }
-
-                iter.remove();
-            }
-        }
-
-        private void cleanUpCancelled()
-        {
-            // TODO: attempt to remove the cancelled from the beginning only (need atomic cas of head)
-            queue.removeIf(RegisteredSignal::isCancelled);
-        }
-
-        public boolean hasWaiters()
-        {
-            return !queue.isEmpty();
-        }
-
-        /**
-         * @return how many threads are waiting
-         */
-        public int getWaiting()
-        {
-            if (!hasWaiters())
-                return 0;
-            Iterator<RegisteredSignal> iter = queue.iterator();
-            int count = 0;
-            while (iter.hasNext())
-            {
-                Signal next = iter.next();
-                if (!next.isCancelled())
-                    count++;
-            }
-            return count;
-        }
-
-        /**
-         * An abstract signal implementation
-         *
-         * TODO: use intrusive linked list
-         */
-        public static abstract class AbstractSignal extends AbstractAwaitable implements Signal
-        {
-            public Signal await() throws InterruptedException
-            {
-                while (!isSignalled())
-                {
-                    checkInterrupted();
-                    LockSupport.park();
-                }
-                checkAndClear();
-                return this;
-            }
-
-            public boolean awaitUntil(long nanoTimeDeadline) throws InterruptedException
-            {
-                long now;
-                while (nanoTimeDeadline > (now = nanoTime()) && !isSignalled())
-                {
-                    checkInterrupted();
-                    long delta = nanoTimeDeadline - now;
-                    LockSupport.parkNanos(delta);
-                }
-                return checkAndClear();
-            }
-
-            private void checkInterrupted() throws InterruptedException
-            {
-                if (Thread.interrupted())
-                {
-                    cancel();
-                    throw new InterruptedException();
-                }
-            }
-        }
-
-        /**
-         * A signal registered with this WaitQueue
-         */
-        private class RegisteredSignal extends AbstractSignal
-        {
-            private volatile Thread thread = Thread.currentThread();
-            volatile int state;
-
-            public boolean isSignalled()
-            {
-                return state == SIGNALLED;
-            }
-
-            public boolean isCancelled()
-            {
-                return state == CANCELLED;
-            }
-
-            public boolean isSet()
-            {
-                return state != NOT_SET;
-            }
-
-            private Thread doSignal()
-            {
-                if (!isSet() && signalledUpdater.compareAndSet(this, NOT_SET, SIGNALLED))
-                {
-                    Thread thread = this.thread;
-                    LockSupport.unpark(thread);
-                    this.thread = null;
-                    return thread;
-                }
-                return null;
-            }
-
-            public void signal()
-            {
-                doSignal();
-            }
-
-            public boolean checkAndClear()
-            {
-                if (!isSet() && signalledUpdater.compareAndSet(this, NOT_SET, CANCELLED))
-                {
-                    thread = null;
-                    cleanUpCancelled();
-                    return false;
-                }
-                // must now be signalled assuming correct API usage
-                return true;
-            }
-
-            /**
-             * Should only be called by the registered thread. Indicates the signal can be retired,
-             * and if signalled propagates the signal to another waiting thread
-             */
-            public void cancel()
-            {
-                if (isCancelled())
-                    return;
-                if (!signalledUpdater.compareAndSet(this, NOT_SET, CANCELLED))
-                {
-                    // must already be signalled - switch to cancelled and
-                    state = CANCELLED;
-                    // propagate the signal
-                    Standard.this.signal();
-                }
-                thread = null;
-                cleanUpCancelled();
-            }
-        }
-
-        /**
-         * A RegisteredSignal that stores a TimerContext, and stops the timer when either cancelled or
-         * finished waiting. i.e. if the timer is started when the signal is registered it tracks the
-         * time in between registering and invalidating the signal.
-         */
-        private final class SignalWithListener<V> extends RegisteredSignal
-        {
-            private final V supplyOnDone;
-            private final Consumer<V> receiveOnDone;
-
-            private SignalWithListener(V supplyOnDone, Consumer<V> receiveOnDone)
-            {
-                this.receiveOnDone = receiveOnDone;
-                this.supplyOnDone = supplyOnDone;
-            }
-
-
-            @Override
-            public boolean checkAndClear()
-            {
-                receiveOnDone.accept(supplyOnDone);
-                return super.checkAndClear();
-            }
-
-            @Override
-            public void cancel()
-            {
-                if (!isCancelled())
-                {
-                    receiveOnDone.accept(supplyOnDone);
-                    super.cancel();
-                }
-            }
-        }
-    }
-
-    /**
-     * Loops waiting on the supplied condition and WaitQueue and will not return until the condition is true
-     */
-    public static void waitOnCondition(BooleanSupplier condition, WaitQueue queue) throws InterruptedException
-    {
-        while (!condition.getAsBoolean())
-        {
-            Signal s = queue.register();
-            if (!condition.getAsBoolean()) s.await();
-            else s.cancel();
-        }
-    }
-}
diff --git a/accord-core/src/test/java/accord/NetworkFilter.java b/accord-core/src/test/java/accord/NetworkFilter.java
index f0e4bd3..42711d7 100644
--- a/accord-core/src/test/java/accord/NetworkFilter.java
+++ b/accord-core/src/test/java/accord/NetworkFilter.java
@@ -73,7 +73,7 @@ public class NetworkFilter
 
     public static Predicate<Message> notMessageType(Class<? extends Message> klass)
     {
-        return Predicate.not(isMessageType(klass));
+        return msg -> !klass.isAssignableFrom(msg.getClass());
     }
 
     /**
diff --git a/accord-core/src/test/java/accord/Utils.java b/accord-core/src/test/java/accord/Utils.java
index 50dcbdf..f9766e4 100644
--- a/accord-core/src/test/java/accord/Utils.java
+++ b/accord-core/src/test/java/accord/Utils.java
@@ -84,12 +84,12 @@ public class Utils
 
     public static Txn writeTxn(Keys keys)
     {
-        return new Txn(keys, MockStore.read(keys), MockStore.QUERY, MockStore.update(keys));
+        return new Txn.InMemory(keys, MockStore.read(keys), MockStore.QUERY, MockStore.update(keys));
     }
 
     public static Txn readTxn(Keys keys)
     {
-        return new Txn(keys, MockStore.read(keys), MockStore.QUERY);
+        return new Txn.InMemory(keys, MockStore.read(keys), MockStore.QUERY);
     }
 
     public static Shard shard(KeyRange range, List<Node.Id> nodes, Set<Node.Id> fastPath)
diff --git a/accord-core/src/test/java/accord/burn/BurnTest.java b/accord-core/src/test/java/accord/burn/BurnTest.java
index 7be5724..59a6fd7 100644
--- a/accord-core/src/test/java/accord/burn/BurnTest.java
+++ b/accord-core/src/test/java/accord/burn/BurnTest.java
@@ -57,6 +57,8 @@ import accord.verify.StrictSerializabilityVerifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static accord.utils.Utils.toArray;
+
 public class BurnTest
 {
     private static final Logger logger = LoggerFactory.getLogger(BurnTest.class);
@@ -93,7 +95,7 @@ public class BurnTest
             requestKeys.addAll(update.keySet());
             ListRead read = new ListRead(readKeys, new Keys(requestKeys));
             ListQuery query = new ListQuery(client, count);
-            ListRequest request = new ListRequest(new Txn(new Keys(requestKeys), read, query, update));
+            ListRequest request = new ListRequest(new Txn.InMemory(new Keys(requestKeys), read, query, update));
             packets.add(new Packet(client, node, count, request));
         }
 
@@ -162,7 +164,7 @@ public class BurnTest
 
         StrictSerializabilityVerifier strictSerializable = new StrictSerializabilityVerifier(keyCount);
 
-        Packet[] requests = generate(random, clients, nodes, keyCount, operations).toArray(Packet[]::new);
+        Packet[] requests = toArray(generate(random, clients, nodes, keyCount, operations), Packet[]::new);
         int[] starts = new int[requests.length];
         Packet[] replies = new Packet[requests.length];
 
@@ -232,7 +234,7 @@ public class BurnTest
 
         try
         {
-            Cluster.run(nodes.toArray(Id[]::new), () -> queue,
+            Cluster.run(toArray(nodes, Id[]::new), () -> queue,
                         responseSink, failures::add,
                         () -> new Random(random.nextLong()),
                         () -> new AtomicLong()::incrementAndGet,
diff --git a/accord-core/src/test/java/accord/burn/TopologyUpdate.java b/accord-core/src/test/java/accord/burn/TopologyUpdate.java
index d17630e..16ab44d 100644
--- a/accord-core/src/test/java/accord/burn/TopologyUpdate.java
+++ b/accord-core/src/test/java/accord/burn/TopologyUpdate.java
@@ -21,6 +21,7 @@ package accord.burn;
 import accord.api.Key;
 import accord.api.Result;
 import accord.api.TestableConfigurationService;
+import accord.impl.InMemoryCommandStore;
 import accord.local.Command;
 import accord.local.Node;
 import accord.local.Status;
@@ -47,6 +48,8 @@ import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Stream;
 
+import static accord.impl.InMemoryCommandStores.inMemory;
+
 public class TopologyUpdate
 {
     private static final Logger logger = LoggerFactory.getLogger(TopologyUpdate.class);
@@ -89,9 +92,9 @@ public class TopologyUpdate
                 return;
             }
 
-            Key progressKey = node.trySelectProgressKey(txnId, txn.keys, homeKey); // likely to be null, unless flip-flop of ownership
+            Key progressKey = node.trySelectProgressKey(txnId, txn.keys(), homeKey); // likely to be null, unless flip-flop of ownership
             // TODO: can skip the homeKey if it's not a participating key in the transaction
-            node.forEachLocalSince(txn.keys, epoch, commandStore -> {
+            inMemory(node).forEachLocalSince(txn.keys(), epoch, commandStore -> {
                 switch (status)
                 {
                     case AcceptedInvalidate:
@@ -159,9 +162,9 @@ public class TopologyUpdate
         Map<TxnId, CommandSync> syncMessages = new ConcurrentHashMap<>();
         Consumer<Command> commandConsumer = command -> syncMessages.put(command.txnId(), new CommandSync(command, epoch));
         if (committedOnly)
-            node.forEachLocal(commandStore -> commandStore.forCommittedInEpoch(ranges, epoch, commandConsumer));
+            inMemory(node).forEachLocal(commandStore -> InMemoryCommandStore.inMemory(commandStore).forCommittedInEpoch(ranges, epoch, commandConsumer));
         else
-            node.forEachLocal(commandStore -> commandStore.forEpochCommands(ranges, epoch, commandConsumer));
+            inMemory(node).forEachLocal(commandStore -> InMemoryCommandStore.inMemory(commandStore).forEpochCommands(ranges, epoch, commandConsumer));
         return syncMessages.values().stream().map(cmd -> MessageTask.of(node, recipients.apply(cmd), "Sync:" + cmd.txnId + ':' + epoch + ':' + forEpoch, cmd::process));
     }
 
diff --git a/accord-core/src/test/java/accord/coordinate/CoordinateTest.java b/accord-core/src/test/java/accord/coordinate/CoordinateTest.java
index b8db5d2..922c87a 100644
--- a/accord-core/src/test/java/accord/coordinate/CoordinateTest.java
... 1384 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org