Always read writes from same txn epoch.

We must do this even in the event that the timestamp of a write from our
own txn gets pushed forward so txn is attempting to read earlier
timestamp. We can't have a txn intent getting pushed forward by another
txn only to have it become invisible to the current txn, which isn't yet
aware the txn commit timestamp has been pushed.

Allow non-transactional reads and writes to push intents. This is
done by generating a random priority for the non-transactional actor.
We could consider allowing priority to be set as an optional field of
RequestHeader, outside of RequestHeader.Txn for non-transactional commands.

Change push txn code to always allow pushing the timestamp forward on
txn intents where isolation level is SNAPSHOT. Since snapshot isolation
txns don't mind having their timestamp advanced, this is better overall
for system performance--readers never block on snapshot isolation txns.

Added unittests for all cases.
In the history you outline, you need to take into consideration the relative timestamps of the two txns. The double-push scenario you outline won't actually happen (but even if it did, wouldn't be a problem). To see this: If Txn-1 has a higher timestamp, then Txn-2's read of key "a" will not trigger an internal push txn (it can happily read the earlier version).

Otherwise, it pushes Txn-1 as in your example, then Txn-1 tries to read "b" at its earlier timestamp (since Txn-1 has an earlier timestamp in this case AND it doesn't know it's been pushed yet). So it doesn't push Txn-2. However, when Txn-1 goes to commit, it notices that its commit timestamp has been pushed, so it must restart.

Let's say the history were to happen exactly as you describe, where Txn-2 pushes Txn-1 over "a" and then Txn-1 pushes Txn-2 over "b". In this history, both have pushed timetamps. The snapshot isolation txn (Txn-2), regardless of whether it commits first or second, will always succeed. Txn-1, on the other, will always restart if it was pushed. The fact that it restarts is what guarantees its serializability.

I'm not exactly sure what you mean by "linearizability of txn-1". Ordering of commands within a transaction is guaranteed because we only ratchet our requests timestamps monotonically upwards. The linearizability of multiple transactions in the system is somewhat orthogonal--more a question of whether there's a way to observe different orderings in situ while transactions are underway than the final commit timestamps on transactions suggest after the fact. I don't how that applies to this example. Could you elaborate?