text: /**
   * 1. Test that rollback can successfully recover committed prepared transactions that were prepared
   * before the stable timestamp and committed between the stable timestamp and the common point.
   * 2. Test that rollback can successfully recover aborted prepared transactions that were prepared
   * and aborted between the stable timestamp and the common point.
   *
   * @tags: [
   *   uses_prepare_transaction,
   *   uses_transactions,
   * ]
   */
  (function() {
  "use strict";
  
  load("jstests/core/txns/libs/prepare_helpers.js");
  load("jstests/replsets/libs/rollback_test.js");
  
  const dbName = "test";
  const collName = "recover_committed_aborted_prepared_transactions";
  
  const rollbackTest = new RollbackTest(dbName);
  let primary = rollbackTest.getPrimary();
  
  // Create collection we're using beforehand.
  let testDB = primary.getDB(dbName);
  let testColl = testDB.getCollection(collName);
  
  assert.commandWorked(testDB.runCommand({create: collName}));
  
  // Start two different sessions on the primary.
  let session1 = primary.startSession({causalConsistency: false});
  const sessionID1 = session1.getSessionId();
  const session2 = primary.startSession({causalConsistency: false});
  
  let sessionDB1 = session1.getDatabase(dbName);
  let sessionColl1 = sessionDB1.getCollection(collName);
  
  const sessionDB2 = session2.getDatabase(dbName);
  const sessionColl2 = sessionDB2.getCollection(collName);
  
  assert.commandWorked(sessionColl1.insert({_id: 1}));
  
  rollbackTest.awaitLastOpCommitted();
  
  // Prepare a transaction on the first session which will be committed eventually.
  session1.startTransaction();
  assert.commandWorked(sessionColl1.insert({_id: 2}));
  const prepareTimestamp = PrepareHelpers.prepareTransaction(session1);
  
  // Prevent the stable timestamp from moving beyond the following prepared transactions so
  // that when we replay the oplog from the stable timestamp, we correctly recover them.
  assert.commandWorked(
      primary.adminCommand({configureFailPoint: 'disableSnapshotting', mode: 'alwaysOn'}));
  
  // The following transactions will be prepared before the common point, so they must be in
  // prepare after rollback recovery.
  
  // Prepare another transaction on the second session which will be aborted.
  session2.startTransaction();
  assert.commandWorked(sessionColl2.insert({_id: 3}));
  const prepareTimestamp2 = PrepareHelpers.prepareTransaction(session2, {w: 1});
  
  // Commit the first transaction.
  assert.commandWorked(PrepareHelpers.commitTransaction(session1, prepareTimestamp));
  
  // Abort the second transaction.
  assert.commandWorked(session2.abortTransaction_forTesting());
  
  // Check that we have two transactions in the transactions table.
  assert.eq(primary.getDB('config')['transactions'].find().itcount(), 2);
  
  // The following write will be rolled back.
  rollbackTest.transitionToRollbackOperations();
  assert.commandWorked(testColl.insert({_id: 4}));
  
  rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
  rollbackTest.transitionToSyncSourceOperationsDuringRollback();
  
  try {
      rollbackTest.transitionToSteadyStateOperations();
  } finally {
      assert.commandWorked(
          primary.adminCommand({configureFailPoint: 'disableSnapshotting', mode: 'off'}));
  }
  
  // Make sure there are two transactions in the transactions table.
  assert.eq(primary.getDB('config')['transactions'].find().itcount(), 2);
  
  // Make sure we can see the first two writes and the insert from the first prepared transaction.
  // Make sure we cannot see the insert from the second prepared transaction or the writes after
  // transitionToRollbackOperations.
  assert.sameMembers(testColl.find().toArray(), [{_id: 1}, {_id: 2}]);
  assert.sameMembers(sessionColl1.find().toArray(), [{_id: 1}, {_id: 2}]);
  
  assert.eq(testColl.count(), 2);
  assert.eq(sessionColl1.count(), 2);
  
  // Get the correct members after the topology changes.
  primary = rollbackTest.getPrimary();
  testDB = primary.getDB(dbName);
  testColl = testDB.getCollection(collName);
  const rst = rollbackTest.getTestFixture();
  const secondaries = rst.getSecondaries();
  
  // Make sure we can successfully run a prepared transaction on the same first session after
  // going through rollback. This ensures that the session state has properly been restored.
  session1 = PrepareHelpers.createSessionWithGivenId(primary, sessionID1, {causalConsistency: false});
  sessionDB1 = session1.getDatabase(dbName);
  sessionColl1 = sessionDB1.getCollection(collName);
  // The next transaction on this session should have a txnNumber of 1. We explicitly set this
  // since createSessionWithGivenId does not restore the current txnNumber in the shell.
  session1.setTxnNumber_forTesting(1);
  
  session1.startTransaction();
  assert.commandWorked(sessionColl1.insert({_id: 5}));
  const prepareTimestamp3 = PrepareHelpers.prepareTransaction(session1);
  // Make sure we can successfully retry the commitTransaction command after rollback.
  assert.commandWorked(PrepareHelpers.commitTransaction(session1, prepareTimestamp3));
  
  session1.startTransaction();
  assert.commandWorked(sessionColl1.insert({_id: 6}));
  PrepareHelpers.prepareTransaction(session1);
  assert.commandWorked(session1.abortTransaction_forTesting());
  // Retrying the abortTransaction command should fail with a NoSuchTransaction error.
  assert.commandFailedWithCode(sessionDB1.adminCommand({
      abortTransaction: 1,
      txnNumber: NumberLong(session1.getTxnNumber_forTesting()),
      autocommit: false,
  }),
                               ErrorCodes.NoSuchTransaction);
  
  // Make sure we can see the insert after committing the prepared transaction.
  assert.sameMembers(testColl.find().toArray(), [{_id: 1}, {_id: 2}, {_id: 5}]);
  assert.eq(testColl.count(), 3);
  
  rollbackTest.stop();
  }());
  