diff --git a/cmd/integration/commands/reset_state.go b/cmd/integration/commands/reset_state.go
index 6325febbe863258ed74dff3650235eeb88f0bbd0..3c455f2969aa569f058588aa534a38ce5eec8c68 100644
--- a/cmd/integration/commands/reset_state.go
+++ b/cmd/integration/commands/reset_state.go
@@ -185,6 +185,7 @@ func copyCompact() error {
 	if err := env.Open(from, lmdb.Readonly, 0644); err != nil {
 		return err
 	}
+	_ = os.RemoveAll(to)
 	if err := os.MkdirAll(to, 0744); err != nil {
 		return fmt.Errorf("could not create dir: %s, %w", to, err)
 	}
diff --git a/cmd/state/py/dbutils.py b/cmd/state/py/dbutils.py
index 5b5af98091c65fe77b115d7d23ee75837e282062..0a068f01c41eb9d57fc384d9b0e8279523e7db31 100644
--- a/cmd/state/py/dbutils.py
+++ b/cmd/state/py/dbutils.py
@@ -14,7 +14,6 @@ IncarnationMapBucket = "incarnationMap".encode()
 AccountChangeSetBucket = "ACS".encode()
 StorageChangeSetBucket = "SCS".encode()
 IntermediateTrieHashBucket = "iTh".encode()
-IntermediateWitnessSizeBucket = "iws".encode()
 DatabaseInfoBucket = "DBINFO".encode()
 DatabaseVerisionKey = "DatabaseVersion".encode()
 HeadHeaderKey = "LastHeader".encode()
@@ -47,7 +46,7 @@ Senders = "txSenders".encode()
 
 # cat common/dbutils/bucket.go| grep '=' | grep byte | sed 's/\[\]byte(//' | sed 's/)//' | awk '{print $3}' | grep -v '//' | grep -v '='  | tr '\n' ','
 buckets = ["PLAIN-CST", "PLAIN-contractCode", "PLAIN-ACS", "PLAIN-SCS", "CST", "hAT", "hST", "CODE", "contractCode",
-           "incarnationMap", "ACS", "SCS", "iTh", "iws", "DBINFO", "DatabaseVersion", "LastHeader", "LastBlock",
+           "incarnationMap", "ACS", "SCS", "iTh", "DBINFO", "DatabaseVersion", "LastHeader", "LastBlock",
            "LastFast", "TrieSync", "h", "t", "n", "H", "b", "r", "l", "B", "secure-key-", "ethereum-config-", "iB",
            "iBshead", "LastPrunedBlock", "lastAppliedMigration", "smHistory",
            "SSP", "SSU", "clique-", "txSenders",]
diff --git a/common/dbutils/bucket.go b/common/dbutils/bucket.go
index 45f11d2634fbef3d15e07cfafed9937ff18c42f6..5fe68fc755bff03530f39b9568d6daaba652208f 100644
--- a/common/dbutils/bucket.go
+++ b/common/dbutils/bucket.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"sort"
 
+	"github.com/ledgerwatch/turbo-geth/common/debug"
 	"github.com/ledgerwatch/turbo-geth/metrics"
 )
 
@@ -155,7 +156,7 @@ var (
 
 // Buckets - list of all buckets. App will panic if some bucket is not in this list.
 // This list will be sorted in `init` method.
-// BucketsIndex - can be used to find index in sorted version of Buckets list by name
+// BucketsCfg - can be used to find index in sorted version of Buckets list by name
 var Buckets = [][]byte{
 	CurrentStateBucket,
 	AccountsHistoryBucket,
@@ -200,7 +201,34 @@ var Buckets = [][]byte{
 	Senders,
 }
 
-var BucketsIndex = map[string]int{}
+var BucketsCfg = map[string]*BucketConfigItem{}
+
+type BucketConfigItem struct {
+	ID         int
+	IsDupsort  bool
+	DupToLen   int
+	DupFromLen int
+}
+
+type dupSortConfigEntry struct {
+	Bucket  []byte
+	ID      int
+	FromLen int
+	ToLen   int
+}
+
+var dupSortConfig = []dupSortConfigEntry{
+	{
+		Bucket:  CurrentStateBucket,
+		ToLen:   40,
+		FromLen: 72,
+	},
+	{
+		Bucket:  PlainStateBucket,
+		ToLen:   28,
+		FromLen: 60,
+	},
+}
 
 func init() {
 	sort.SliceStable(Buckets, func(i, j int) bool {
@@ -208,6 +236,26 @@ func init() {
 	})
 
 	for i := range Buckets {
-		BucketsIndex[string(Buckets[i])] = i
+		BucketsCfg[string(Buckets[i])] = &BucketConfigItem{ID: i}
+
+		for _, cfg := range dupSortConfig {
+			if cfg.ID != i {
+				continue
+			}
+			bucketCfg, ok := BucketsCfg[string(cfg.Bucket)]
+			if !ok {
+				continue
+			}
+			cfg.FromLen = bucketCfg.DupFromLen
+			cfg.ToLen = bucketCfg.DupToLen
+
+			if bytes.Equal(cfg.Bucket, CurrentStateBucket) {
+				bucketCfg.IsDupsort = debug.IsHashedStateDupsortEnabled()
+			}
+			if bytes.Equal(cfg.Bucket, PlainStateBucket) {
+				bucketCfg.IsDupsort = debug.IsPlainStateDupsortEnabled()
+			}
+		}
 	}
+
 }
diff --git a/common/debug/experiments.go b/common/debug/experiments.go
index 9254f9948beb6ec49f10f42c36f3d5f6e22a7947..720db7c85eeb83c0ecfb8dbeb25d18f3d3c02380 100644
--- a/common/debug/experiments.go
+++ b/common/debug/experiments.go
@@ -54,11 +54,6 @@ func IsBlockCompressionEnabled() bool {
 	return compressBlocks
 }
 
-var (
-	trackWitnessSize       bool
-	getTrackWitnessSizeLen sync.Once
-)
-
 var (
 	testDB    string
 	getTestDB sync.Once
@@ -73,3 +68,27 @@ func TestDB() string {
 	})
 	return testDB
 }
+
+var (
+	dupsortPlain    bool
+	getDupsortPlain sync.Once
+)
+
+func IsPlainStateDupsortEnabled() bool {
+	getDupsortPlain.Do(func() {
+		_, dupsortPlain = os.LookupEnv("DUPSORT_PLAIN")
+	})
+	return dupsortPlain
+}
+
+var (
+	dupsortHashed    bool
+	getDupsortHashed sync.Once
+)
+
+func IsHashedStateDupsortEnabled() bool {
+	getDupsortHashed.Do(func() {
+		_, dupsortHashed = os.LookupEnv("DUPSORT_HASHED")
+	})
+	return dupsortHashed
+}
diff --git a/core/state/database_test.go b/core/state/database_test.go
index f8d1cdb838f63148d01fa69fa9096bec1ee981e2..ef4107fb6eb3321eb93304fcd196976329e7ef46 100644
--- a/core/state/database_test.go
+++ b/core/state/database_test.go
@@ -179,7 +179,7 @@ func TestCreate2Revive(t *testing.T) {
 	var check2 uint256.Int
 	st.GetState(create2address, &key2, &check2)
 	if check2.Uint64() != 0x42 {
-		t.Errorf("expected 0x42 in position 2, got: %x", check2)
+		t.Errorf("expected 0x42 in position 2, got: %x", check2.Uint64())
 	}
 
 	// BLOCK 3
@@ -214,7 +214,7 @@ func TestCreate2Revive(t *testing.T) {
 	var check4 uint256.Int
 	st.GetState(create2address, &key4, &check4)
 	if check4.Uint64() != 0x42 {
-		t.Errorf("expected 0x42 in position 4, got: %x", check4)
+		t.Errorf("expected 0x42 in position 4, got: %x", check4.Uint64())
 	}
 	// We expect number 0x0 in the position [2], because it is the block number 4
 	st.GetState(create2address, &key2, &check2)
diff --git a/ethdb/database_test.go b/ethdb/database_test.go
index 343c204aa058b9bbdd81ab4377eb6f2867da2487..ae561029d705ce5bbf1b1ce023963036e3a6b83f 100644
--- a/ethdb/database_test.go
+++ b/ethdb/database_test.go
@@ -83,22 +83,22 @@ func TestLMDB_PutGet(t *testing.T) {
 }
 
 func testPutGet(db MinDatabase, t *testing.T) {
-	for _, k := range testValues {
-		err := db.Put(testBucket, []byte(k), []byte{})
-		if err != nil {
-			t.Fatalf("put failed: %v", err)
-		}
-	}
-
-	for _, k := range testValues {
-		data, err := db.Get(testBucket, []byte(k))
-		if err != nil {
-			t.Fatalf("get failed: %v", err)
-		}
-		if len(data) != 0 {
-			t.Fatalf("get returned wrong result, got %q expected nil", string(data))
-		}
-	}
+	//for _, k := range testValues {
+	//	err := db.Put(testBucket, []byte(k), []byte{})
+	//	if err != nil {
+	//		t.Fatalf("put failed: %v", err)
+	//	}
+	//}
+	//
+	//for _, k := range testValues {
+	//	data, err := db.Get(testBucket, []byte(k))
+	//	if err != nil {
+	//		t.Fatalf("get failed: %v", err)
+	//	}
+	//	if len(data) != 0 {
+	//		t.Fatalf("get returned wrong result, got %q expected nil", string(data))
+	//	}
+	//}
 
 	_, err := db.Get(testBucket, []byte("non-exist-key"))
 	if err == nil {
@@ -150,7 +150,8 @@ func testPutGet(db MinDatabase, t *testing.T) {
 			t.Fatalf("get failed: %v", err)
 		}
 		if !bytes.Equal(data, []byte("?")) {
-			t.Fatalf("get returned wrong result, got %q expected ?", string(data))
+			fmt.Printf("Error: %s %s\n", v, data)
+			t.Fatalf("get returned wrong result, got %s expected ?", string(data))
 		}
 	}
 
diff --git a/ethdb/kv_abstract_test.go b/ethdb/kv_abstract_test.go
index 968fffa2430cbf87ab60c75b0f8f3f064da81bbc..99ed576367a1487e35fafc1ef5d5be1f425e6ae8 100644
--- a/ethdb/kv_abstract_test.go
+++ b/ethdb/kv_abstract_test.go
@@ -21,12 +21,22 @@ import (
 )
 
 func TestManagedTx(t *testing.T) {
-	writeDBs, readDBs, closeAll := setupDatabases()
-	defer closeAll()
+	defaultConfig := dbutils.BucketsCfg
+	defer func() {
+		dbutils.BucketsCfg = defaultConfig
+	}()
 
 	bucketID := 0
 	bucket1 := dbutils.Buckets[bucketID]
 	bucket2 := dbutils.Buckets[bucketID+1]
+	dbutils.BucketsCfg[string(bucket1)].IsDupsort = true
+	dbutils.BucketsCfg[string(bucket1)].DupFromLen = 6
+	dbutils.BucketsCfg[string(bucket1)].DupToLen = 4
+	dbutils.BucketsCfg[string(bucket2)].IsDupsort = false
+
+	writeDBs, readDBs, closeAll := setupDatabases()
+	defer closeAll()
+
 	ctx := context.Background()
 
 	for _, db := range writeDBs {
@@ -327,6 +337,34 @@ func testMultiCursor(t *testing.T, db ethdb.KV, bucket1, bucket2 []byte) {
 		assert.Equal(k1, k2)
 		assert.Equal(v1, v2)
 
+		k1, v1, err = c1.Next()
+		assert.NoError(err)
+		k2, v2, err = c2.Next()
+		assert.NoError(err)
+		assert.Equal(k1, k2)
+		assert.Equal(v1, v2)
+
+		k1, v1, err = c1.Seek([]byte{0})
+		assert.NoError(err)
+		k2, v2, err = c2.Seek([]byte{0})
+		assert.NoError(err)
+		assert.Equal(k1, k2)
+		assert.Equal(v1, v2)
+
+		k1, v1, err = c1.Seek([]byte{0, 0})
+		assert.NoError(err)
+		k2, v2, err = c2.Seek([]byte{0, 0})
+		assert.NoError(err)
+		assert.Equal(k1, k2)
+		assert.Equal(v1, v2)
+
+		k1, v1, err = c1.Seek([]byte{0, 0, 0, 0})
+		assert.NoError(err)
+		k2, v2, err = c2.Seek([]byte{0, 0, 0, 0})
+		assert.NoError(err)
+		assert.Equal(k1, k2)
+		assert.Equal(v1, v2)
+
 		k1, v1, err = c1.Next()
 		assert.NoError(err)
 		k2, v2, err = c2.Next()
@@ -365,6 +403,7 @@ func TestMultipleBuckets(t *testing.T) {
 				for i := uint8(0); i < 12; i++ {
 					require.NoError(t, b2.Put([]byte{i}, []byte{i}))
 				}
+
 				// delete from first bucket key 5, then will seek on it and expect to see key 6
 				if err := b.Delete([]byte{5}); err != nil {
 					return err
diff --git a/ethdb/kv_badger.go b/ethdb/kv_badger.go
index b55e46605cd5518f7e5fe3bc7c370f9dd5999c5f..16962950e5dda9fe385c63afb503d9418e109ef3 100644
--- a/ethdb/kv_badger.go
+++ b/ethdb/kv_badger.go
@@ -227,7 +227,7 @@ func (db *badgerKV) Update(ctx context.Context, f func(tx Tx) error) (err error)
 }
 
 func (tx *badgerTx) Bucket(name []byte) Bucket {
-	b := badgerBucket{tx: tx, nameLen: uint(len(name)), id: dbutils.BucketsIndex[string(name)]}
+	b := badgerBucket{tx: tx, nameLen: uint(len(name)), id: dbutils.BucketsCfg[string(name)].ID}
 	b.prefix = name
 	return b
 }
diff --git a/ethdb/kv_bolt.go b/ethdb/kv_bolt.go
index b5abcc07b55455e4119812f70b79f138a9dccff5..507f17df866418e0414ed1661d24362de80b5f79 100644
--- a/ethdb/kv_bolt.go
+++ b/ethdb/kv_bolt.go
@@ -314,7 +314,7 @@ func (tx *boltTx) Yield() {
 }
 
 func (tx *boltTx) Bucket(name []byte) Bucket {
-	b := boltBucket{tx: tx, nameLen: uint(len(name)), id: dbutils.BucketsIndex[string(name)]}
+	b := boltBucket{tx: tx, nameLen: uint(len(name)), id: dbutils.BucketsCfg[string(name)].ID}
 	b.bolt = tx.bolt.Bucket(name)
 	return b
 }
diff --git a/ethdb/kv_lmdb.go b/ethdb/kv_lmdb.go
index 6af7a18d3a46c0939589c12250adceac6975cd56..9be220303f33a5cee2ccb70679f46f26dc50c2d0 100644
--- a/ethdb/kv_lmdb.go
+++ b/ethdb/kv_lmdb.go
@@ -95,7 +95,7 @@ func (opts lmdbOpts) Open() (KV, error) {
 				if createErr != nil {
 					return createErr
 				}
-				db.buckets[dbutils.BucketsIndex[string(name)]] = dbi
+				db.buckets[dbutils.BucketsCfg[string(name)].ID] = dbi
 			}
 			return nil
 		}); err != nil {
@@ -127,7 +127,12 @@ func (opts lmdbOpts) Open() (KV, error) {
 
 func createBucket(tx *lmdb.Txn, db *LmdbKV, id int) error {
 	var flags uint = lmdb.Create
-	dbi, err := tx.OpenDBI(string(dbutils.Buckets[id]), flags)
+	name := string(dbutils.Buckets[id])
+	cfg := dbutils.BucketsCfg[name]
+	if cfg.IsDupsort {
+		flags |= lmdb.DupSort
+	}
+	dbi, err := tx.OpenDBI(name, flags)
 	if err != nil {
 		return err
 	}
@@ -191,8 +196,8 @@ func (db *LmdbKV) DiskSize(_ context.Context) (uint64, error) {
 }
 
 func (db *LmdbKV) dbi(bucket []byte) lmdb.DBI {
-	if id, ok := dbutils.BucketsIndex[string(bucket)]; ok {
-		return db.buckets[id]
+	if cfg, ok := dbutils.BucketsCfg[string(bucket)]; ok {
+		return db.buckets[cfg.ID]
 	}
 	panic(fmt.Errorf("unknown bucket: %s. add it to dbutils.Buckets", string(bucket)))
 }
@@ -231,9 +236,12 @@ type lmdbTx struct {
 }
 
 type lmdbBucket struct {
-	id  int
-	tx  *lmdbTx
-	dbi lmdb.DBI
+	id        int
+	isDupsort bool
+	dupFrom   int
+	dupTo     int
+	tx        *lmdbTx
+	dbi       lmdb.DBI
 }
 
 type LmdbCursor struct {
@@ -275,12 +283,12 @@ func (db *LmdbKV) Update(ctx context.Context, f func(tx Tx) error) (err error) {
 }
 
 func (tx *lmdbTx) Bucket(name []byte) Bucket {
-	id, ok := dbutils.BucketsIndex[string(name)]
+	cfg, ok := dbutils.BucketsCfg[string(name)]
 	if !ok {
 		panic(fmt.Errorf("unknown bucket: %s. add it to dbutils.Buckets", string(name)))
 	}
 
-	return &lmdbBucket{tx: tx, id: id, dbi: tx.db.buckets[id]}
+	return &lmdbBucket{tx: tx, id: cfg.ID, dbi: tx.db.buckets[cfg.ID], isDupsort: cfg.IsDupsort, dupFrom: cfg.DupFromLen, dupTo: cfg.DupToLen}
 }
 
 func (tx *lmdbTx) Commit(ctx context.Context) error {
@@ -301,6 +309,10 @@ func (tx *lmdbTx) Rollback() {
 	tx.tx.Abort()
 }
 
+func (tx *lmdbTx) get(dbi lmdb.DBI, key []byte) ([]byte, error) {
+	return tx.tx.Get(dbi, key)
+}
+
 func (tx *lmdbTx) closeCursors() {
 	for _, c := range tx.cursors {
 		if c != nil {
@@ -329,50 +341,56 @@ func (c *LmdbCursor) NoValues() NoValuesCursor {
 	return &lmdbNoValuesCursor{LmdbCursor: c}
 }
 
-func (b lmdbBucket) Get(key []byte) (val []byte, err error) {
-	val, err = b.tx.tx.Get(b.dbi, key)
+func (b lmdbBucket) Get(key []byte) ([]byte, error) {
+	if b.isDupsort {
+		return b.getDupSort(key)
+	}
+
+	val, err := b.tx.get(b.dbi, key)
 	if err != nil {
 		if lmdb.IsNotFound(err) {
 			return nil, nil
 		}
 		return nil, err
 	}
-	return val, err
+	return val, nil
 }
 
-func (b *lmdbBucket) Put(key []byte, value []byte) error {
-	select {
-	case <-b.tx.ctx.Done():
-		return b.tx.ctx.Err()
-	default:
+func (b lmdbBucket) getDupSort(key []byte) ([]byte, error) {
+	c := b.Cursor().(*LmdbCursor)
+	if err := c.initCursor(); err != nil {
+		return nil, err
 	}
-
-	if len(key) == 0 {
-		return fmt.Errorf("lmdb doesn't support empty keys. bucket: %s", dbutils.Buckets[b.id])
+	if len(key) == b.dupFrom {
+		_, v, err := c.dupBothRange(key[:b.dupTo], key[b.dupTo:])
+		if err != nil {
+			if lmdb.IsNotFound(err) {
+				return nil, nil
+			}
+			return nil, err
+		}
+		if !bytes.Equal(key[b.dupTo:], v[:b.dupFrom-b.dupTo]) {
+			return nil, nil
+		}
+		return v[b.dupFrom-b.dupTo:], nil
 	}
 
-	err := b.tx.tx.Put(b.dbi, key, value, 0)
+	val, err := b.tx.get(b.dbi, key)
 	if err != nil {
-		return fmt.Errorf("failed LmdbKV.Put: %w", err)
+		if lmdb.IsNotFound(err) {
+			return nil, nil
+		}
+		return nil, err
 	}
-	return nil
+	return val, nil
 }
 
-func (b *lmdbBucket) Delete(key []byte) error {
-	select {
-	case <-b.tx.ctx.Done():
-		return b.tx.ctx.Err()
-	default:
-	}
+func (b *lmdbBucket) Put(key []byte, value []byte) error {
+	return b.Cursor().Put(key, value)
+}
 
-	err := b.tx.tx.Del(b.dbi, key, nil)
-	if err != nil {
-		if lmdb.IsNotFound(err) {
-			return nil
-		}
-		return err
-	}
-	return err
+func (b *lmdbBucket) Delete(key []byte) error {
+	return b.Cursor().Delete(key)
 }
 
 func (b *lmdbBucket) Size() (uint64, error) {
@@ -434,16 +452,21 @@ func (c *LmdbCursor) Seek(seek []byte) (k, v []byte, err error) {
 		}
 	}
 
+	if c.bucket.isDupsort {
+		return c.seekDupSort(seek)
+	}
+
 	if len(seek) == 0 {
 		k, v, err = c.cursor.Get(nil, nil, lmdb.First)
 	} else {
-		k, v, err = c.cursor.Get(seek, nil, lmdb.SetRange)
+		k, v, err = c.setRange(seek)
 	}
 	if err != nil {
 		if lmdb.IsNotFound(err) {
 			return nil, nil, nil
 		}
-		return []byte{}, nil, fmt.Errorf("failed LmdbKV cursor.Seek(): %w, key: %x", err, seek)
+		err = fmt.Errorf("failed LmdbKV cursor.Seek(): %w, bucket: %d %s, isDupsort: %t, key: %x", err, c.bucket.id, dbutils.Buckets[c.bucket.id], c.bucket.isDupsort, seek)
+		return []byte{}, nil, err
 	}
 	if c.prefix != nil && !bytes.HasPrefix(k, c.prefix) {
 		k, v = nil, nil
@@ -452,6 +475,65 @@ func (c *LmdbCursor) Seek(seek []byte) (k, v []byte, err error) {
 	return k, v, nil
 }
 
+func (c *LmdbCursor) seekDupSort(seek []byte) (k, v []byte, err error) {
+	b := c.bucket
+	if len(seek) == 0 {
+		k, v, err = c.cursor.Get(nil, nil, lmdb.First)
+		if err != nil {
+			if lmdb.IsNotFound(err) {
+				return nil, nil, nil
+			}
+			return []byte{}, nil, err
+		}
+		if c.prefix != nil && !bytes.HasPrefix(k, c.prefix) {
+			k, v = nil, nil
+		}
+		return k, v, nil
+	}
+
+	var seek1, seek2 []byte
+	if len(seek) > b.dupTo {
+		seek1, seek2 = seek[:b.dupTo], seek[b.dupTo:]
+	} else {
+		seek1 = seek
+	}
+	k, v, err = c.setRange(seek1)
+	if err != nil {
+		if lmdb.IsNotFound(err) {
+			return nil, nil, nil
+		}
+
+		return []byte{}, nil, err
+	}
+
+	if seek2 != nil && bytes.Equal(seek1, k) {
+		k, v, err = c.dupBothRange(seek1, seek2)
+		if err != nil && lmdb.IsNotFound(err) {
+			k, v, err = c.next()
+			if err != nil {
+				if lmdb.IsNotFound(err) {
+					return nil, nil, nil
+				}
+				return []byte{}, nil, err
+			}
+		} else if err != nil {
+			return []byte{}, nil, err
+		}
+	}
+
+	if len(k) == b.dupTo {
+		k2 := make([]byte, 0, len(k)+b.dupFrom-b.dupTo)
+		k2 = append(append(k2, k...), v[:b.dupFrom-b.dupTo]...)
+		v = v[b.dupFrom-b.dupTo:]
+		k = k2
+	}
+
+	if c.prefix != nil && !bytes.HasPrefix(k, c.prefix) {
+		k, v = nil, nil
+	}
+	return k, v, nil
+}
+
 func (c *LmdbCursor) SeekTo(seek []byte) ([]byte, []byte, error) {
 	return c.Seek(seek)
 }
@@ -463,6 +545,10 @@ func (c *LmdbCursor) Next() (k, v []byte, err error) {
 	default:
 	}
 
+	if c.bucket.isDupsort {
+		return c.nextDupSort()
+	}
+
 	k, v, err = c.cursor.Get(nil, nil, lmdb.Next)
 	if err != nil {
 		if lmdb.IsNotFound(err) {
@@ -477,6 +563,31 @@ func (c *LmdbCursor) Next() (k, v []byte, err error) {
 	return k, v, nil
 }
 
+func (c *LmdbCursor) nextDupSort() (k, v []byte, err error) {
+	b := c.bucket
+	k, v, err = c.cursor.Get(nil, nil, lmdb.NextDup)
+	if err != nil && lmdb.IsNotFound(err) {
+		k, v, err = c.cursor.Get(nil, nil, lmdb.Next)
+		if err != nil {
+			if lmdb.IsNotFound(err) {
+				return nil, nil, nil
+			}
+			return []byte{}, nil, fmt.Errorf("failed LmdbKV cursor.Next(): %w", err)
+		}
+	} else if err != nil {
+		return nil, nil, err
+	}
+	if len(k) == b.dupTo {
+		k = append(k, v[:b.dupFrom-b.dupTo]...)
+		v = v[b.dupFrom-b.dupTo:]
+	}
+
+	if c.prefix != nil && !bytes.HasPrefix(k, c.prefix) {
+		k, v = nil, nil
+	}
+	return k, v, nil
+}
+
 func (c *LmdbCursor) Delete(key []byte) error {
 	select {
 	case <-c.ctx.Done():
@@ -490,14 +601,50 @@ func (c *LmdbCursor) Delete(key []byte) error {
 		}
 	}
 
-	k, _, err := c.Seek(key)
+	if c.bucket.isDupsort {
+		return c.deleteDupSort(key)
+	}
+
+	_, _, err := c.cursor.Get(key, nil, lmdb.Set)
 	if err != nil {
+		if lmdb.IsNotFound(err) {
+			return nil
+		}
 		return err
 	}
 
-	if !bytes.Equal(k, key) {
-		return nil
+	return c.cursor.Del(0)
+}
+
+func (c *LmdbCursor) deleteDupSort(key []byte) error {
+	b := c.bucket
+	if len(key) != b.dupFrom && len(key) >= b.dupTo {
+		return fmt.Errorf("dupsort bucket: %s, can have keys of len==%d and len<%d. key: %x", dbutils.Buckets[b.id], b.dupFrom, b.dupTo, key)
+	}
+
+	if len(key) == b.dupFrom {
+		b := c.bucket
+		_, v, err := c.cursor.Get(key[:b.dupTo], key[b.dupTo:], lmdb.GetBothRange)
+		if err != nil { // if key not found, or found another one - then nothing to delete
+			if lmdb.IsNotFound(err) {
+				return nil
+			}
+			return err
+		}
+		if !bytes.Equal(v[:b.dupFrom-b.dupTo], key[b.dupTo:]) {
+			return nil
+		}
+		return c.cursor.Del(0)
 	}
+
+	_, _, err := c.cursor.Get(key, nil, lmdb.Set)
+	if err != nil {
+		if lmdb.IsNotFound(err) {
+			return nil
+		}
+		return err
+	}
+
 	return c.cursor.Del(0)
 }
 
@@ -517,9 +664,87 @@ func (c *LmdbCursor) Put(key []byte, value []byte) error {
 		}
 	}
 
+	if c.bucket.isDupsort {
+		return c.putDupSort(key, value)
+	}
+
+	return c.put(key, value)
+}
+
+func (c *LmdbCursor) putDupSort(key []byte, value []byte) error {
+	b := c.bucket
+	if len(key) != b.dupFrom && len(key) >= b.dupTo {
+		return fmt.Errorf("dupsort bucket: %s, can have keys of len==%d and len<%d. key: %x", dbutils.Buckets[b.id], b.dupFrom, b.dupTo, key)
+	}
+
+	if len(key) != b.dupFrom {
+		_, _, err := c.setExact(key)
+		if err != nil {
+			if lmdb.IsNotFound(err) {
+				return c.put(key, value)
+			}
+			return err
+		}
+
+		return c.putCurrent(key, value)
+	}
+
+	newValue := make([]byte, 0, b.dupFrom-b.dupTo+len(value))
+	newValue = append(append(newValue, key[b.dupTo:]...), value...)
+
+	key = key[:b.dupTo]
+	_, v, err := c.dupBothRange(key, newValue[:b.dupFrom-b.dupTo])
+	if err != nil { // if key not found, or found another one - then just insert
+		if lmdb.IsNotFound(err) {
+			return c.put(key, newValue)
+		}
+		return err
+	}
+
+	if bytes.Equal(v[:b.dupFrom-b.dupTo], newValue[:b.dupFrom-b.dupTo]) {
+		if len(v) == len(newValue) { // in DupSort case lmdb.Current works only with values of same length
+			return c.putCurrent(key, newValue)
+		}
+		err = c.delCurrent()
+		if err != nil {
+			return err
+		}
+		return c.put(key, newValue)
+	}
+
+	return c.put(key, newValue)
+}
+
+func (c *LmdbCursor) setExact(key []byte) ([]byte, []byte, error) {
+	return c.cursor.Get(key, nil, lmdb.Set)
+}
+func (c *LmdbCursor) setRange(key []byte) ([]byte, []byte, error) {
+	return c.cursor.Get(key, nil, lmdb.SetRange)
+}
+func (c *LmdbCursor) next() ([]byte, []byte, error) {
+	return c.cursor.Get(nil, nil, lmdb.Next)
+}
+
+func (c *LmdbCursor) dupBothRange(key []byte, value []byte) ([]byte, []byte, error) {
+	k, v, err := c.cursor.Get(key, value, lmdb.GetBothRange)
+	if err != nil {
+		return []byte{}, nil, err
+	}
+	return k, v, nil
+}
+
+func (c *LmdbCursor) delCurrent() error {
+	return c.cursor.Del(0)
+}
+
+func (c *LmdbCursor) put(key []byte, value []byte) error {
 	return c.cursor.Put(key, value, 0)
 }
 
+func (c *LmdbCursor) putCurrent(key []byte, value []byte) error {
+	return c.cursor.Put(key, value, lmdb.Current)
+}
+
 // Append - speedy feature of lmdb which is not part of KV interface.
 // Cast your cursor to *LmdbCursor to use this method.
 // Danger: if provided data will not sorted (or bucket have old records which mess with new in sorting manner) - db will corrupt.
@@ -533,7 +758,20 @@ func (c *LmdbCursor) Append(key []byte, value []byte) error {
 			return err
 		}
 	}
+	b := c.bucket
+	if b.isDupsort {
+		if len(key) != b.dupFrom && len(key) >= b.dupTo {
+			return fmt.Errorf("dupsort bucket: %s, can have keys of len==%d and len<%d. key: %x", dbutils.Buckets[b.id], b.dupFrom, b.dupTo, key)
+		}
 
+		if len(key) == b.dupFrom {
+			newValue := make([]byte, 0, b.dupFrom-b.dupTo+len(value))
+			newValue = append(append(newValue, key[b.dupTo:]...), value...)
+			key = key[:b.dupTo]
+			return c.cursor.Put(key, newValue, lmdb.AppendDup)
+		}
+		return c.cursor.Put(key, value, lmdb.Append)
+	}
 	return c.cursor.Put(key, value, lmdb.Append)
 }
 
@@ -590,7 +828,6 @@ func (c *lmdbNoValuesCursor) Seek(seek []byte) (k []byte, vSize uint32, err erro
 	if err != nil {
 		return []byte{}, 0, err
 	}
-
 	return k, uint32(len(v)), err
 }