diff --git a/src/box/alter.cc b/src/box/alter.cc
index 19eba879634b120aa04319aadcd4e239038a96f4..412f19049c6dcac2cd77973946fdc9095b5c6b47 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -868,7 +868,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 ab3315298c75903f0b8b7f46b6f1bf1e0e0df7f3..551a7e1d413f499c25ea3e2843dafb62255180ac 100644
--- a/src/box/applier.cc
+++ b/src/box/applier.cc
@@ -1219,8 +1219,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 a476e8e39527cfd4a654fe55fef1fb1857841fd6..aa65b31adc8ee4a61ced16efc5a43d632031e66b 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -579,9 +579,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.
  */
@@ -590,13 +590,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);
@@ -604,15 +605,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 ed8c214df5b013e863f27d7de49211ee04652e28..d0fc7cc5d2e36ef86ef8d4aed276959f86b99154 100644
--- a/src/box/txn.c
+++ b/src/box/txn.c
@@ -357,7 +357,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");
 	}
@@ -710,7 +710,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");
 		}
@@ -739,7 +739,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 0750e5f786d75387ca6214d4fb3c17ed15c27860..de669fdc101db0e68d5297f0f60b0973b09aa968 100644
--- a/src/box/txn.h
+++ b/src/box/txn.h
@@ -909,6 +909,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 4e0669a26885c907e2fdb6ac640a5b916d08f6f9..6d6faf510e7e90e59ccad61c4b92ef1fd7eb84af 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -4395,7 +4395,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