diff --git a/src/box/alter.cc b/src/box/alter.cc
index afb02e80a1fff97efb4443baa0f7dec799e12fac..d6522f4046045c2441f2c597316eff53a887feb7 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -825,7 +825,8 @@ struct mh_i32_t *AlterSpaceLock::registry;
 static int
 alter_space_commit(struct trigger *trigger, void *event)
 {
-	struct txn *txn = (struct txn *) event;
+	(void)event;
+	struct txn *txn = in_txn();
 	struct alter_space *alter = (struct alter_space *) trigger->data;
 	/*
 	 * The engine (vinyl) expects us to pass the signature of
diff --git a/src/box/applier.cc b/src/box/applier.cc
index c721289a54c4463819efc920044762cad2a0287e..8d2875737e00b7756a58ee2e4b9bf05decd39348 100644
--- a/src/box/applier.cc
+++ b/src/box/applier.cc
@@ -1221,8 +1221,9 @@ applier_rollback_by_wal_io(int64_t signature)
 static int
 applier_txn_rollback_cb(struct trigger *trigger, void *event)
 {
-	(void) trigger;
-	struct txn *txn = (struct txn *) event;
+	(void)trigger;
+	(void)event;
+	struct txn *txn = in_txn();
 	/*
 	 * Synchronous transaction rollback due to receiving a
 	 * ROLLBACK entry is a normal event and requires no
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 7ae524fecc4c04f9e32805db03f10286814e772e..9aa25a7e741fc8263eba1a5d13f10c8429981804 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -485,9 +485,9 @@ lbox_txn_iterator_next(struct lua_State *L)
 }
 
 /**
- * Open an iterator over the transaction statements. This is a C
- * closure and 1 upvalue should be available - id of the
- * transaction to iterate over.
+ * Open an iterator over the transaction statements. This is a C closure and
+ * 2 upvalues should be available: first is an id of the transaction to iterate
+ * over, second is the first statement of the iteration.
  * It returns 3 values which can be used in Lua 'for': iterator
  * generator function, unused nil and the zero key.
  */
@@ -496,13 +496,14 @@ lbox_txn_pairs(struct lua_State *L)
 {
 	int64_t txn_id = luaL_toint64(L, lua_upvalueindex(1));
 	struct txn *txn = in_txn();
+	struct txn_stmt *stmt;
+	stmt = (struct txn_stmt *)lua_topointer(L, lua_upvalueindex(2));
 	if (txn == NULL || txn->id != txn_id) {
 		diag_set(ClientError, ER_CURSOR_NO_TRANSACTION);
 		return luaT_error(L);
 	}
 	luaL_pushint64(L, txn_id);
-	lua_pushlightuserdata(L, stailq_first_entry(&txn->stmts,
-						    struct txn_stmt, next));
+	lua_pushlightuserdata(L, stmt);
 	lua_pushcclosure(L, lbox_txn_iterator_next, 2);
 	lua_pushnil(L);
 	lua_pushinteger(L, 0);
@@ -510,15 +511,17 @@ lbox_txn_pairs(struct lua_State *L)
 }
 
 /**
- * Push an argument for on_commit Lua trigger. The argument is
+ * Push an argument for on_commit and on_rollback Lua triggers. The argument is
  * a function to open an iterator over the transaction statements.
  */
 static int
 lbox_push_txn(struct lua_State *L, void *event)
 {
-	struct txn *txn = (struct txn *) event;
+	struct txn *txn = in_txn();
+	struct txn_stmt *stmt = (struct txn_stmt *)event;
 	luaL_pushint64(L, txn->id);
-	lua_pushcclosure(L, lbox_txn_pairs, 1);
+	lua_pushlightuserdata(L, stmt);
+	lua_pushcclosure(L, lbox_txn_pairs, 2);
 	return 1;
 }
 
diff --git a/src/box/txn.c b/src/box/txn.c
index f409fff6b312c657743c864ff497e9cf7f62b50f..da7005b61c85067b4f3449c0d812cbdbf6a61847 100644
--- a/src/box/txn.c
+++ b/src/box/txn.c
@@ -355,7 +355,7 @@ txn_rollback_one_stmt(struct txn *txn, struct txn_stmt *stmt)
 {
 	if (txn->engine != NULL && stmt->space != NULL)
 		engine_rollback_statement(txn->engine, txn, stmt);
-	if (stmt->has_triggers && trigger_run(&stmt->on_rollback, txn) != 0) {
+	if (stmt->has_triggers && trigger_run(&stmt->on_rollback, stmt) != 0) {
 		diag_log();
 		panic("statement rollback trigger failed");
 	}
@@ -704,7 +704,7 @@ txn_complete_fail(struct txn *txn)
 	if (txn->engine != NULL)
 		engine_rollback(txn->engine, txn);
 	if (txn_has_flag(txn, TXN_HAS_TRIGGERS)) {
-		if (trigger_run(&txn->on_rollback, txn) != 0) {
+		if (trigger_run(&txn->on_rollback, txn_first_stmt(txn)) != 0) {
 			diag_log();
 			panic("transaction rollback trigger failed");
 		}
@@ -733,7 +733,8 @@ txn_complete_success(struct txn *txn)
 		 * so that a trigger sees the changes done by previous triggers
 		 * (this is vital for DDL).
 		 */
-		if (trigger_run_reverse(&txn->on_commit, txn) != 0) {
+		if (trigger_run_reverse(&txn->on_commit,
+					txn_first_stmt(txn)) != 0) {
 			diag_log();
 			panic("transaction commit trigger failed");
 		}
diff --git a/src/box/txn.h b/src/box/txn.h
index 03ef60a253605b2e5fd25a3491ce48923120f658..526453aa2e6583be453a4cd031342ff1ff009296 100644
--- a/src/box/txn.h
+++ b/src/box/txn.h
@@ -891,6 +891,13 @@ txn_is_first_statement(struct txn *txn)
 	return stailq_last(&txn->stmts) == stailq_first(&txn->stmts);
 }
 
+/** The first statement of the transaction. */
+static inline struct txn_stmt *
+txn_first_stmt(struct txn *txn)
+{
+	return stailq_first_entry(&txn->stmts, struct txn_stmt, next);
+}
+
 /** The current statement of the transaction. */
 static inline struct txn_stmt *
 txn_current_stmt(struct txn *txn)
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index f163ff660dcb1814546f3261c9f90f6a6f76a86e..135b3e030debb9c7df7b00b0e5edc49b2604fb26 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -4373,7 +4373,8 @@ vinyl_space_build_index(struct space *src_space, struct index *new_index,
 static int
 vy_deferred_delete_on_commit(struct trigger *trigger, void *event)
 {
-	struct txn *txn = event;
+	(void)event;
+	struct txn *txn = in_txn();
 	struct vy_mem *mem = trigger->data;
 	/*
 	 * Update dump_lsn so that we can skip dumped deferred