diff --git a/zircon/system/ulib/blobfs/blobfs.cpp b/zircon/system/ulib/blobfs/blobfs.cpp
index b0bce191845328c0e24ff67b85966fff904348ec..7d2c99119e81ab5dbad7898be14d94f0869996ce 100644
--- a/zircon/system/ulib/blobfs/blobfs.cpp
+++ b/zircon/system/ulib/blobfs/blobfs.cpp
@@ -253,19 +253,20 @@ void Blobfs::WriteBitmap(WritebackWork* wb, uint64_t nblocks, uint64_t start_blo
         fbl::round_up(start_block + nblocks, kBlobfsBlockBits) / kBlobfsBlockBits;
 
     // Write back the block allocation bitmap
-    wb->Enqueue(allocator_->GetBlockMapVmo(), bbm_start_block,
-                BlockMapStartBlock(info_) + bbm_start_block, bbm_end_block - bbm_start_block);
+    wb->Transaction().Enqueue(allocator_->GetBlockMapVmo(), bbm_start_block,
+                              BlockMapStartBlock(info_) + bbm_start_block, bbm_end_block -
+                              bbm_start_block);
 }
 
 void Blobfs::WriteNode(WritebackWork* wb, uint32_t map_index) {
     TRACE_DURATION("blobfs", "Blobfs::WriteNode", "map_index", map_index);
     uint64_t b = (map_index * sizeof(Inode)) / kBlobfsBlockSize;
-    wb->Enqueue(allocator_->GetNodeMapVmo(), b, NodeMapStartBlock(info_) + b, 1);
+    wb->Transaction().Enqueue(allocator_->GetNodeMapVmo(), b, NodeMapStartBlock(info_) + b, 1);
 }
 
 void Blobfs::WriteInfo(WritebackWork* wb) {
     memcpy(info_mapping_.start(), &info_, sizeof(info_));
-    wb->Enqueue(info_mapping_.vmo(), 0, 0, 1);
+    wb->Transaction().Enqueue(info_mapping_.vmo(), 0, 0, 1);
 }
 
 zx_status_t Blobfs::CreateFsId() {
@@ -401,8 +402,8 @@ zx_status_t Blobfs::AddInodes(fzl::ResizeableVmoMapper* node_map) {
     }
 
     WriteInfo(wb.get());
-    wb.get()->Enqueue(node_map->vmo(), inoblks_old, NodeMapStartBlock(info_) + inoblks_old,
-                      inoblks - inoblks_old);
+    wb->Transaction().Enqueue(node_map->vmo(), inoblks_old, NodeMapStartBlock(info_) + inoblks_old,
+                              inoblks - inoblks_old);
     return EnqueueWork(std::move(wb), EnqueueType::kJournal);
 }
 
@@ -461,7 +462,8 @@ zx_status_t Blobfs::AddBlocks(size_t nblocks, RawBitmap* block_map) {
         uint64_t vmo_offset = abmblks_old;
         uint64_t dev_offset = BlockMapStartBlock(info_) + abmblks_old;
         uint64_t length = abmblks - abmblks_old;
-        wb.get()->Enqueue(block_map->StorageUnsafe()->GetVmo(), vmo_offset, dev_offset, length);
+        wb->Transaction().Enqueue(block_map->StorageUnsafe()->GetVmo(), vmo_offset, dev_offset,
+                                  length);
     }
 
     info_.vslice_count += length;
diff --git a/zircon/system/ulib/blobfs/include/blobfs/write-txn.h b/zircon/system/ulib/blobfs/include/blobfs/write-txn.h
index de59a10925ff830af50a43ed227bcec7657ebd9a..b205835234d0adbc29fcb6827d888ff230547136 100644
--- a/zircon/system/ulib/blobfs/include/blobfs/write-txn.h
+++ b/zircon/system/ulib/blobfs/include/blobfs/write-txn.h
@@ -68,7 +68,6 @@ public:
         vmoid_ = VMOID_INVALID;
     }
 
-protected:
     // Activates the transaction.
     zx_status_t Flush();
 
diff --git a/zircon/system/ulib/blobfs/include/blobfs/writeback-work.h b/zircon/system/ulib/blobfs/include/blobfs/writeback-work.h
index 84dfba45dfb0e3e2caa8d86dd308456697c445a8..f09737a163893b35612e8d54cb0f8fc4706c7552 100644
--- a/zircon/system/ulib/blobfs/include/blobfs/writeback-work.h
+++ b/zircon/system/ulib/blobfs/include/blobfs/writeback-work.h
@@ -19,8 +19,7 @@
 namespace blobfs {
 
 // A wrapper around a WriteTxn with added support for callback invocation on completion.
-class WritebackWork : public WriteTxn,
-                      public fbl::SinglyLinkedListable<std::unique_ptr<WritebackWork>> {
+class WritebackWork : public fbl::SinglyLinkedListable<std::unique_ptr<WritebackWork>> {
 public:
     using ReadyCallback = fbl::Function<bool()>;
     using SyncCallback = fs::Vnode::SyncCallback;
@@ -55,7 +54,10 @@ public:
     // and resets the WritebackWork to its initial state.
     zx_status_t Complete();
 
+    WriteTxn& Transaction() { return transaction_; }
+
 private:
+    WriteTxn transaction_;
     // Optional callbacks.
     ReadyCallback ready_cb_; // Call to check whether work is ready to be processed.
     SyncCallback sync_cb_; // Call after work has been completely flushed.
diff --git a/zircon/system/ulib/blobfs/journal-entry.cpp b/zircon/system/ulib/blobfs/journal-entry.cpp
index fecb25d4d21dfbe7e13e2090ea028e7b14033e5f..04118bc7ed5500b1ef0cb873bb7a8042fba8e6a9 100644
--- a/zircon/system/ulib/blobfs/journal-entry.cpp
+++ b/zircon/system/ulib/blobfs/journal-entry.cpp
@@ -21,15 +21,15 @@ JournalEntry::JournalEntry(JournalBase* journal, EntryStatus status, size_t head
         return;
     }
 
-    size_t work_blocks = work_->BlkCount();
+    size_t work_blocks = work_->Transaction().BlkCount();
     // Ensure the work is valid.
     ZX_DEBUG_ASSERT(work_blocks > 0);
-    ZX_DEBUG_ASSERT(work_->IsBuffered());
+    ZX_DEBUG_ASSERT(work_->Transaction().IsBuffered());
     ZX_DEBUG_ASSERT(work_blocks <= kMaxEntryDataBlocks);
 
     // Copy all target blocks from the WritebackWork to the entry's header block.
-    for (size_t i = 0; i < work_->Requests().size(); i++) {
-        WriteRequest& request = work_->Requests()[i];
+    for (size_t i = 0; i < work_->Transaction().Requests().size(); i++) {
+        WriteRequest& request = work_->Transaction().Requests()[i];
         for (size_t j = request.dev_offset; j < request.dev_offset + request.length; j++) {
             header_block_.target_blocks[block_count_++] = j;
         }
diff --git a/zircon/system/ulib/blobfs/journal.cpp b/zircon/system/ulib/blobfs/journal.cpp
index 764ff5c12218ca2b89354a6c7589090e3c4b4dfb..468e08b21b91030e42f9cb1c6d7fa9c1fba45f8e 100644
--- a/zircon/system/ulib/blobfs/journal.cpp
+++ b/zircon/system/ulib/blobfs/journal.cpp
@@ -209,10 +209,10 @@ zx_status_t Journal::InitWriteback() {
 zx_status_t Journal::Enqueue(fbl::unique_ptr<WritebackWork> work) {
     // Verify that the work exists and has not already been prepared for writeback.
     ZX_DEBUG_ASSERT(work != nullptr);
-    ZX_DEBUG_ASSERT(!work->IsBuffered());
+    ZX_DEBUG_ASSERT(!work->Transaction().IsBuffered());
 
     // Block count will be the number of blocks in the transaction + header + commit.
-    size_t blocks = work->BlkCount();
+    size_t blocks = work->Transaction().BlkCount();
     // By default set the header/commit indices to the buffer capacity,
     // since this will be an invalid index value.
     size_t header_index = entries_->capacity();
@@ -249,7 +249,7 @@ zx_status_t Journal::Enqueue(fbl::unique_ptr<WritebackWork> work) {
             // header and commit blocks asynchronously, since this will involve calculating the
             // checksum.
             // TODO(planders): Release the lock while transaction is being copied.
-            entries_->CopyTransaction(work.get());
+            entries_->CopyTransaction(&work->Transaction());
 
             // Assign commit_index immediately after copying to the buffer.
             // Increase length_ accordingly.
@@ -313,7 +313,7 @@ fbl::unique_ptr<JournalEntry> Journal::CreateEntry(uint64_t header_index, uint64
                                                    fbl::unique_ptr<WritebackWork> work) {
     EntryStatus status = EntryStatus::kInit;
 
-    if (work->BlkCount() == 0) {
+    if (work->Transaction().BlkCount() == 0) {
         // If the work has no transactions, this is a sync work - we can return early.
         // Right now we make the assumption that if a WritebackWork has any transactions, it cannot
         // have a corresponding sync callback. We may need to revisit this later.
@@ -408,8 +408,10 @@ void Journal::PrepareDelete(JournalEntry* entry, WritebackWork* work) {
     memset(entries_->MutableData(commit_index), 0, kBlobfsBlockSize);
 
     // Enqueue transactions for the header/commit blocks.
-    entries_->AddTransaction(header_index, start_block_ + 1 + header_index, 1, work);
-    entries_->AddTransaction(commit_index, start_block_ + 1 + commit_index, 1, work);
+    entries_->AddTransaction(header_index, start_block_ + 1 + header_index, 1,
+                             &work->Transaction());
+    entries_->AddTransaction(commit_index, start_block_ + 1 + commit_index, 1,
+                             &work->Transaction());
 }
 
 fbl::unique_ptr<WritebackWork> Journal::CreateWork() {
@@ -420,7 +422,7 @@ fbl::unique_ptr<WritebackWork> Journal::CreateWork() {
 }
 
 zx_status_t Journal::EnqueueEntryWork(fbl::unique_ptr<WritebackWork> work) {
-    entries_->ValidateTransaction(work.get());
+    entries_->ValidateTransaction(&work->Transaction());
     return transaction_manager_->EnqueueWork(std::move(work), EnqueueType::kData);
 }
 
@@ -486,7 +488,7 @@ zx_status_t Journal::ReplayEntry(size_t header_index, size_t remaining_length,
     // Enqueue one block at a time, since they may not end up being contiguous on disk.
     for (unsigned i = 0; i < header->num_blocks; i++) {
         size_t vmo_block = (header_index + i + 1) % entries_->capacity();
-        entries_->AddTransaction(vmo_block, header->target_blocks[i], 1, work.get());
+        entries_->AddTransaction(vmo_block, header->target_blocks[i], 1, &work->Transaction());
     }
 
     // Replay (and therefore mount) will fail if we cannot enqueue the replay work. Since the
@@ -510,7 +512,7 @@ zx_status_t Journal::CommitReplay() {
     memset(entries_->MutableData(0), 0, kBlobfsBlockSize);
     fbl::unique_ptr<WritebackWork> work = CreateWork();
 
-    entries_->AddTransaction(0, start_block_ + 1, 1, work.get());
+    entries_->AddTransaction(0, start_block_ + 1, 1, &work->Transaction());
 
     zx_status_t status;
     if ((status = EnqueueEntryWork(std::move(work))) != ZX_OK) {
@@ -566,8 +568,8 @@ zx_status_t Journal::WriteInfo(uint64_t start, uint64_t length) {
     uint8_t* info_ptr = reinterpret_cast<uint8_t*>(info);
     info->checksum = crc32(0, info_ptr, sizeof(JournalInfo));
 
-    info_->AddTransaction(0, start_block_, 1, work.get());
-    info_->ValidateTransaction(work.get());
+    info_->AddTransaction(0, start_block_, 1, &work->Transaction());
+    info_->ValidateTransaction(&work->Transaction());
     return transaction_manager_->EnqueueWork(std::move(work), EnqueueType::kData);
 }
 
@@ -606,12 +608,12 @@ void Journal::AddEntryTransaction(size_t start, size_t length, WritebackWork* wo
 
     // Enqueue the first part of the transaction.
     size_t disk_start = start_block_ + 1;
-    entries_->AddTransaction(start, disk_start + start, first_length, work);
+    entries_->AddTransaction(start, disk_start + start, first_length, &work->Transaction());
 
     // If we wrapped around to the front of the journal,
     // enqueue a second transaction with the remaining data + commit block.
     if (first_length < length) {
-        entries_->AddTransaction(0, disk_start, length - first_length, work);
+        entries_->AddTransaction(0, disk_start, length - first_length, &work->Transaction());
     }
 }
 
diff --git a/zircon/system/ulib/blobfs/test/journal-test.cpp b/zircon/system/ulib/blobfs/test/journal-test.cpp
index 4b6e82a8a36aa53a6588894a5ed4cf1b63e2f9a9..1e48682e226dbf5a03978276bbe0fb045368f23f 100644
--- a/zircon/system/ulib/blobfs/test/journal-test.cpp
+++ b/zircon/system/ulib/blobfs/test/journal-test.cpp
@@ -38,8 +38,8 @@ public:
 
         zx::vmo vmo;
         ZX_ASSERT(zx::vmo::create(PAGE_SIZE, 0, &vmo) == ZX_OK);
-        work->Enqueue(vmo, 0, 0, block_count);
-        work->SetBuffer(2);
+        work->Transaction().Enqueue(vmo, 0, 0, block_count);
+        work->Transaction().SetBuffer(2);
         return work;
     }
 
diff --git a/zircon/system/ulib/blobfs/writeback-queue.cpp b/zircon/system/ulib/blobfs/writeback-queue.cpp
index 574d0e96cbf2850743205531a0eecefcf5e50cf3..28b1ee008030fbd9ebfd219ff4b2e68bba66b524 100644
--- a/zircon/system/ulib/blobfs/writeback-queue.cpp
+++ b/zircon/system/ulib/blobfs/writeback-queue.cpp
@@ -83,18 +83,18 @@ zx_status_t WritebackQueue::Enqueue(fbl::unique_ptr<WritebackWork> work) {
         // enqueued and ultimately processed by the WritebackThread. This will help us avoid
         // potential race conditions if the work callback must acquire a lock.
         status = ZX_ERR_BAD_STATE;
-    } else if (!work->IsBuffered()) {
+    } else if (!work->Transaction().IsBuffered()) {
         ZX_DEBUG_ASSERT(state_ == WritebackState::kRunning);
 
         // Only copy blocks to the buffer if they have not already been copied to another buffer.
-        EnsureSpaceLocked(work->BlkCount());
+        EnsureSpaceLocked(work->Transaction().BlkCount());
 
         // It is possible that the queue entered a read only state
         // while we were waiting to ensure space, so check again now.
         if (IsReadOnly()) {
             status = ZX_ERR_BAD_STATE;
         } else {
-            buffer_->CopyTransaction(work.get());
+            buffer_->CopyTransaction(&work->Transaction());
         }
     }
 
@@ -143,8 +143,8 @@ int WritebackQueue::WritebackThread(void* arg) {
             auto work = b->work_queue_.pop();
             TRACE_DURATION("blobfs", "WritebackQueue::WritebackThread", "work ptr", work.get());
 
-            bool our_buffer = b->buffer_->VerifyTransaction(work.get());
-            size_t blk_count = work->BlkCount();
+            bool our_buffer = b->buffer_->VerifyTransaction(&work->Transaction());
+            size_t blk_count = work->Transaction().BlkCount();
 
             // Stay unlocked while processing a unit of work.
             b->lock_.Release();
@@ -155,7 +155,7 @@ int WritebackQueue::WritebackThread(void* arg) {
             } else {
                 // If we should complete the work, make sure it has been buffered.
                 // (This is not necessary if we are currently in an error state).
-                ZX_DEBUG_ASSERT(work->IsBuffered());
+                ZX_DEBUG_ASSERT(work->Transaction().IsBuffered());
                 zx_status_t status;
                 if ((status = work->Complete()) != ZX_OK) {
                     FS_TRACE_ERROR("Work failed with status %d - "
diff --git a/zircon/system/ulib/blobfs/writeback-work.cpp b/zircon/system/ulib/blobfs/writeback-work.cpp
index f2365d2e015eaf051178378f442832b558c3194c..f0fc6378986f482245fb0cff50ed11ed5d1efe21 100644
--- a/zircon/system/ulib/blobfs/writeback-work.cpp
+++ b/zircon/system/ulib/blobfs/writeback-work.cpp
@@ -9,7 +9,7 @@
 namespace blobfs {
 
 void WritebackWork::MarkCompleted(zx_status_t status) {
-    WriteTxn::Reset();
+    transaction_.Reset();
     if (sync_cb_) {
         sync_cb_(status);
     }
@@ -51,12 +51,12 @@ void WritebackWork::SetSyncCallback(SyncCallback callback) {
 
 // Returns the number of blocks of the writeback buffer that have been consumed
 zx_status_t WritebackWork::Complete() {
-    zx_status_t status = Flush();
+    zx_status_t status = transaction_.Flush();
     MarkCompleted(status);
     return status;
 }
 
 WritebackWork::WritebackWork(TransactionManager* transaction_manager)
-    : WriteTxn(transaction_manager), ready_cb_(nullptr), sync_cb_(nullptr) {}
+    : transaction_(transaction_manager), ready_cb_(nullptr), sync_cb_(nullptr) {}
 
 } // namespace blobfs
diff --git a/zircon/system/ulib/blobfs/writeback.cpp b/zircon/system/ulib/blobfs/writeback.cpp
index 8ef56c7e0f27e62e4ae53d38bc07363b83390ba9..a4929ed409d7ced5cf78f933933479d734bcd9e8 100644
--- a/zircon/system/ulib/blobfs/writeback.cpp
+++ b/zircon/system/ulib/blobfs/writeback.cpp
@@ -15,7 +15,7 @@ zx_status_t EnqueuePaginated(fbl::unique_ptr<WritebackWork>* work,
     const size_t kMaxChunkBlocks = (3 * transaction_manager->WritebackCapacity()) / 4;
     uint64_t delta_blocks = fbl::min(nblocks, kMaxChunkBlocks);
     while (nblocks > 0) {
-        if ((*work)->BlkCount() + delta_blocks > kMaxChunkBlocks) {
+        if ((*work)->Transaction().BlkCount() + delta_blocks > kMaxChunkBlocks) {
             // If enqueueing these blocks could push us past the writeback buffer capacity
             // when combined with all previous writes, break this transaction into a smaller
             // chunk first.
@@ -31,7 +31,7 @@ zx_status_t EnqueuePaginated(fbl::unique_ptr<WritebackWork>* work,
             *work = std::move(tmp);
         }
 
-        (*work)->Enqueue(vmo, relative_block, absolute_block, delta_blocks);
+        (*work)->Transaction().Enqueue(vmo, relative_block, absolute_block, delta_blocks);
         relative_block += delta_blocks;
         absolute_block += delta_blocks;
         nblocks -= delta_blocks;