From aeac971f0b36b4521dbae902e98439fb3b2c8665 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=9B=D0=B5=D0=BE=D0=BD=D0=B8=D0=B4=20=D0=AE=D1=80=D1=8C?=
 =?UTF-8?q?=D0=B5=D0=B2=20=28Leonid=20Yuriev=29?= <leo@yuriev.ru>
Date: Sat, 15 Mar 2025 22:00:36 +0300
Subject: [PATCH] =?UTF-8?q?mdbx:=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0?=
 =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?=
 =?UTF-8?q?=D1=80=D0=BA=D0=B8=20=D0=BA=D1=83=D1=80=D1=81=D0=BE=D1=80=D0=BE?=
 =?UTF-8?q?=D0=B2=20=D0=BD=D0=B0=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B5=20API-?=
 =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B9=20=D1=81=20=D0=B4?=
 =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20?=
 =?UTF-8?q?`cursor=5Fcheck()`.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/api-cold.c           |   2 +-
 src/api-cursor.c         | 155 ++++++++++++---------------------------
 src/api-range-estimate.c |  34 ++++-----
 src/api-txn-data.c       |  57 ++++++--------
 src/api-txn.c            |  12 ++-
 src/cogs.h               |  14 +---
 src/cursor.c             |  30 ++++++++
 src/cursor.h             |  15 ++++
 src/txn.c                |   5 +-
 9 files changed, 149 insertions(+), 175 deletions(-)

diff --git a/src/api-cold.c b/src/api-cold.c
index 8a8c8588..57e1d667 100644
--- a/src/api-cold.c
+++ b/src/api-cold.c
@@ -141,7 +141,7 @@ __cold int mdbx_env_warmup(const MDBX_env *env, const MDBX_txn *txn, MDBX_warmup
     return LOG_IFERR(MDBX_EINVAL);
 
   if (txn) {
-    int err = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_ERROR);
+    int err = check_txn(txn, MDBX_TXN_FINISHED | MDBX_TXN_ERROR);
     if (unlikely(err != MDBX_SUCCESS))
       return LOG_IFERR(err);
   }
diff --git a/src/api-cursor.c b/src/api-cursor.c
index d4ce3beb..d5a71e5a 100644
--- a/src/api-cursor.c
+++ b/src/api-cursor.c
@@ -27,18 +27,12 @@ int mdbx_cursor_renew(MDBX_txn *txn, MDBX_cursor *mc) {
 }
 
 int mdbx_cursor_reset(MDBX_cursor *mc) {
-  if (unlikely(!mc))
-    return LOG_IFERR(MDBX_EINVAL);
+  int rc = cursor_check(mc, MDBX_TXN_FINISHED);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
-  if (likely(mc->signature == cur_signature_live)) {
-    cursor_reset((cursor_couple_t *)mc);
-    return MDBX_SUCCESS;
-  }
-
-  if (likely(mc->signature == cur_signature_ready4dispose))
-    return MDBX_SUCCESS;
-
-  return LOG_IFERR(MDBX_EBADSIGN);
+  cursor_reset((cursor_couple_t *)mc);
+  return MDBX_SUCCESS;
 }
 
 int mdbx_cursor_bind(MDBX_txn *txn, MDBX_cursor *mc, MDBX_dbi dbi) {
@@ -50,17 +44,17 @@ int mdbx_cursor_bind(MDBX_txn *txn, MDBX_cursor *mc, MDBX_dbi dbi) {
     return LOG_IFERR(rc);
   }
 
-  int rc = check_txn(txn, MDBX_TXN_BLOCKED);
-  if (unlikely(rc != MDBX_SUCCESS))
-    return LOG_IFERR(rc);
-
-  rc = dbi_check(txn, dbi);
+  int rc = check_txn(txn, MDBX_TXN_FINISHED | MDBX_TXN_HAS_CHILD);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
   if (unlikely(dbi == FREE_DBI && !(txn->flags & MDBX_TXN_RDONLY)))
     return LOG_IFERR(MDBX_EACCESS);
 
+  rc = dbi_check(txn, dbi);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
+
   if (unlikely(mc->backup)) /* Cursor from parent transaction */
     LOG_IFERR(MDBX_EINVAL);
 
@@ -200,12 +194,11 @@ int mdbx_cursor_close2(MDBX_cursor *mc) {
 }
 
 int mdbx_cursor_copy(const MDBX_cursor *src, MDBX_cursor *dest) {
-  if (unlikely(!src))
-    return LOG_IFERR(MDBX_EINVAL);
-  if (unlikely(src->signature != cur_signature_live))
-    return LOG_IFERR((src->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
+  int rc = cursor_check(src, MDBX_TXN_FINISHED | MDBX_TXN_HAS_CHILD);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
-  int rc = mdbx_cursor_bind(src->txn, dest, cursor_dbi(src));
+  rc = mdbx_cursor_bind(src->txn, dest, cursor_dbi(src));
   if (unlikely(rc != MDBX_SUCCESS))
     return rc;
 
@@ -278,15 +271,16 @@ int mdbx_txn_release_all_cursors_ex(const MDBX_txn *txn, bool unbind, size_t *co
 
 int mdbx_cursor_compare(const MDBX_cursor *l, const MDBX_cursor *r, bool ignore_multival) {
   const int incomparable = INT16_MAX + 1;
+
   if (unlikely(!l))
     return r ? -incomparable * 9 : 0;
   else if (unlikely(!r))
     return incomparable * 9;
 
-  if (unlikely(l->signature != cur_signature_live))
-    return (r->signature == cur_signature_live) ? -incomparable * 8 : 0;
-  if (unlikely(r->signature != cur_signature_live))
-    return (l->signature == cur_signature_live) ? incomparable * 8 : 0;
+  if (unlikely(cursor_check_pure(l) != MDBX_SUCCESS))
+    return (cursor_check_pure(r) == MDBX_SUCCESS) ? -incomparable * 8 : 0;
+  if (unlikely(cursor_check_pure(r) != MDBX_SUCCESS))
+    return (cursor_check_pure(l) == MDBX_SUCCESS) ? incomparable * 8 : 0;
 
   if (unlikely(l->clc != r->clc)) {
     if (l->txn->env != r->txn->env)
@@ -352,13 +346,7 @@ int mdbx_cursor_compare(const MDBX_cursor *l, const MDBX_cursor *r, bool ignore_
 }
 
 int mdbx_cursor_count_ex(const MDBX_cursor *mc, size_t *count, MDBX_stat *ns, size_t bytes) {
-  if (unlikely(mc == nullptr))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
-
-  int rc = check_txn(mc->txn, MDBX_TXN_BLOCKED);
+  int rc = cursor_check_ro(mc);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
@@ -408,11 +396,9 @@ int mdbx_cursor_count(const MDBX_cursor *mc, size_t *count) {
 }
 
 int mdbx_cursor_on_first(const MDBX_cursor *mc) {
-  if (unlikely(mc == nullptr))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
+  int rc = cursor_check_pure(mc);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   for (intptr_t i = 0; i <= mc->top; ++i) {
     if (mc->ki[i])
@@ -423,11 +409,9 @@ int mdbx_cursor_on_first(const MDBX_cursor *mc) {
 }
 
 int mdbx_cursor_on_first_dup(const MDBX_cursor *mc) {
-  if (unlikely(mc == nullptr))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
+  int rc = cursor_check_pure(mc);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   if (is_filled(mc) && mc->subcur) {
     mc = &mc->subcur->cursor;
@@ -441,11 +425,9 @@ int mdbx_cursor_on_first_dup(const MDBX_cursor *mc) {
 }
 
 int mdbx_cursor_on_last(const MDBX_cursor *mc) {
-  if (unlikely(mc == nullptr))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
+  int rc = cursor_check_pure(mc);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   for (intptr_t i = 0; i <= mc->top; ++i) {
     size_t nkeys = page_numkeys(mc->pg[i]);
@@ -457,11 +439,9 @@ int mdbx_cursor_on_last(const MDBX_cursor *mc) {
 }
 
 int mdbx_cursor_on_last_dup(const MDBX_cursor *mc) {
-  if (unlikely(mc == nullptr))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
+  int rc = cursor_check_pure(mc);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   if (is_filled(mc) && mc->subcur) {
     mc = &mc->subcur->cursor;
@@ -476,29 +456,18 @@ int mdbx_cursor_on_last_dup(const MDBX_cursor *mc) {
 }
 
 int mdbx_cursor_eof(const MDBX_cursor *mc) {
-  if (unlikely(mc == nullptr))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
+  int rc = cursor_check_pure(mc);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   return is_eof(mc) ? MDBX_RESULT_TRUE : MDBX_RESULT_FALSE;
 }
 
 int mdbx_cursor_get(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, MDBX_cursor_op op) {
-  if (unlikely(mc == nullptr))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
-
-  int rc = check_txn(mc->txn, MDBX_TXN_BLOCKED);
+  int rc = cursor_check_ro(mc);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
-  if (unlikely(cursor_dbi_changed(mc)))
-    return LOG_IFERR(MDBX_BAD_DBI);
-
   return LOG_IFERR(cursor_ops(mc, key, data, op));
 }
 
@@ -623,19 +592,13 @@ int mdbx_cursor_get_batch(MDBX_cursor *mc, size_t *count, MDBX_val *pairs, size_
     return LOG_IFERR(MDBX_EINVAL);
 
   *count = 0;
-  if (unlikely(mc == nullptr || limit < 4 || limit > INTPTR_MAX - 2))
+  if (unlikely(limit < 4 || limit > INTPTR_MAX - 2))
     return LOG_IFERR(MDBX_EINVAL);
 
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
-
-  int rc = check_txn(mc->txn, MDBX_TXN_BLOCKED);
+  int rc = cursor_check_ro(mc);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
-  if (unlikely(cursor_dbi_changed(mc)))
-    return LOG_IFERR(MDBX_BAD_DBI);
-
   if (unlikely(mc->subcur))
     return LOG_IFERR(MDBX_INCOMPATIBLE) /* must be a non-dupsort table */;
 
@@ -704,11 +667,9 @@ bailout:
 /*----------------------------------------------------------------------------*/
 
 int mdbx_cursor_set_userctx(MDBX_cursor *mc, void *ctx) {
-  if (unlikely(!mc))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_ready4dispose && mc->signature != cur_signature_live))
-    return LOG_IFERR(MDBX_EBADSIGN);
+  int rc = cursor_check(mc, 0);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   cursor_couple_t *couple = container_of(mc, cursor_couple_t, outer);
   couple->userctx = ctx;
@@ -746,21 +707,13 @@ MDBX_dbi mdbx_cursor_dbi(const MDBX_cursor *mc) {
 /*----------------------------------------------------------------------------*/
 
 int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, MDBX_put_flags_t flags) {
-  if (unlikely(mc == nullptr || key == nullptr || data == nullptr))
+  if (unlikely(key == nullptr || data == nullptr))
     return LOG_IFERR(MDBX_EINVAL);
 
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
-
-  int rc = check_txn_rw(mc->txn, MDBX_TXN_BLOCKED);
+  int rc = cursor_check_rw(mc);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
-  if (unlikely(cursor_dbi_changed(mc)))
-    return LOG_IFERR(MDBX_BAD_DBI);
-
-  cASSERT(mc, cursor_is_tracked(mc));
-
   /* Check this first so counter will always be zero on any early failures. */
   if (unlikely(flags & MDBX_MULTIPLE)) {
     if (unlikely(flags & MDBX_RESERVE))
@@ -785,35 +738,21 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data, MDBX_p
     data->iov_base = nullptr;
   }
 
-  if (unlikely(mc->txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)))
-    return LOG_IFERR((mc->txn->flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN);
-
   return LOG_IFERR(cursor_put_checklen(mc, key, data, flags));
 }
 
 int mdbx_cursor_del(MDBX_cursor *mc, MDBX_put_flags_t flags) {
-  if (unlikely(!mc))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
-
-  int rc = check_txn_rw(mc->txn, MDBX_TXN_BLOCKED);
+  int rc = cursor_check_rw(mc);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
-  if (unlikely(cursor_dbi_changed(mc)))
-    return LOG_IFERR(MDBX_BAD_DBI);
-
   return LOG_IFERR(cursor_del(mc, flags));
 }
 
 __cold int mdbx_cursor_ignord(MDBX_cursor *mc) {
-  if (unlikely(!mc))
-    return LOG_IFERR(MDBX_EINVAL);
-
-  if (unlikely(mc->signature != cur_signature_live))
-    return LOG_IFERR((mc->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
+  int rc = cursor_check(mc, 0);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   mc->checking |= z_ignord;
   if (mc->subcur)
diff --git a/src/api-range-estimate.c b/src/api-range-estimate.c
index 5356d4da..56564da0 100644
--- a/src/api-range-estimate.c
+++ b/src/api-range-estimate.c
@@ -16,12 +16,6 @@ __hot static int cursor_diff(const MDBX_cursor *const __restrict x, const MDBX_c
   r->level = 0;
   r->root_nkeys = 0;
 
-  if (unlikely(x->signature != cur_signature_live))
-    return (x->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN;
-
-  if (unlikely(y->signature != cur_signature_live))
-    return (y->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN;
-
   int rc = check_txn(x->txn, MDBX_TXN_BLOCKED);
   if (unlikely(rc != MDBX_SUCCESS))
     return rc;
@@ -146,12 +140,20 @@ __hot static ptrdiff_t estimate(const tree_t *tree, diff_t *const __restrict dr)
  * Range-Estimation API */
 
 __hot int mdbx_estimate_distance(const MDBX_cursor *first, const MDBX_cursor *last, ptrdiff_t *distance_items) {
-  if (unlikely(first == nullptr || last == nullptr || distance_items == nullptr))
+  if (unlikely(!distance_items))
     return LOG_IFERR(MDBX_EINVAL);
 
+  int rc = cursor_check_pure(first);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
+
+  rc = cursor_check_pure(last);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
+
   *distance_items = 0;
   diff_t dr;
-  int rc = cursor_diff(last, first, &dr);
+  rc = cursor_diff(last, first, &dr);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
@@ -172,14 +174,10 @@ __hot int mdbx_estimate_distance(const MDBX_cursor *first, const MDBX_cursor *la
 
 __hot int mdbx_estimate_move(const MDBX_cursor *cursor, MDBX_val *key, MDBX_val *data, MDBX_cursor_op move_op,
                              ptrdiff_t *distance_items) {
-  if (unlikely(cursor == nullptr || distance_items == nullptr || move_op == MDBX_GET_CURRENT ||
-               move_op == MDBX_GET_MULTIPLE))
+  if (unlikely(!distance_items || move_op == MDBX_GET_CURRENT || move_op == MDBX_GET_MULTIPLE))
     return LOG_IFERR(MDBX_EINVAL);
 
-  if (unlikely(cursor->signature != cur_signature_live))
-    return LOG_IFERR((cursor->signature == cur_signature_ready4dispose) ? MDBX_EINVAL : MDBX_EBADSIGN);
-
-  int rc = check_txn(cursor->txn, MDBX_TXN_BLOCKED);
+  int rc = cursor_check_ro(cursor);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
@@ -232,10 +230,6 @@ __hot int mdbx_estimate_move(const MDBX_cursor *cursor, MDBX_val *key, MDBX_val
 
 __hot int mdbx_estimate_range(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *begin_key, const MDBX_val *begin_data,
                               const MDBX_val *end_key, const MDBX_val *end_data, ptrdiff_t *size_items) {
-  int rc = check_txn(txn, MDBX_TXN_BLOCKED);
-  if (unlikely(rc != MDBX_SUCCESS))
-    return LOG_IFERR(rc);
-
   if (unlikely(!size_items))
     return LOG_IFERR(MDBX_EINVAL);
 
@@ -248,6 +242,10 @@ __hot int mdbx_estimate_range(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val
   if (unlikely(begin_key == MDBX_EPSILON && end_key == MDBX_EPSILON))
     return LOG_IFERR(MDBX_EINVAL);
 
+  int rc = check_txn(txn, MDBX_TXN_BLOCKED);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
+
   cursor_couple_t begin;
   /* LY: first, initialize cursor to refresh a DB in case it have DB_STALE */
   rc = cursor_init(&begin.outer, txn, dbi);
diff --git a/src/api-txn-data.c b/src/api-txn-data.c
index 76bdf32a..2be54df8 100644
--- a/src/api-txn-data.c
+++ b/src/api-txn-data.c
@@ -51,15 +51,15 @@ __cold int mdbx_dbi_dupsort_depthmask(const MDBX_txn *txn, MDBX_dbi dbi, uint32_
 }
 
 int mdbx_canary_get(const MDBX_txn *txn, MDBX_canary *canary) {
-  int rc = check_txn(txn, MDBX_TXN_BLOCKED);
+  if (unlikely(canary == nullptr))
+    return LOG_IFERR(MDBX_EINVAL);
+
+  int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
   if (unlikely(rc != MDBX_SUCCESS)) {
     memset(canary, 0, sizeof(*canary));
     return LOG_IFERR(rc);
   }
 
-  if (unlikely(canary == nullptr))
-    return LOG_IFERR(MDBX_EINVAL);
-
   *canary = txn->canary;
   return MDBX_SUCCESS;
 }
@@ -68,13 +68,13 @@ int mdbx_get(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *d
   DKBUF_DEBUG;
   DEBUG("===> get db %u key [%s]", dbi, DKEY_DEBUG(key));
 
+  if (unlikely(!key || !data))
+    return LOG_IFERR(MDBX_EINVAL);
+
   int rc = check_txn(txn, MDBX_TXN_BLOCKED);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
-  if (unlikely(!key || !data))
-    return LOG_IFERR(MDBX_EINVAL);
-
   cursor_couple_t cx;
   rc = cursor_init(&cx.outer, txn, dbi);
   if (unlikely(rc != MDBX_SUCCESS))
@@ -84,15 +84,12 @@ int mdbx_get(const MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *d
 }
 
 int mdbx_get_equal_or_great(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data) {
-  int rc = check_txn(txn, MDBX_TXN_BLOCKED);
-  if (unlikely(rc != MDBX_SUCCESS))
-    return LOG_IFERR(rc);
-
   if (unlikely(!key || !data))
     return LOG_IFERR(MDBX_EINVAL);
 
-  if (unlikely(txn->flags & MDBX_TXN_BLOCKED))
-    return LOG_IFERR(MDBX_BAD_TXN);
+  int rc = check_txn(txn, MDBX_TXN_BLOCKED);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   cursor_couple_t cx;
   rc = cursor_init(&cx.outer, txn, dbi);
@@ -106,13 +103,13 @@ int mdbx_get_ex(const MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data
   DKBUF_DEBUG;
   DEBUG("===> get db %u key [%s]", dbi, DKEY_DEBUG(key));
 
+  if (unlikely(!key || !data))
+    return LOG_IFERR(MDBX_EINVAL);
+
   int rc = check_txn(txn, MDBX_TXN_BLOCKED);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
-  if (unlikely(!key || !data))
-    return LOG_IFERR(MDBX_EINVAL);
-
   cursor_couple_t cx;
   rc = cursor_init(&cx.outer, txn, dbi);
   if (unlikely(rc != MDBX_SUCCESS))
@@ -179,7 +176,7 @@ int mdbx_canary_put(MDBX_txn *txn, const MDBX_canary *canary) {
  * расположен в той-же странице памяти, в том числе для многостраничных
  * P_LARGE страниц с длинными данными. */
 int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) {
-  int rc = check_txn(txn, MDBX_TXN_BLOCKED);
+  int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
   if (unlikely(rc != MDBX_SUCCESS))
     return LOG_IFERR(rc);
 
@@ -215,18 +212,15 @@ int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) {
 }
 
 int mdbx_del(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, const MDBX_val *data) {
-  int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
-  if (unlikely(rc != MDBX_SUCCESS))
-    return LOG_IFERR(rc);
-
   if (unlikely(!key))
     return LOG_IFERR(MDBX_EINVAL);
 
   if (unlikely(dbi <= FREE_DBI))
     return LOG_IFERR(MDBX_BAD_DBI);
 
-  if (unlikely(txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)))
-    return LOG_IFERR((txn->flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN);
+  int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   cursor_couple_t cx;
   rc = cursor_init(&cx.outer, txn, dbi);
@@ -254,10 +248,6 @@ int mdbx_del(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, const MDBX_val *d
 }
 
 int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, MDBX_put_flags_t flags) {
-  int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
-  if (unlikely(rc != MDBX_SUCCESS))
-    return LOG_IFERR(rc);
-
   if (unlikely(!key || !data))
     return LOG_IFERR(MDBX_EINVAL);
 
@@ -268,8 +258,9 @@ int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, M
                          MDBX_APPENDDUP | MDBX_CURRENT | MDBX_MULTIPLE)))
     return LOG_IFERR(MDBX_EINVAL);
 
-  if (unlikely(txn->flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)))
-    return LOG_IFERR((txn->flags & MDBX_TXN_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN);
+  int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
 
   cursor_couple_t cx;
   rc = cursor_init(&cx.outer, txn, dbi);
@@ -330,10 +321,6 @@ int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *data, M
 
 int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *new_data, MDBX_val *old_data,
                     MDBX_put_flags_t flags, MDBX_preserve_func preserver, void *preserver_context) {
-  int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
-  if (unlikely(rc != MDBX_SUCCESS))
-    return LOG_IFERR(rc);
-
   if (unlikely(!key || !old_data || old_data == new_data))
     return LOG_IFERR(MDBX_EINVAL);
 
@@ -350,6 +337,10 @@ int mdbx_replace_ex(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key, MDBX_val *
                          MDBX_APPENDDUP | MDBX_CURRENT)))
     return LOG_IFERR(MDBX_EINVAL);
 
+  int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED);
+  if (unlikely(rc != MDBX_SUCCESS))
+    return LOG_IFERR(rc);
+
   cursor_couple_t cx;
   rc = cursor_init(&cx.outer, txn, dbi);
   if (unlikely(rc != MDBX_SUCCESS))
diff --git a/src/api-txn.c b/src/api-txn.c
index d38c5b0e..04feabea 100644
--- a/src/api-txn.c
+++ b/src/api-txn.c
@@ -9,7 +9,7 @@ __attribute__((__no_sanitize_thread__, __noinline__))
 #endif
 int mdbx_txn_straggler(const MDBX_txn *txn, int *percent)
 {
-  int rc = check_txn(txn, MDBX_TXN_BLOCKED);
+  int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
   if (likely(rc == MDBX_SUCCESS))
     rc = check_env(txn->env, true);
   if (unlikely(rc != MDBX_SUCCESS))
@@ -217,9 +217,13 @@ int mdbx_txn_begin_ex(MDBX_env *env, MDBX_txn *parent, MDBX_txn_flags_t flags, M
   MDBX_txn *txn = nullptr;
   if (parent) {
     /* Nested transactions: Max 1 child, write txns only, no writemap */
-    rc = check_txn_rw(parent, MDBX_TXN_RDONLY | MDBX_WRITEMAP | MDBX_TXN_BLOCKED);
-    if (unlikely(rc != MDBX_SUCCESS)) {
-      if (rc == MDBX_BAD_TXN && (parent->flags & (MDBX_TXN_RDONLY | MDBX_TXN_BLOCKED)) == 0) {
+    rc = check_txn(parent, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
+    if (unlikely(rc != MDBX_SUCCESS))
+      return LOG_IFERR(rc);
+
+    if (unlikely(parent->flags & (MDBX_TXN_RDONLY | MDBX_WRITEMAP))) {
+      rc = MDBX_BAD_TXN;
+      if ((parent->flags & MDBX_TXN_RDONLY) == 0) {
         ERROR("%s mode is incompatible with nested transactions", "MDBX_WRITEMAP");
         rc = MDBX_INCOMPATIBLE;
       }
diff --git a/src/cogs.h b/src/cogs.h
index 7e814e7b..498e92b7 100644
--- a/src/cogs.h
+++ b/src/cogs.h
@@ -416,10 +416,11 @@ static __always_inline int check_txn(const MDBX_txn *txn, int bad_bits) {
       return MDBX_EPERM;
 
     if (unlikely(txn->flags & bad_bits)) {
+      if ((bad_bits & MDBX_TXN_RDONLY) && unlikely(txn->flags & MDBX_TXN_RDONLY))
+        return MDBX_EACCESS;
       if ((bad_bits & MDBX_TXN_PARKED) == 0)
         return MDBX_BAD_TXN;
-      else
-        return txn_check_badbits_parked(txn, bad_bits);
+      return txn_check_badbits_parked(txn, bad_bits);
     }
   }
 
@@ -437,14 +438,7 @@ static __always_inline int check_txn(const MDBX_txn *txn, int bad_bits) {
 }
 
 static inline int check_txn_rw(const MDBX_txn *txn, int bad_bits) {
-  int err = check_txn(txn, bad_bits & ~MDBX_TXN_PARKED);
-  if (unlikely(err))
-    return err;
-
-  if (unlikely(txn->flags & MDBX_TXN_RDONLY))
-    return MDBX_EACCESS;
-
-  return MDBX_SUCCESS;
+  return check_txn(txn, (bad_bits | MDBX_TXN_RDONLY) & ~MDBX_TXN_PARKED);
 }
 
 /*----------------------------------------------------------------------------*/
diff --git a/src/cursor.c b/src/cursor.c
index 43f192a2..99139572 100644
--- a/src/cursor.c
+++ b/src/cursor.c
@@ -2331,3 +2331,33 @@ __hot int cursor_ops(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, const MDBX_
     return MDBX_EINVAL;
   }
 }
+
+int cursor_check(const MDBX_cursor *mc, int txn_bad_bits) {
+  if (unlikely(mc == nullptr))
+    return MDBX_EINVAL;
+
+  if (unlikely(mc->signature != cur_signature_live)) {
+    if (mc->signature != cur_signature_ready4dispose)
+      return MDBX_EBADSIGN;
+    return (txn_bad_bits > MDBX_TXN_FINISHED) ? MDBX_EINVAL : MDBX_SUCCESS;
+  }
+
+  /* проверяем что курсор в связном списке для отслеживания, исключение допускается только для read-only операций для
+   * служебных/временных курсоров на стеке. */
+  MDBX_MAYBE_UNUSED char stack_top[sizeof(void *)];
+  cASSERT(mc, cursor_is_tracked(mc) || (!(txn_bad_bits & MDBX_TXN_RDONLY) && stack_top < (char *)mc &&
+                                        (char *)mc - stack_top < (ptrdiff_t)globals.sys_pagesize * 4));
+
+  if (txn_bad_bits) {
+    int rc = check_txn(mc->txn, txn_bad_bits);
+    if (unlikely(rc != MDBX_SUCCESS)) {
+      cASSERT(mc, rc != MDBX_RESULT_TRUE);
+      return rc;
+    }
+
+    if (unlikely(cursor_dbi_changed(mc)))
+      return MDBX_BAD_DBI;
+  }
+
+  return MDBX_SUCCESS;
+}
diff --git a/src/cursor.h b/src/cursor.h
index f9d4ea82..d8100999 100644
--- a/src/cursor.h
+++ b/src/cursor.h
@@ -292,6 +292,21 @@ MDBX_NOTHROW_PURE_FUNCTION static inline bool check_leaf_type(const MDBX_cursor
   return (((page_type(mp) ^ mc->checking) & (z_branch | z_leaf | z_largepage | z_dupfix)) == 0);
 }
 
+MDBX_INTERNAL int cursor_check(const MDBX_cursor *mc, int txn_bad_bits);
+
+/* без необходимости доступа к данным, без активации припаркованных транзакций. */
+static inline int cursor_check_pure(const MDBX_cursor *mc) {
+  return cursor_check(mc, MDBX_TXN_BLOCKED - MDBX_TXN_PARKED);
+}
+
+/* для чтения данных, с активацией припаркованных транзакций. */
+static inline int cursor_check_ro(const MDBX_cursor *mc) { return cursor_check(mc, MDBX_TXN_BLOCKED); }
+
+/* для записи данных. */
+static inline int cursor_check_rw(const MDBX_cursor *mc) {
+  return cursor_check(mc, (MDBX_TXN_BLOCKED - MDBX_TXN_PARKED) | MDBX_TXN_RDONLY);
+}
+
 MDBX_INTERNAL MDBX_cursor *cursor_eot(MDBX_cursor *cursor, MDBX_txn *txn);
 MDBX_INTERNAL int cursor_shadow(MDBX_cursor *cursor, MDBX_txn *nested, const size_t dbi);
 
diff --git a/src/txn.c b/src/txn.c
index 09c4a3d0..e2cc53f3 100644
--- a/src/txn.c
+++ b/src/txn.c
@@ -388,7 +388,10 @@ int txn_check_badbits_parked(const MDBX_txn *txn, int bad_bits) {
    *  - но при распарковке поломанные транзакции завершаются.
    *  - получается что транзакцию можно припарковать, потом поломать вызвав
    *    mdbx_txn_break(), но далее любое её использование приведет к завершению
-   *    при распарковке. */
+   *    при распарковке.
+   *
+   * Поэтому для припаркованных транзакций возвращается ошибка если не-включена
+   * авто-распарковка, либо есть другие плохие биты. */
   if ((txn->flags & (bad_bits | MDBX_TXN_AUTOUNPARK)) != (MDBX_TXN_PARKED | MDBX_TXN_AUTOUNPARK))
     return LOG_IFERR(MDBX_BAD_TXN);