x 1 месяц назад
Сommit
7fdf312f9c

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+Cargo.lock
+/target

+ 24 - 0
Cargo.toml

@@ -0,0 +1,24 @@
+[package]
+name = "kvdb-overlay"
+version = "0.1.0"
+homepage = "https://dark.fi"
+description = "Overlay mechanism for key-value embedded databases"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+repository = "https://codeberg.org/darkrenaissance/kvdb-overlay"
+license = "AGPL-3.0-only"
+edition = "2024"
+
+[dependencies]
+thiserror = "2.0.18"
+fjall = { version = "3.1", optional = true }
+sled = { version = "0.34", optional = true }
+tempfile = "3.20.0"
+
+[dev-dependencies]
+# To execute async tests
+smol = "2"
+
+[features]
+default = ["fjall-backend"]
+fjall-backend = ["fjall"]
+sled-backend = ["sled"]

+ 660 - 0
LICENSE.md

@@ -0,0 +1,660 @@
+### GNU AFFERO GENERAL PUBLIC LICENSE
+
+Version 3, 19 November 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc.
+<https://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+### Preamble
+
+The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains
+free software for all its users.
+
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing
+under this license.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+### TERMS AND CONDITIONS
+
+#### 0. Definitions.
+
+"This License" refers to version 3 of the GNU Affero General Public
+License.
+
+"Copyright" also means copyright-like laws that apply to other kinds
+of works, such as semiconductor masks.
+
+"The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of
+an exact copy. The resulting work is called a "modified version" of
+the earlier work or a work "based on" the earlier work.
+
+A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user
+through a computer network, with no transfer of a copy, is not
+conveying.
+
+An interactive user interface displays "Appropriate Legal Notices" to
+the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+#### 1. Source Code.
+
+The "source code" for a work means the preferred form of the work for
+making modifications to it. "Object code" means any non-source form of
+a work.
+
+A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can
+regenerate automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same
+work.
+
+#### 2. Basic Permissions.
+
+All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not convey,
+without conditions so long as your license otherwise remains in force.
+You may convey covered works to others for the sole purpose of having
+them make modifications exclusively for you, or provide you with
+facilities for running those works, provided that you comply with the
+terms of this License in conveying all material for which you do not
+control copyright. Those thus making or running the covered works for
+you must do so exclusively on your behalf, under your direction and
+control, on terms that prohibit them from making any copies of your
+copyrighted material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under the
+conditions stated below. Sublicensing is not allowed; section 10 makes
+it unnecessary.
+
+#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such
+circumvention is effected by exercising rights under this License with
+respect to the covered work, and you disclaim any intention to limit
+operation or modification of the work as a means of enforcing, against
+the work's users, your or third parties' legal rights to forbid
+circumvention of technological measures.
+
+#### 4. Conveying Verbatim Copies.
+
+You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+#### 5. Conveying Modified Source Versions.
+
+You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these
+conditions:
+
+-   a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+-   b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under
+    section 7. This requirement modifies the requirement in section 4
+    to "keep intact all notices".
+-   c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy. This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged. This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+-   d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+#### 6. Conveying Non-Source Forms.
+
+You may convey a covered work in object code form under the terms of
+sections 4 and 5, provided that you also convey the machine-readable
+Corresponding Source under the terms of this License, in one of these
+ways:
+
+-   a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+-   b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the Corresponding
+    Source from a network server at no charge.
+-   c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source. This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+-   d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge. You need not require recipients to copy the
+    Corresponding Source along with the object code. If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source. Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+-   e) Convey the object code using peer-to-peer transmission,
+    provided you inform other peers where the object code and
+    Corresponding Source of the work are being offered to the general
+    public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal,
+family, or household purposes, or (2) anything designed or sold for
+incorporation into a dwelling. In determining whether a product is a
+consumer product, doubtful cases shall be resolved in favor of
+coverage. For a particular product received by a particular user,
+"normally used" refers to a typical or common use of that class of
+product, regardless of the status of the particular user or of the way
+in which the particular user actually uses, or expects or is expected
+to use, the product. A product is a consumer product regardless of
+whether the product has substantial commercial, industrial or
+non-consumer uses, unless such uses represent the only significant
+mode of use of the product.
+
+"Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to
+install and execute modified versions of a covered work in that User
+Product from a modified version of its Corresponding Source. The
+information must suffice to ensure that the continued functioning of
+the modified object code is in no case prevented or interfered with
+solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or
+updates for a work that has been modified or installed by the
+recipient, or for the User Product in which it has been modified or
+installed. Access to a network may be denied when the modification
+itself materially and adversely affects the operation of the network
+or violates the rules and protocols for communication across the
+network.
+
+Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+#### 7. Additional Terms.
+
+"Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders
+of that material) supplement the terms of this License with terms:
+
+-   a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+-   b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+-   c) Prohibiting misrepresentation of the origin of that material,
+    or requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+-   d) Limiting the use for publicity purposes of names of licensors
+    or authors of the material; or
+-   e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+-   f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions
+    of it) with contractual assumptions of liability to the recipient,
+    for any liability that these contractual assumptions directly
+    impose on those licensors and authors.
+
+All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions; the
+above requirements apply either way.
+
+#### 8. Termination.
+
+You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+However, if you cease all violation of this License, then your license
+from a particular copyright holder is reinstated (a) provisionally,
+unless and until the copyright holder explicitly and finally
+terminates your license, and (b) permanently, if the copyright holder
+fails to notify you of the violation by some reasonable means prior to
+60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+#### 9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or run
+a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+#### 10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+#### 11. Patents.
+
+A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+A contributor's "essential patent claims" are all patent claims owned
+or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+A patent license is "discriminatory" if it does not include within the
+scope of its coverage, prohibits the exercise of, or is conditioned on
+the non-exercise of one or more of the rights that are specifically
+granted under this License. You may not convey a covered work if you
+are a party to an arrangement with a third party that is in the
+business of distributing software, under which you make payment to the
+third party based on the extent of your activity of conveying the
+work, and under which the third party grants, to any of the parties
+who would receive the covered work from you, a discriminatory patent
+license (a) in connection with copies of the covered work conveyed by
+you (or copies made from those copies), or (b) primarily for and in
+connection with specific products or compilations that contain the
+covered work, unless you entered into that arrangement, or that patent
+license was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+#### 12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under
+this License and any other pertinent obligations, then as a
+consequence you may not convey it at all. For example, if you agree to
+terms that obligate you to collect a royalty for further conveying
+from those to whom you convey the Program, the only way you could
+satisfy both those terms and this License would be to refrain entirely
+from conveying the Program.
+
+#### 13. Remote Network Interaction; Use with the GNU General Public License.
+
+Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your
+version supports such interaction) an opportunity to receive the
+Corresponding Source of your version by providing access to the
+Corresponding Source from a network server at no charge, through some
+standard or customary means of facilitating copying of software. This
+Corresponding Source shall include the Corresponding Source for any
+work covered by version 3 of the GNU General Public License that is
+incorporated pursuant to the following paragraph.
+
+Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+#### 14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions
+of the GNU Affero General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever
+published by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions
+of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+#### 15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
+WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
+PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
+DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+#### 16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
+CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
+NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
+LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
+TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
+PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+#### 17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+### How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively state
+the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+        <one line to give the program's name and a brief idea of what it does.>
+        Copyright (C) <year>  <name of author>
+
+        This program is free software: you can redistribute it and/or modify
+        it under the terms of the GNU Affero General Public License as
+        published by the Free Software Foundation, either version 3 of the
+        License, or (at your option) any later version.
+
+        This program is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+        GNU Affero General Public License for more details.
+
+        You should have received a copy of the GNU Affero General Public License
+        along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper
+mail.
+
+If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for
+the specific requirements.
+
+You should also get your employer (if you work as a programmer) or
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. For more information on this, and how to apply and follow
+the GNU AGPL, see <https://www.gnu.org/licenses/>.

+ 27 - 0
README.md

@@ -0,0 +1,27 @@
+kvdb-overlay
+============
+
+This Rust library serves as a minimal overlay mechanism for various
+key-value embedded databases.
+
+This mechanism enables us to simulate changes in a database/tree so
+that keys and values can be dynamically mutated, while avoiding having
+to change the underlying database. With this, we can perform changes to
+the trees and access the latest changes in-memory, and then only when
+we're satisfied with the results, we can actually atomically write it
+into the actual database.
+
+This functionality can also serve as a rollback-like mechanism for the
+database.
+
+Currently supported databases:
+
+* [sled](https://docs.rs/sled)
+* [fjall](https://docs.rs/fjall)
+
+Usage examples are offered in the repository as test units, and docs
+can be found on [docs.rs/kvdb-overlay](https://docs.rs/kvdb-overlay).
+
+## License
+
+GNU AGPLv3.

+ 51 - 0
src/batch.rs

@@ -0,0 +1,51 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::collections::BTreeMap;
+
+/// Struct representing a batch of updates we want to atomically write
+/// to the database. `None` value of a key indicates its removal.
+pub struct Batch {
+    pub(crate) writes: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
+}
+
+impl Batch {
+    pub fn new() -> Self {
+        Self {
+            writes: BTreeMap::new(),
+        }
+    }
+
+    pub fn insert(&mut self, key: &[u8], value: &[u8]) {
+        self.writes.insert(key.to_vec(), Some(value.to_vec()));
+    }
+
+    pub fn remove(&mut self, key: &[u8]) {
+        self.writes.insert(key.to_vec(), None);
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.writes.is_empty()
+    }
+}
+
+impl Default for Batch {
+    fn default() -> Self {
+        Self::new()
+    }
+}

+ 342 - 0
src/database.rs

@@ -0,0 +1,342 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::path::Path;
+
+use tempfile::{TempDir, tempdir};
+
+#[cfg(feature = "sled-backend")]
+use sled::{Transactional, transaction::ConflictableTransactionError};
+
+use crate::{Batch, Result, Tree, TreeOverlay, TreeOverlayStateDiff, tree::check_tree_name};
+
+/// Struct representing the abstraction over the supported key-value
+/// embedded databases.
+#[derive(Clone)]
+pub struct Database {
+    #[cfg(feature = "sled-backend")]
+    db: sled::Db,
+    #[cfg(feature = "fjall-backend")]
+    db: fjall::Database,
+}
+
+impl Database {
+    pub fn new(
+        #[cfg(feature = "sled-backend")] db: sled::Db,
+        #[cfg(feature = "fjall-backend")] db: fjall::Database,
+    ) -> Self {
+        Self { db }
+    }
+
+    /// Opens a Database with the default backend configuration at the
+    /// specified path.
+    pub fn open_default(path: &Path) -> Result<Self> {
+        let db = {
+            #[cfg(feature = "sled-backend")]
+            {
+                sled::open(path)?
+            }
+            #[cfg(feature = "fjall-backend")]
+            {
+                fjall::Database::builder(path).open()?
+            }
+        };
+        Ok(Self::new(db))
+    }
+
+    /// Opens a temporary Database with the default backend
+    /// configuration. Returned temp folder reference must outlive the
+    /// created Database.
+    pub fn open_temp() -> Result<(Self, TempDir)> {
+        let folder = tempdir()?;
+        Ok((Self::open_default(folder.path())?, folder))
+    }
+
+    /// Creates or opens a new database tree.
+    pub fn open_tree(
+        &self,
+        name: &str,
+        #[cfg(feature = "fjall-backend")] create_options: impl FnOnce() -> fjall::KeyspaceCreateOptions,
+    ) -> Result<Tree> {
+        check_tree_name(name)?;
+
+        let tree = {
+            #[cfg(feature = "sled-backend")]
+            {
+                self.db.open_tree(name)?
+            }
+            #[cfg(feature = "fjall-backend")]
+            {
+                self.db.keyspace(name, create_options)?
+            }
+        };
+        Ok(Tree::new(tree))
+    }
+
+    /// Creates or opens a new database tree with the default backend
+    /// configuration.
+    pub fn open_tree_default(&self, name: &str) -> Result<Tree> {
+        #[cfg(feature = "sled-backend")]
+        {
+            self.open_tree(name)
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            self.open_tree(name, fjall::KeyspaceCreateOptions::default)
+        }
+    }
+
+    /// Flush changes into disk.
+    pub fn flush(&self, #[cfg(feature = "fjall-backend")] mode: fjall::PersistMode) -> Result<()> {
+        #[cfg(feature = "sled-backend")]
+        self.db.flush()?;
+        #[cfg(feature = "fjall-backend")]
+        self.db.persist(mode)?;
+        Ok(())
+    }
+
+    /// Flush changes into disk asynchronously.
+    pub async fn flush_async(
+        &self,
+        #[cfg(feature = "fjall-backend")] mode: fjall::PersistMode,
+    ) -> Result<()> {
+        #[cfg(feature = "sled-backend")]
+        self.db.flush_async().await?;
+        #[cfg(feature = "fjall-backend")]
+        self.db.persist(mode)?;
+        Ok(())
+    }
+
+    /// Flush changes into disk using default persist mode.
+    pub fn flush_default_mode(&self) -> Result<()> {
+        #[cfg(feature = "sled-backend")]
+        self.flush()?;
+        #[cfg(feature = "fjall-backend")]
+        self.flush(fjall::PersistMode::SyncAll)?;
+        Ok(())
+    }
+
+    /// Flush changes into disk using default persist mode
+    /// asynchronously.
+    pub async fn flush_default_mode_async(&self) -> Result<()> {
+        #[cfg(feature = "sled-backend")]
+        self.flush_async().await?;
+        #[cfg(feature = "fjall-backend")]
+        self.flush_async(fjall::PersistMode::SyncAll).await?;
+        Ok(())
+    }
+
+    #[cfg(feature = "fjall-backend")]
+    /// Generate a batch over the whole `fjall` database.
+    pub fn fjall_batch(&self) -> fjall::OwnedWriteBatch {
+        self.db.batch()
+    }
+
+    /// Returns the trees names saved in the database.
+    pub fn tree_names(&self) -> Result<Vec<String>> {
+        #[cfg(feature = "sled-backend")]
+        {
+            let db_tree_names = self.db.tree_names();
+            let mut tree_names = Vec::with_capacity(db_tree_names.len() - 1);
+            for tree_name in db_tree_names.iter() {
+                // Skip default sled tree
+                if tree_name != b"__sled__default" {
+                    match String::from_utf8(tree_name.to_vec()) {
+                        Ok(s) => tree_names.push(s),
+                        Err(e) => return Err(crate::Error::MalformedTreeMame(e.to_string())),
+                    }
+                }
+            }
+            Ok(tree_names)
+        }
+        #[cfg(feature = "fjall-backend")]
+        Ok(self
+            .db
+            .list_keyspace_names()
+            .iter()
+            .map(|s| s.to_string())
+            .collect())
+    }
+
+    /// Removes the tree from the database.
+    pub fn drop_tree(&self, name: &str) -> Result<()> {
+        check_tree_name(name)?;
+        #[cfg(feature = "sled-backend")]
+        self.db.drop_tree(name)?;
+        #[cfg(feature = "fjall-backend")]
+        self.db.delete_keyspace(
+            self.db
+                .keyspace(name, fjall::KeyspaceCreateOptions::default)?,
+        )?;
+        Ok(())
+    }
+
+    /// Atomically write provided tree batches into the database.
+    pub fn atomic_write(&self, changes: &[(&Tree, &Batch)]) -> Result<()> {
+        #[cfg(feature = "sled-backend")]
+        {
+            // Grab all referenced trees
+            let mut trees = Vec::with_capacity(changes.len());
+            for (tree, _) in changes {
+                trees.push(tree.tree());
+            }
+
+            // Now we write them to sled
+            if let Err(e) = trees.transaction(|trees| {
+                for (i, tree) in trees.iter().enumerate() {
+                    // Build and apply its batch
+                    let mut sled_batch = sled::Batch::default();
+                    for (key, value) in &changes[i].1.writes {
+                        match value {
+                            Some(v) => sled_batch.insert(key.as_slice(), v.as_slice()),
+                            None => sled_batch.remove(key.as_slice()),
+                        }
+                    }
+                    tree.apply_batch(&sled_batch)?;
+                }
+
+                Ok::<(), ConflictableTransactionError<sled::Error>>(())
+            }) {
+                return Err(crate::Error::Transaction(e.to_string()));
+            };
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            // Grab a batch over the whole database
+            let mut fjall_batch = self.fjall_batch();
+
+            // Insert all changes into the batch
+            for (tree, batch) in changes {
+                for (key, value) in &batch.writes {
+                    match value {
+                        Some(v) => fjall_batch.insert(tree.tree(), key, v),
+                        None => fjall_batch.remove(tree.tree(), key),
+                    }
+                }
+            }
+
+            // Apply the batch
+            fjall_batch.commit()?;
+        }
+
+        Ok(())
+    }
+
+    /// Atomically write provided [`TreeOverlay`] slice changes into
+    /// the database.
+    pub fn write_tree_overlays_changes(&self, overlays: &[&TreeOverlay]) -> Result<()> {
+        #[cfg(feature = "sled-backend")]
+        {
+            // Grab all referenced trees
+            let mut trees = Vec::with_capacity(overlays.len());
+            for overlay in overlays {
+                trees.push(overlay.tree.tree());
+            }
+
+            // Now we write them to sled
+            if let Err(e) = trees.transaction(|trees| {
+                for (i, tree) in trees.iter().enumerate() {
+                    if let Some(batch) = &overlays[i].aggregate() {
+                        // Build and apply its batch
+                        let mut sled_batch = sled::Batch::default();
+                        for (key, value) in &batch.writes {
+                            match value {
+                                Some(v) => sled_batch.insert(key.as_slice(), v.as_slice()),
+                                None => sled_batch.remove(key.as_slice()),
+                            }
+                        }
+                        tree.apply_batch(&sled_batch)?;
+                    }
+                }
+
+                Ok::<(), ConflictableTransactionError<sled::Error>>(())
+            }) {
+                return Err(crate::Error::Transaction(e.to_string()));
+            };
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            // Grab a batch over the whole database
+            let mut fjall_batch = self.fjall_batch();
+
+            // Insert all overlays changes into the batch
+            for overlay in overlays {
+                if let Some(batch) = overlay.aggregate() {
+                    for (key, value) in batch.writes {
+                        match value {
+                            Some(v) => fjall_batch.insert(overlay.tree.tree(), key, v),
+                            None => fjall_batch.remove(overlay.tree.tree(), key),
+                        }
+                    }
+                }
+            }
+
+            // Apply the batch
+            fjall_batch.commit()?;
+        }
+
+        Ok(())
+    }
+
+    /// Write provided [`TreeOverlayStateDiff`] changes into the
+    /// database.
+    pub fn write_tree_overlays_diff(
+        &self,
+        tree: &Tree,
+        diff: &TreeOverlayStateDiff,
+        revert: bool,
+    ) -> Result<()> {
+        let batch = if revert {
+            diff.revert()
+        } else {
+            diff.aggregate()
+        };
+
+        let Some(batch) = batch else { return Ok(()) };
+
+        #[cfg(feature = "sled-backend")]
+        {
+            let mut sled_batch = sled::Batch::default();
+            for (key, value) in batch.writes {
+                match value {
+                    Some(v) => sled_batch.insert(key, v),
+                    None => sled_batch.remove(key),
+                }
+            }
+            tree.tree().apply_batch(sled_batch)?;
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            // Grab a batch over the whole database
+            let mut fjall_batch = self.fjall_batch();
+
+            // Insert all diff changes into the batch
+            for (key, value) in batch.writes {
+                match value {
+                    Some(v) => fjall_batch.insert(tree.tree(), key, v),
+                    None => fjall_batch.remove(tree.tree(), key),
+                }
+            }
+
+            // Apply the batch
+            fjall_batch.commit()?;
+        }
+
+        Ok(())
+    }
+}

+ 522 - 0
src/database_overlay.rs

@@ -0,0 +1,522 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::collections::BTreeMap;
+
+#[cfg(feature = "sled-backend")]
+use sled::{Transactional, transaction::ConflictableTransactionError};
+
+use crate::{
+    Batch, Database, DatabaseOverlayState, DatabaseOverlayStateDiff, Error, Result, Tree,
+    TreeOverlay, TreeOverlayIter, TreeOverlayStateDiff,
+};
+
+/// An overlay on top of an entire [`Database`] which can span multiple trees
+#[derive(Clone)]
+pub struct DatabaseOverlay {
+    /// The [`Database`] that is being overlayed.
+    db: Database,
+    /// Current overlay cache state
+    pub state: DatabaseOverlayState,
+    /// Checkpointed cache state to revert to
+    checkpoint: DatabaseOverlayState,
+}
+
+impl DatabaseOverlay {
+    /// Instantiate a new [`DatabaseOverlay`] on top of a given
+    /// [`Database`].
+    /// Note: Provided protected trees don't have to be opened as
+    /// protected, as they are setup as protected here.
+    pub fn new(db: &Database, protected_tree_names: Vec<String>) -> Result<Self> {
+        let initial_tree_names = db.tree_names()?;
+        Ok(Self {
+            db: db.clone(),
+            state: DatabaseOverlayState::new(
+                initial_tree_names.clone(),
+                protected_tree_names.clone(),
+            ),
+            checkpoint: DatabaseOverlayState::new(initial_tree_names, protected_tree_names),
+        })
+    }
+
+    /// Create a new [`TreeOverlay`] on top of a given `tree_name`.
+    /// This function will also open a new tree inside `database`
+    /// regardless of if it has existed before, so for convenience, we
+    /// also provide [`DatabaseOverlay::purge_new_trees`] in case we
+    /// decide we don't want to write the batches, and drop the new
+    /// trees. Additionally, a boolean flag is passed to mark the
+    /// oppened tree as protected, meanning that it can't be removed
+    /// and its references will never be dropped.
+    pub fn open_tree(
+        &mut self,
+        name: &str,
+        #[cfg(feature = "fjall-backend")] create_options: impl FnOnce() -> fjall::KeyspaceCreateOptions,
+        protected: bool,
+    ) -> Result<()> {
+        // Check if we have already opened this tree
+        if self.state.caches.contains_key(name) {
+            return Ok(());
+        }
+
+        // Open this tree in the database
+        let tree = self.db.open_tree(
+            name,
+            #[cfg(feature = "fjall-backend")]
+            create_options,
+        )?;
+        let mut cache = TreeOverlay::new(&tree);
+
+        // If we are reopenning a dropped tree, grab its cache
+        if let Some(diff) = self.state.dropped_trees.remove(name) {
+            cache.state = (&diff).into();
+        }
+
+        // In case it hasn't existed before, we also need to track it
+        // in `self.new_tree_names`.
+        let name = name.to_string();
+        if !self.state.initial_tree_names.contains(&name) {
+            self.state.new_tree_names.push(name.clone());
+        }
+
+        self.state.caches.insert(name.clone(), cache);
+
+        // Mark tree as protected if requested
+        if protected && !self.state.protected_tree_names.contains(&name) {
+            self.state.protected_tree_names.push(name);
+        }
+
+        Ok(())
+    }
+
+    /// Create a new [`TreeOverlay`] on top of a given `tree_name`.
+    /// This function will also open a new tree inside `database`,
+    /// using the default backend configuration, regardless of if it
+    /// has existed before, so for convenience, we also provide
+    /// [`DatabaseOverlay::purge_new_trees`] in case we decide we don't
+    /// want to write the batches, and drop the new trees.
+    /// Additionally, a boolean flag is passed to mark the oppened tree
+    /// as protected, meanning that it can't be removed and its
+    /// references will never be dropped.
+    pub fn open_tree_default(&mut self, name: &str, protected: bool) -> Result<()> {
+        #[cfg(feature = "sled-backend")]
+        {
+            self.open_tree(name, protected)
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            self.open_tree(name, fjall::KeyspaceCreateOptions::default, protected)
+        }
+    }
+
+    /// Drop a tree from the overlay.
+    pub fn drop_tree(&mut self, name: &str) -> Result<()> {
+        // Check if tree is protected
+        let name = name.to_string();
+        if self.state.protected_tree_names.contains(&name) {
+            return Err(Error::ProtectedTreeDrop(name));
+        }
+
+        // Check if already removed
+        if self.state.dropped_trees.contains_key(&name) {
+            return Err(Error::CollectionNotFound(name));
+        }
+
+        // Check if its a new tree we created
+        if self.state.new_tree_names.contains(&name) {
+            self.state.new_tree_names.retain(|x| x != &name);
+            let tree = match self.get_cache(&name) {
+                Ok(cache) => &cache.tree,
+                _ => &self.db.open_tree_default(&name)?,
+            };
+            let diff = TreeOverlayStateDiff::new_dropped(tree);
+            self.state.caches.remove(&name);
+            self.state.dropped_trees.insert(name, diff);
+
+            return Ok(());
+        }
+
+        // Check if tree existed in the database
+        if !self.state.initial_tree_names.contains(&name) {
+            return Err(Error::CollectionNotFound(name));
+        }
+
+        let tree = match self.get_cache(&name) {
+            Ok(cache) => &cache.tree,
+            _ => &self.db.open_tree_default(&name)?,
+        };
+        let diff = TreeOverlayStateDiff::new_dropped(tree);
+        self.state.caches.remove(&name);
+        self.state.dropped_trees.insert(name, diff);
+
+        Ok(())
+    }
+
+    /// Drop newly created trees from the database. This is a
+    /// convenience function that should be used when we decide that we
+    /// don't want to apply any cache changes, and we want to revert
+    /// back to the initial state.
+    pub fn purge_new_trees(&self) -> Result<()> {
+        for i in &self.state.new_tree_names {
+            self.db.drop_tree(i)?;
+        }
+
+        Ok(())
+    }
+
+    /// Fetch the cache for a given tree.
+    fn get_cache(&self, name: &str) -> Result<&TreeOverlay> {
+        let name = name.to_string();
+        if self.state.dropped_trees.contains_key(&name) {
+            return Err(Error::CollectionNotFound(name));
+        }
+
+        if let Some(v) = self.state.caches.get(&name) {
+            return Ok(v);
+        }
+
+        Err(Error::CollectionNotFound(name))
+    }
+
+    /// Fetch a mutable reference to the cache for a given tree.
+    fn get_cache_mut(&mut self, name: &str) -> Result<&mut TreeOverlay> {
+        let name = name.to_string();
+        if self.state.dropped_trees.contains_key(&name) {
+            return Err(Error::CollectionNotFound(name));
+        }
+
+        if let Some(v) = self.state.caches.get_mut(&name) {
+            return Ok(v);
+        }
+
+        Err(Error::CollectionNotFound(name))
+    }
+
+    /// Fetch all our caches current [`Tree`] pointers.
+    pub fn get_state_trees(&self) -> BTreeMap<String, Tree> {
+        // Grab our state tree pointers
+        let mut state_trees = BTreeMap::new();
+        for (name, cache) in self.state.caches.iter() {
+            state_trees.insert(name.clone(), cache.tree.clone());
+        }
+
+        state_trees
+    }
+
+    /// Returns `true` if the overlay contains a value for a specified
+    /// key in the specified tree cache.
+    pub fn contains_key(&self, name: &str, key: &[u8]) -> Result<bool> {
+        let cache = self.get_cache(name)?;
+        cache.contains_key(key)
+    }
+
+    /// Retrieve a value from the overlay if it exists in the specified
+    /// tree cache.
+    pub fn get(&self, name: &str, key: &[u8]) -> Result<Option<Vec<u8>>> {
+        let cache = self.get_cache(name)?;
+        cache.get(key)
+    }
+
+    /// Returns `true` if specified tree cache is empty.
+    pub fn is_empty(&self, name: &str) -> Result<bool> {
+        let cache = self.get_cache(name)?;
+        cache.is_empty()
+    }
+
+    /// Returns last value from the overlay if the specified tree cache
+    /// is not empty.
+    pub fn last(&self, name: &str) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
+        let cache = self.get_cache(name)?;
+        cache.last()
+    }
+
+    /// Insert a key to a new value in the specified tree cache,
+    /// returning the last value if it was set.
+    pub fn insert(&mut self, name: &str, key: &[u8], value: &[u8]) -> Result<Option<Vec<u8>>> {
+        let cache = self.get_cache_mut(name)?;
+        cache.insert(key, value)
+    }
+
+    /// Delete a value in the specified tree cache, returning the old
+    /// value if it existed.
+    pub fn remove(&mut self, name: &str, key: &[u8]) -> Result<Option<Vec<u8>>> {
+        let cache = self.get_cache_mut(name)?;
+        cache.remove(key)
+    }
+
+    /// Removes all values from the specified tree cache and marks all
+    /// its tree records as removed.
+    pub fn clear(&mut self, name: &str) -> Result<()> {
+        let cache = self.get_cache_mut(name)?;
+        cache.clear()
+    }
+
+    /// Aggregate all the current overlay changes into [`Batch`]
+    /// instances and return a vector of `Tree` and their respective
+    /// `Batch` that can be used for further operations. If there are
+    /// no changes, vector will be empty.
+    fn aggregate(&self) -> Result<Vec<(Tree, Batch)>> {
+        self.state.aggregate()
+    }
+
+    /// Ensure all new trees that have been opened exist in the
+    /// database by reopening them with the default backend
+    /// configuration, atomically apply all batches on all trees as a
+    /// transaction, and drop dropped trees from the database. This
+    /// function **does not** perform a db flush. This should be done
+    /// externally, since then there is a choice to perform either
+    /// blocking or async IO. After execution is successful, caller
+    /// should *NOT* use the overlay again.
+    pub fn apply(&mut self) -> Result<()> {
+        // Ensure new trees exist
+        let new_tree_names = self.state.new_tree_names.clone();
+        for tree_names in &new_tree_names {
+            let tree = self.db.open_tree_default(tree_names)?;
+            // Update cache tree pointer, it must exist
+            let cache = self.get_cache_mut(tree_names)?;
+            cache.tree = tree;
+        }
+
+        // Drop removed trees
+        for tree in self.state.dropped_trees.keys() {
+            self.db.drop_tree(tree)?;
+        }
+
+        // Aggregate batches
+        let batches = self.aggregate()?;
+        if batches.is_empty() {
+            return Ok(());
+        }
+
+        #[cfg(feature = "sled-backend")]
+        {
+            // Grab all referenced trees
+            let trees: Vec<&sled::Tree> = batches.iter().map(|(t, _)| t.tree()).collect();
+
+            // Perform an atomic transaction over all the collected trees and
+            // apply the batches.
+            if let Err(e) = trees.transaction(|trees| {
+                for (i, tree) in trees.iter().enumerate() {
+                    // Build and apply its batch
+                    let mut sled_batch = sled::Batch::default();
+                    for (key, value) in &batches[i].1.writes {
+                        match value {
+                            Some(v) => sled_batch.insert(key.as_slice(), v.as_slice()),
+                            None => sled_batch.remove(key.as_slice()),
+                        }
+                    }
+                    tree.apply_batch(&sled_batch)?;
+                }
+
+                Ok::<(), ConflictableTransactionError<sled::Error>>(())
+            }) {
+                return Err(Error::Transaction(e.to_string()));
+            };
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            // Grab a batch over the whole database
+            let mut fjall_batch = self.db.fjall_batch();
+
+            // Aggregate the overlay changes into the batch
+            for (tree, batch) in batches {
+                for (key, value) in batch.writes {
+                    match value {
+                        Some(v) => fjall_batch.insert(tree.tree(), key, v),
+                        None => fjall_batch.remove(tree.tree(), key),
+                    }
+                }
+            }
+
+            // Apply the batch
+            fjall_batch.commit()?;
+        }
+
+        Ok(())
+    }
+
+    /// Checkpoint current cache state so we can revert to it, if
+    /// needed.
+    pub fn checkpoint(&mut self) {
+        self.checkpoint = self.state.clone();
+    }
+
+    /// Revert to current cache state checkpoint. This function will
+    /// not drop new trees from the `db`, so caller should handle it.
+    pub fn revert_to_checkpoint(&mut self) {
+        self.state = self.checkpoint.clone();
+    }
+
+    /// Calculate differences from provided overlay state changes
+    /// sequence. This can be used when we want to keep track of
+    /// consecutive individual changes performed over the current
+    /// overlay state. If the sequence is empty, current state
+    /// is returned as the diff.
+    pub fn diff(&self, sequence: &[DatabaseOverlayStateDiff]) -> Result<DatabaseOverlayStateDiff> {
+        // Grab current state
+        let mut current = DatabaseOverlayStateDiff::new(&self.state)?;
+
+        // Remove provided diffs sequence
+        for diff in sequence {
+            current.remove_diff(diff);
+        }
+
+        Ok(current)
+    }
+
+    /// Add provided `db` overlay state changes from our own.
+    pub fn add_diff(&mut self, diff: &DatabaseOverlayStateDiff) -> Result<()> {
+        self.state.add_diff(&self.db, diff)
+    }
+
+    /// Remove provided `db` overlay state changes from our own.
+    pub fn remove_diff(&mut self, diff: &DatabaseOverlayStateDiff) {
+        self.state.remove_diff(diff)
+    }
+
+    /// For a provided `DatabaseOverlayStateDiff`, ensure all trees
+    /// exist in the database by reopening them with the default
+    /// backend configuration, atomically apply all batches on all
+    /// trees as a transaction, and drop dropped trees from the
+    /// database. After that, remove the state changes from our own.
+    /// This is will also mutate the initial trees, based on what was
+    /// oppened and/or dropped. This function **does not** perform a db
+    /// flush. This should be done externally, since then there is a
+    /// choice to perform either blocking or async IO.
+    pub fn apply_diff(&mut self, diff: &DatabaseOverlayStateDiff) -> Result<()> {
+        // We assert that the diff doesn't try to drop any of our
+        // protected trees.
+        for name in diff.dropped_trees.keys() {
+            if self.state.protected_tree_names.contains(name) {
+                return Err(Error::ProtectedTreeDrop(name.clone()));
+            }
+        }
+        for (name, (_, drop)) in diff.caches.iter() {
+            if *drop && self.state.protected_tree_names.contains(name) {
+                return Err(Error::ProtectedTreeDrop(name.clone()));
+            }
+        }
+
+        // Grab current state trees
+        let mut state_trees = self.get_state_trees();
+
+        // Ensure diff trees exist
+        for (name, (_, drop)) in diff.caches.iter() {
+            // Check if its an unknown tree
+            if !self.state.initial_tree_names.contains(name)
+                && !self.state.new_tree_names.contains(name)
+            {
+                self.state.new_tree_names.push(name.clone());
+            }
+
+            // Check if it should be dropped
+            if *drop {
+                self.db.drop_tree(name)?;
+                continue;
+            }
+
+            if !state_trees.contains_key(name) {
+                let tree = self.db.open_tree_default(name)?;
+                state_trees.insert(name.clone(), tree);
+            }
+        }
+
+        // Drop removed trees and ensure restored trees exist
+        for (name, (_, restored)) in diff.dropped_trees.iter() {
+            if !restored {
+                state_trees.remove(name);
+                self.db.drop_tree(name)?;
+                continue;
+            }
+
+            // Check if its an unknown tree
+            if !self.state.initial_tree_names.contains(name)
+                && !self.state.new_tree_names.contains(name)
+            {
+                self.state.new_tree_names.push(name.clone());
+            }
+
+            if !state_trees.contains_key(name) {
+                let tree = self.db.open_tree_default(name)?;
+                state_trees.insert(name.clone(), tree);
+            }
+        }
+
+        // Aggregate batches
+        let batches = diff.aggregate(&state_trees)?;
+        if batches.is_empty() {
+            self.remove_diff(diff);
+            return Ok(());
+        }
+
+        #[cfg(feature = "sled-backend")]
+        {
+            // Grab all referenced trees
+            let trees: Vec<&sled::Tree> = batches.iter().map(|(t, _)| t.tree()).collect();
+
+            // Perform an atomic transaction over all the collected trees and
+            // apply the batches.
+            if let Err(e) = trees.transaction(|trees| {
+                for (i, tree) in trees.iter().enumerate() {
+                    // Build and apply its batch
+                    let mut sled_batch = sled::Batch::default();
+                    for (key, value) in &batches[i].1.writes {
+                        match value {
+                            Some(v) => sled_batch.insert(key.as_slice(), v.as_slice()),
+                            None => sled_batch.remove(key.as_slice()),
+                        }
+                    }
+                    tree.apply_batch(&sled_batch)?;
+                }
+
+                Ok::<(), ConflictableTransactionError<sled::Error>>(())
+            }) {
+                return Err(Error::Transaction(e.to_string()));
+            };
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            // Grab a batch over the whole database
+            let mut fjall_batch = self.db.fjall_batch();
+
+            // Aggregate the overlay changes into the batch
+            for (tree, batch) in batches {
+                for (key, value) in batch.writes {
+                    match value {
+                        Some(v) => fjall_batch.insert(tree.tree(), key, v),
+                        None => fjall_batch.remove(tree.tree(), key),
+                    }
+                }
+            }
+
+            // Apply the batch
+            fjall_batch.commit()?;
+        }
+
+        // Remove changes from our current state
+        self.remove_diff(diff);
+
+        Ok(())
+    }
+
+    /// Retrieve an immutable itterator from the overlay if the
+    /// specified tree cache exists.
+    pub fn iter(&self, name: &str) -> Result<TreeOverlayIter<'_>> {
+        let cache = self.get_cache(name)?;
+        Ok(cache.iter())
+    }
+}

+ 434 - 0
src/database_overlay_state.rs

@@ -0,0 +1,434 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::collections::{BTreeMap, BTreeSet};
+
+use crate::{Batch, Database, Error, Result, Tree, TreeOverlay, TreeOverlayStateDiff};
+
+/// Struct representing [`DatabaseOverlay`] cache state.
+#[derive(Debug, Clone)]
+pub struct DatabaseOverlayState {
+    /// Existing trees in `Database` at the time of instantiation, so
+    /// we can track newly opened trees.
+    pub initial_tree_names: Vec<String>,
+    /// New trees that have been opened, but didn't exist in `Database`
+    /// before.
+    pub new_tree_names: Vec<String>,
+    /// Pointers to [`TreeOverlay`] instances that have been created.
+    pub caches: BTreeMap<String, TreeOverlay>,
+    /// Trees that were dropped, along with their last state full diff.
+    pub dropped_trees: BTreeMap<String, TreeOverlayStateDiff>,
+    /// Protected trees, that we don't allow their removal, and don't
+    /// drop their references if they become stale.
+    pub protected_tree_names: Vec<String>,
+}
+
+impl DatabaseOverlayState {
+    /// Instantiate a new [`DatabaseOverlayState`].
+    pub fn new(initial_tree_names: Vec<String>, protected_tree_names: Vec<String>) -> Self {
+        Self {
+            initial_tree_names,
+            new_tree_names: vec![],
+            caches: BTreeMap::new(),
+            dropped_trees: BTreeMap::new(),
+            protected_tree_names,
+        }
+    }
+
+    /// Aggregate all the current overlay changes into [`Batch`]
+    /// instances and return vectors of [`Tree`] and their respective
+    /// [`Batch`] that can be used for further operations. If there are
+    /// no changes, both vectors will be empty.
+    pub fn aggregate(&self) -> Result<Vec<(Tree, Batch)>> {
+        let mut batches = vec![];
+
+        for (key, cache) in self.caches.iter() {
+            if self.dropped_trees.contains_key(key) {
+                return Err(Error::CollectionNotFound(key.clone()));
+            }
+
+            if let Some(batch) = cache.aggregate() {
+                batches.push((cache.tree.clone(), batch));
+            }
+        }
+
+        Ok(batches)
+    }
+
+    /// Add provided `Database` overlay state changes to our own.
+    /// If a `Tree` doesn't exists it is opened using the default
+    /// backend configuration
+    pub fn add_diff(&mut self, database: &Database, diff: &DatabaseOverlayStateDiff) -> Result<()> {
+        self.initial_tree_names
+            .retain(|x| diff.initial_tree_names.contains(x));
+
+        for (k, (cache, drop)) in diff.caches.iter() {
+            if *drop {
+                assert!(!self.protected_tree_names.contains(k));
+                self.new_tree_names.retain(|x| x != k);
+                self.caches.remove(k);
+                self.dropped_trees.insert(k.clone(), cache.clone());
+                continue;
+            }
+
+            let Some(tree_overlay) = self.caches.get_mut(k) else {
+                if !self.initial_tree_names.contains(k) && !self.new_tree_names.contains(k) {
+                    self.new_tree_names.push(k.clone());
+                }
+                let mut overlay = TreeOverlay::new(&database.open_tree_default(k)?);
+                overlay.add_diff(cache);
+                self.caches.insert(k.clone(), overlay);
+                continue;
+            };
+
+            // Add the diff to our tree overlay state
+            tree_overlay.add_diff(cache);
+        }
+
+        for (k, (cache, restored)) in &diff.dropped_trees {
+            // Drop the trees that are not restored
+            if !restored {
+                if self.dropped_trees.contains_key(k) {
+                    continue;
+                }
+                self.new_tree_names.retain(|x| x != k);
+                self.caches.remove(k);
+                self.dropped_trees.insert(k.clone(), cache.clone());
+                continue;
+            }
+            assert!(!self.protected_tree_names.contains(k));
+
+            // Restore the tree
+            self.initial_tree_names.retain(|x| x != k);
+            if !self.new_tree_names.contains(k) {
+                self.new_tree_names.push(k.clone());
+            }
+
+            let mut overlay = TreeOverlay::new(&database.open_tree_default(k)?);
+            overlay.add_diff(cache);
+            self.caches.insert(k.clone(), overlay);
+        }
+
+        Ok(())
+    }
+
+    /// Remove provided `database` overlay state changes from our own.
+    pub fn remove_diff(&mut self, diff: &DatabaseOverlayStateDiff) {
+        // We have some assertions here to catch catastrophic
+        // logic bugs here, as all our fields are depending on each
+        // other when checking for differences.
+        for (k, (cache, drop)) in diff.caches.iter() {
+            // We must know the tree
+            assert!(
+                self.initial_tree_names.contains(k)
+                    || self.new_tree_names.contains(k)
+                    || self.dropped_trees.contains_key(k)
+            );
+            if !self.initial_tree_names.contains(k) {
+                self.initial_tree_names.push(k.clone());
+            }
+            self.new_tree_names.retain(|x| x != k);
+
+            // Check if tree is marked for drop
+            if *drop {
+                assert!(!self.protected_tree_names.contains(k));
+                self.initial_tree_names.retain(|x| x != k);
+                self.new_tree_names.retain(|x| x != k);
+                self.caches.remove(k);
+                self.dropped_trees.remove(k);
+                continue;
+            }
+
+            // If the key is not in the cache, and it exists
+            // in the dropped trees, update its diff
+            let Some(tree_overlay) = self.caches.get_mut(k) else {
+                let Some(tree_overlay) = self.dropped_trees.get_mut(k) else {
+                    continue;
+                };
+                tree_overlay.update_values(cache);
+                continue;
+            };
+
+            // If the state is unchanged, handle the stale tree
+            if tree_overlay.state == cache.into() {
+                // If tree is protected, we simply reset its cache
+                if self.protected_tree_names.contains(k) {
+                    tree_overlay.state.cache = BTreeMap::new();
+                    tree_overlay.state.removed = BTreeSet::new();
+                    tree_overlay.checkpoint();
+                    continue;
+                }
+
+                // Drop the stale reference
+                self.caches.remove(k);
+                continue;
+            }
+
+            // Remove the diff from our tree overlay state
+            tree_overlay.remove_diff(cache);
+        }
+
+        // Now we handle the dropped trees
+        for (k, (cache, restored)) in diff.dropped_trees.iter() {
+            // We must know the tree
+            assert!(
+                self.initial_tree_names.contains(k)
+                    || self.new_tree_names.contains(k)
+                    || self.dropped_trees.contains_key(k)
+            );
+
+            // Drop the trees that are not restored
+            if !restored {
+                assert!(!self.protected_tree_names.contains(k));
+                self.initial_tree_names.retain(|x| x != k);
+                self.new_tree_names.retain(|x| x != k);
+                self.caches.remove(k);
+                self.dropped_trees.remove(k);
+                continue;
+            }
+
+            // Restore the tree
+            self.initial_tree_names.retain(|x| x != k);
+            if !self.new_tree_names.contains(k) {
+                self.new_tree_names.push(k.clone());
+            }
+
+            // Skip if not in cache
+            let Some(tree_overlay) = self.caches.get_mut(k) else {
+                continue;
+            };
+
+            // If the state is unchanged, handle the stale tree
+            if tree_overlay.state == cache.into() {
+                // If tree is protected, we simply reset its cache
+                if self.protected_tree_names.contains(k) {
+                    tree_overlay.state.cache = BTreeMap::new();
+                    tree_overlay.state.removed = BTreeSet::new();
+                    tree_overlay.checkpoint();
+                    continue;
+                }
+
+                // Drop the stale reference
+                self.caches.remove(k);
+                continue;
+            }
+
+            // Remove the diff from our tree overlay state
+            tree_overlay.remove_diff(cache);
+        }
+    }
+}
+
+impl Default for DatabaseOverlayState {
+    fn default() -> Self {
+        Self::new(vec![], vec![])
+    }
+}
+
+/// Auxilliary struct representing a [`DatabaseOverlayState`] diff log.
+#[derive(Debug, Default, Clone, PartialEq)]
+pub struct DatabaseOverlayStateDiff {
+    /// Existing trees in `database` at the time of instantiation, so
+    /// we can track newly opened trees.
+    pub initial_tree_names: Vec<String>,
+    /// State diff logs of all [`TreeOverlay`] instances that have been
+    /// created, along with a boolean flag indicating if it should be
+    /// dropped. The drop flag is always set to false, and change to
+    /// true when we inverse the diff of a new tree(not in our initial
+    /// tree names) and the inserts vector is empty, indicating that
+    /// the tree should be dropped.
+    pub caches: BTreeMap<String, (TreeOverlayStateDiff, bool)>,
+    /// Trees that were dropped, along with their last state full diff,
+    /// along with a boolean flag indicating if they should be
+    /// restored. The restore flag is always set to false, and change
+    /// to true when we inverse the diff, unless the tree is a new
+    /// tree(not in our initial tree names).
+    pub dropped_trees: BTreeMap<String, (TreeOverlayStateDiff, bool)>,
+}
+
+impl DatabaseOverlayStateDiff {
+    /// Instantiate a new [`DatabaseOverlayStateDiff`], over the
+    /// provided [`DatabaseOverlayState`].
+    pub fn new(state: &DatabaseOverlayState) -> Result<Self> {
+        let mut caches = BTreeMap::new();
+        let mut dropped_trees = BTreeMap::new();
+
+        for (key, cache) in state.caches.iter() {
+            let diff = cache.diff(&[])?;
+
+            // Skip if diff is empty for an existing tree
+            if diff.cache.is_empty()
+                && diff.removed.is_empty()
+                && !state.new_tree_names.contains(key)
+            {
+                continue;
+            }
+
+            caches.insert(key.clone(), (diff, false));
+        }
+
+        for (key, cache) in state.dropped_trees.iter() {
+            dropped_trees.insert(key.clone(), (cache.clone(), false));
+        }
+
+        Ok(Self {
+            initial_tree_names: state.initial_tree_names.clone(),
+            caches,
+            dropped_trees,
+        })
+    }
+
+    /// Aggregate all the overlay changes into [`Batch`] instances and
+    /// return a vector of `Tree` and their respective `Batch` that can
+    /// be used for further operations. If there are no changes, vector
+    /// will be empty. Provided state trees must contain all the
+    /// [`Tree`] pointers the diff mutates.
+    pub fn aggregate(&self, state_trees: &BTreeMap<String, Tree>) -> Result<Vec<(Tree, Batch)>> {
+        let mut batches = vec![];
+
+        for (key, (cache, drop)) in self.caches.iter() {
+            if *drop {
+                continue;
+            }
+
+            let Some(tree) = state_trees.get(key) else {
+                return Err(Error::CollectionNotFound(key.clone()));
+            };
+
+            if let Some(batch) = cache.aggregate() {
+                batches.push((tree.clone(), batch));
+            }
+        }
+
+        for (key, (cache, restored)) in self.dropped_trees.iter() {
+            if !restored {
+                continue;
+            }
+
+            let Some(tree) = state_trees.get(key) else {
+                return Err(Error::CollectionNotFound(key.clone()));
+            };
+
+            if let Some(batch) = cache.aggregate() {
+                batches.push((tree.clone(), batch));
+            }
+        }
+
+        Ok(batches)
+    }
+
+    /// Produces a [`DatabaseOverlayStateDiff`] containing the inverse
+    /// changes from our own.
+    pub fn inverse(&self) -> Self {
+        let mut diff = Self {
+            initial_tree_names: self.initial_tree_names.clone(),
+            ..Default::default()
+        };
+
+        for (key, (cache, drop)) in self.caches.iter() {
+            let inverse = cache.inverse();
+            // Flip its drop flag if its a new empty tree, otherwise
+            // check if its cache is empty and its a new tree.
+            let drop = if inverse.cache.is_empty()
+                && inverse.removed.is_empty()
+                && !self.initial_tree_names.contains(key)
+            {
+                !drop
+            } else {
+                inverse.cache.is_empty() && !self.initial_tree_names.contains(key)
+            };
+            diff.caches.insert(key.clone(), (inverse, drop));
+        }
+
+        for (key, (cache, restored)) in self.dropped_trees.iter() {
+            if !self.initial_tree_names.contains(key) {
+                continue;
+            }
+            diff.dropped_trees
+                .insert(key.clone(), (cache.clone(), !restored));
+        }
+
+        diff
+    }
+
+    /// Remove provided `database` overlay state changes from our own.
+    pub fn remove_diff(&mut self, other: &Self) {
+        // We have some assertions here to catch catastrophic
+        // logic bugs here, as all our fields are depending on each
+        // other when checking for differences.
+        for initial_tree_name in &other.initial_tree_names {
+            assert!(self.initial_tree_names.contains(initial_tree_name));
+        }
+
+        // First we remove each cache diff
+        for (key, cache_pair) in other.caches.iter() {
+            if !self.initial_tree_names.contains(key) {
+                self.initial_tree_names.push(key.clone());
+            }
+
+            // If the key is not in the cache, and it exists
+            // in the dropped trees, update its diff.
+            let Some(tree_overlay) = self.caches.get_mut(key) else {
+                let Some((tree_overlay, _)) = self.dropped_trees.get_mut(key) else {
+                    continue;
+                };
+                tree_overlay.update_values(&cache_pair.0);
+                continue;
+            };
+
+            // If the state is unchanged, handle the stale tree
+            if tree_overlay == cache_pair {
+                // Drop the stale reference
+                self.caches.remove(key);
+                continue;
+            }
+
+            // Remove the diff from our tree overlay state
+            tree_overlay.0.remove_diff(&cache_pair.0);
+        }
+
+        // Now we handle the dropped trees. We must have all
+        // the keys in our dropped trees keys.
+        for (key, (cache, restored)) in other.dropped_trees.iter() {
+            // Check if the tree was reopened
+            if let Some(tree_overlay) = self.caches.get_mut(key) {
+                assert!(!self.dropped_trees.contains_key(key));
+                // Remove the diff from our tree overlay state
+                tree_overlay.0.remove_diff(cache);
+                continue;
+            }
+            assert!(self.dropped_trees.contains_key(key));
+
+            // Restore tree if its flag is set to true
+            if *restored {
+                self.caches.insert(key.clone(), (cache.clone(), false));
+            }
+
+            // Drop the tree
+            self.initial_tree_names.retain(|x| x != key);
+            self.dropped_trees.remove(key);
+        }
+    }
+
+    /// Auxilliary function to retrieve our newly opened trees.
+    pub fn new_trees(&self) -> Vec<String> {
+        let mut new_trees: Vec<String> = self.caches.keys().cloned().collect();
+        new_trees.retain(|tree| !self.initial_tree_names.contains(tree));
+        new_trees
+    }
+}

+ 68 - 0
src/error.rs

@@ -0,0 +1,68 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/// Main result type used throughout the codebase.
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// General library errors used throughout the codebase.
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+    // =======================
+    // Backend database errors
+    // =======================
+    #[cfg(feature = "sled-backend")]
+    #[error(transparent)]
+    Sled(#[from] sled::Error),
+    #[cfg(feature = "fjall-backend")]
+    #[error("Fjall error: {0}")]
+    Fjall(String),
+
+    // =======================
+    // Crate errors
+    // =======================
+    #[error("Collection was not found: {0}")]
+    CollectionNotFound(String),
+
+    #[error("IO error: {0}")]
+    Io(std::io::ErrorKind),
+
+    #[error("Collection was not found: {0:?}")]
+    KeyNotFound(Vec<u8>),
+
+    #[error("Malformed tree name: {0}")]
+    MalformedTreeMame(String),
+
+    #[error("Protected tree can't be dropped: {0}")]
+    ProtectedTreeDrop(String),
+
+    #[error("Error while executing transaction: {0}")]
+    Transaction(String),
+}
+
+#[cfg(feature = "fjall-backend")]
+impl From<fjall::Error> for Error {
+    fn from(err: fjall::Error) -> Self {
+        Self::Fjall(err.to_string())
+    }
+}
+
+impl From<std::io::Error> for Error {
+    fn from(err: std::io::Error) -> Self {
+        Self::Io(err.kind())
+    }
+}

+ 64 - 0
src/lib.rs

@@ -0,0 +1,64 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! kvdb-overlay is a small crate that serves as tooling to have
+//! intermediate writes to some key-value database. With it, we're able
+//! to write data into an in-memory cache, and only flush to the actual
+//! trees after we decide that everything in some batch was executed
+//! correctly. This gives some minimal infrastructure to be able to
+//! transparently have rollback-like functionality.
+
+#[cfg(all(feature = "sled-backend", feature = "fjall-backend"))]
+compile_error!(
+    "Features `sled-backend` and `fjall-backend` are mutually exclusive; enable exactly one"
+);
+
+#[cfg(not(any(feature = "sled-backend", feature = "fjall-backend")))]
+compile_error!("Exactly one of `sled-backend` or `fjall-backend` must be enabled");
+
+#[cfg(feature = "sled-backend")]
+pub use sled;
+
+#[cfg(feature = "fjall-backend")]
+pub use fjall;
+
+pub use tempfile::{TempDir, tempdir};
+
+pub mod error;
+pub use error::{Error, Result};
+
+pub mod batch;
+pub use batch::Batch;
+
+pub mod database;
+pub use database::Database;
+
+pub mod database_overlay;
+pub use database_overlay::DatabaseOverlay;
+
+pub mod database_overlay_state;
+pub use database_overlay_state::{DatabaseOverlayState, DatabaseOverlayStateDiff};
+
+pub mod tree;
+pub use tree::{Tree, TreeIter};
+
+pub mod tree_overlay;
+pub use tree_overlay::{TreeOverlay, TreeOverlayIter};
+
+pub mod tree_overlay_state;
+pub use tree_overlay_state::{TreeOverlayState, TreeOverlayStateDiff};

+ 331 - 0
src/tree.rs

@@ -0,0 +1,331 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{iter::FusedIterator, ops::RangeBounds};
+
+use crate::Result;
+
+/// Struct representing the abstraction over the supported key-value
+/// embedded databases trees.
+#[derive(Clone)]
+pub struct Tree {
+    #[cfg(feature = "sled-backend")]
+    tree: sled::Tree,
+    #[cfg(feature = "fjall-backend")]
+    tree: fjall::Keyspace,
+}
+
+impl Tree {
+    pub fn new(
+        #[cfg(feature = "sled-backend")] tree: sled::Tree,
+        #[cfg(feature = "fjall-backend")] tree: fjall::Keyspace,
+    ) -> Self {
+        Self { tree }
+    }
+
+    #[cfg(feature = "sled-backend")]
+    /// Returns a reference to underlining tree
+    pub fn tree(&self) -> &sled::Tree {
+        &self.tree
+    }
+
+    #[cfg(feature = "fjall-backend")]
+    /// Returns a reference to underlining tree
+    pub fn tree(&self) -> &fjall::Keyspace {
+        &self.tree
+    }
+
+    /// Returns true if the Tree contains a value for the specified key.
+    pub fn contains_key(&self, k: &[u8]) -> Result<bool> {
+        Ok(self.tree.contains_key(k)?)
+    }
+
+    /// Retrieve a value from the Tree if it exists.    
+    pub fn get(&self, k: &[u8]) -> Result<Option<Vec<u8>>> {
+        Ok(self.tree.get(k)?.map(|v| v.to_vec()))
+    }
+
+    /// Inserts a key-value pair into the tree and return its previous
+    /// value.
+    pub fn insert(&self, k: &[u8], v: &[u8]) -> Result<Option<Vec<u8>>> {
+        let prev = {
+            #[cfg(feature = "sled-backend")]
+            {
+                self.tree.insert(k, v)?.map(|v| v.to_vec())
+            }
+            #[cfg(feature = "fjall-backend")]
+            {
+                let prev = self.tree.get(k)?.map(|v| v.to_vec());
+                self.tree.insert(k, v)?;
+                prev
+            }
+        };
+
+        Ok(prev)
+    }
+
+    /// Removes a key from the tree and returns its last value.
+    pub fn remove(&self, k: &[u8]) -> Result<Option<Vec<u8>>> {
+        let prev = {
+            #[cfg(feature = "sled-backend")]
+            {
+                self.tree.remove(k)?.map(|v| v.to_vec())
+            }
+            #[cfg(feature = "fjall-backend")]
+            {
+                let prev = self.tree.get(k)?.map(|v| v.to_vec());
+                self.tree.remove(k)?;
+                prev
+            }
+        };
+
+        Ok(prev)
+    }
+
+    /// Clears the Tree, removing all values.
+    pub fn clear(&self) -> Result<()> {
+        Ok(self.tree.clear()?)
+    }
+
+    /// Returns the number of elements in this tree.
+    pub fn len(&self) -> Result<usize> {
+        #[cfg(feature = "sled-backend")]
+        {
+            Ok(self.tree.len())
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            Ok(self.tree.len()?)
+        }
+    }
+
+    /// Returns true if the Tree contains no elements.
+    pub fn is_empty(&self) -> Result<bool> {
+        #[cfg(feature = "sled-backend")]
+        {
+            Ok(self.tree.is_empty())
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            Ok(self.tree.is_empty()?)
+        }
+    }
+
+    /// Returns the first key and value in the Tree, or None if the
+    /// Tree is empty.
+    pub fn first(&self) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
+        #[cfg(feature = "sled-backend")]
+        {
+            Ok(self.tree.first()?.map(|(k, v)| (k.to_vec(), v.to_vec())))
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            Ok(match self.tree.first_key_value() {
+                Some(g) => {
+                    let (k, v) = g.into_inner()?;
+                    Some((k.to_vec(), v.to_vec()))
+                }
+                None => None,
+            })
+        }
+    }
+
+    /// Returns the last key and value in the Tree, or None if the
+    /// Tree is empty.
+    pub fn last(&self) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
+        #[cfg(feature = "sled-backend")]
+        {
+            Ok(self.tree.last()?.map(|(k, v)| (k.to_vec(), v.to_vec())))
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            Ok(match self.tree.last_key_value() {
+                Some(g) => {
+                    let (k, v) = g.into_inner()?;
+                    Some((k.to_vec(), v.to_vec()))
+                }
+                None => None,
+            })
+        }
+    }
+
+    /// Retrieve the key and value before the provided key, if one
+    /// exists.
+    pub fn get_lt(&self, k: &[u8]) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
+        #[cfg(feature = "sled-backend")]
+        {
+            Ok(self.tree.get_lt(k)?.map(|(k, v)| (k.to_vec(), v.to_vec())))
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            Ok(match self.tree.range(..k).next_back() {
+                Some(g) => {
+                    let (k, v) = g.into_inner()?;
+                    Some((k.to_vec(), v.to_vec()))
+                }
+                None => None,
+            })
+        }
+    }
+
+    /// Retrieve the key and value after the provided key, if one
+    /// exists.
+    pub fn get_gt(&self, k: &[u8]) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
+        #[cfg(feature = "sled-backend")]
+        {
+            Ok(self.tree.get_gt(k)?.map(|(k, v)| (k.to_vec(), v.to_vec())))
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            let mut iter = self.tree.range(k..);
+            let Some(g) = iter.next() else {
+                return Ok(None);
+            };
+            let (key, value) = g.into_inner()?;
+            if key != k {
+                return Ok(Some((key.to_vec(), value.to_vec())));
+            }
+            Ok(match iter.next() {
+                Some(g) => {
+                    let (k, v) = g.into_inner()?;
+                    Some((k.to_vec(), v.to_vec()))
+                }
+                None => None,
+            })
+        }
+    }
+
+    /// Returns an iterator over the Tree.
+    pub fn iter(&self) -> TreeIter {
+        TreeIter::new(self)
+    }
+
+    /// Returns an iterator over the Tree for the provided range.
+    pub fn range<K, R>(&self, range: R) -> TreeIter
+    where
+        K: AsRef<[u8]>,
+        R: RangeBounds<K>,
+    {
+        TreeIter::range(self, range)
+    }
+}
+
+impl std::fmt::Debug for Tree {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        #[cfg(feature = "sled-backend")]
+        {
+            write!(f, "{:?}", self.tree)
+        }
+        #[cfg(feature = "fjall-backend")]
+        {
+            write!(f, "Tree: {:?}", self.tree.name())
+        }
+    }
+}
+
+/// Tree names can be up to 255 characters long, can not be empty and
+/// can only contain alphanumerics, underscore (`_`), dash (`-`), dot
+/// (`.`), hash tag (`#`) and dollar (`$`).
+pub fn check_tree_name(name: &str) -> Result<()> {
+    if name.is_empty() {
+        return Err(crate::Error::MalformedTreeMame(String::from(
+            "Tree name cannot be empty.",
+        )));
+    }
+
+    if let Err(e) = u8::try_from(name.len()) {
+        return Err(crate::Error::MalformedTreeMame(format!(
+            "Tree name exceeds 255 characters: {e}"
+        )));
+    }
+
+    Ok(())
+}
+
+/// Immutable iterator over the supported key-value embedded databases
+/// iterators.
+pub struct TreeIter {
+    // Iterator over [`Tree`] keys that is being overlayed.
+    #[cfg(feature = "sled-backend")]
+    iter: sled::Iter,
+    #[cfg(feature = "fjall-backend")]
+    iter: fjall::Iter,
+}
+
+impl TreeIter {
+    fn new(tree: &Tree) -> Self {
+        Self {
+            iter: tree.tree.iter(),
+        }
+    }
+
+    fn range<K, R>(tree: &Tree, range: R) -> Self
+    where
+        K: AsRef<[u8]>,
+        R: RangeBounds<K>,
+    {
+        Self {
+            iter: tree.tree.range(range),
+        }
+    }
+}
+
+impl Iterator for TreeIter {
+    type Item = Result<(Vec<u8>, Vec<u8>)>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let record = self.iter.next()?;
+
+        #[cfg(feature = "fjall-backend")]
+        let record = record.into_inner();
+
+        match record {
+            Ok((key, value)) => Some(Ok((key.to_vec(), value.to_vec()))),
+            Err(e) => Some(Err(e.into())),
+        }
+    }
+}
+
+impl DoubleEndedIterator for TreeIter {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        let record = self.iter.next_back()?;
+
+        #[cfg(feature = "fjall-backend")]
+        let record = record.into_inner();
+
+        match record {
+            Ok((key, value)) => Some(Ok((key.to_vec(), value.to_vec()))),
+            Err(e) => Some(Err(e.into())),
+        }
+    }
+}
+
+impl FusedIterator for TreeIter {}
+
+/// Define fusion iteration behavior, allowing us to use the
+/// [`TreeIter`] iterator in loops directly, without using `.iter()`
+/// method of [`Tree`].
+impl IntoIterator for Tree {
+    type Item = Result<(Vec<u8>, Vec<u8>)>;
+
+    type IntoIter = TreeIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}

+ 435 - 0
src/tree_overlay.rs

@@ -0,0 +1,435 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{
+    cmp::Ordering,
+    collections::{BTreeMap, BTreeSet, btree_map::Keys},
+    iter::{FusedIterator, Peekable},
+};
+
+use crate::{Batch, Error, Result, Tree, TreeOverlayState, TreeOverlayStateDiff};
+
+/// An overlay on top of a single [`Tree`] instance.
+#[derive(Debug, Clone)]
+pub struct TreeOverlay {
+    /// The [`Tree`] that is being overlayed.
+    pub tree: Tree,
+    /// Current overlay cache state.
+    pub state: TreeOverlayState,
+    /// Checkpointed cache state to revert to.
+    checkpoint: TreeOverlayState,
+}
+
+impl TreeOverlay {
+    /// Instantiate a new [`TreeOverlay`] on top of a given [`Tree`].
+    pub fn new(tree: &Tree) -> Self {
+        Self {
+            tree: tree.clone(),
+            state: TreeOverlayState::new(),
+            checkpoint: TreeOverlayState::new(),
+        }
+    }
+
+    /// Returns `true` if the overlay contains a value for a specified
+    /// key.
+    pub fn contains_key(&self, key: &[u8]) -> Result<bool> {
+        // First check if the key was removed in the overlay
+        if self.state.removed.contains(key) {
+            return Ok(false);
+        }
+
+        // Then check the cache and the main tree
+        if self.state.cache.contains_key(key) || self.tree.contains_key(key)? {
+            return Ok(true);
+        }
+
+        Ok(false)
+    }
+
+    /// Returns `true` if the overlay is empty.
+    pub fn is_empty(&self) -> Result<bool> {
+        // Keep a counter of all elements
+        let mut counter: i64 = 0;
+
+        // Add existing keys
+        counter += self.tree.len()? as i64;
+
+        // Add new keys
+        for key in self.state.cache.keys() {
+            if !self.tree.contains_key(key)? {
+                counter += 1;
+            }
+        }
+
+        // Subtract removed keys
+        counter -= self.state.removed.len() as i64;
+
+        Ok(counter <= 0)
+    }
+
+    /// Returns last key and value from the overlay or `None` if its empty,
+    /// based on the `Ord` implementation for `Vec<u8>`.
+    pub fn last(&self) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
+        // If both main tree and cache are empty, return None
+        if self.tree.is_empty()? && self.state.cache.is_empty() {
+            return Ok(None);
+        }
+
+        // Grab main tree last record
+        let tree_last = self.tree.last()?;
+
+        // If cache has no records, main tree last exists
+        if self.state.cache.is_empty() {
+            // We can safely unwrap here since main tree is not
+            // empty, as we have already checked if both main
+            // tree and cache are empty.
+            let record = tree_last.unwrap();
+
+            // Check if record is removed
+            if self.state.removed.contains(&record.0) {
+                // Find last existing record
+                let mut key = record.0.clone();
+                while let Some(record) = self.tree.get_lt(&key)? {
+                    // Check if record is removed
+                    if self.state.removed.contains(&record.0) {
+                        key = record.0;
+                        continue;
+                    }
+                    // Return it
+                    return Ok(Some((record.0.clone(), record.1.clone())));
+                }
+
+                // Return None if no records exist
+                return Ok(None);
+            }
+
+            // Return it
+            return Ok(Some((record.0.clone(), record.1.clone())));
+        }
+
+        // Grab cache last record.
+        // We can safely unwrap here as we checked if the cache is
+        // empty on the previous step.
+        let cache_last = self.state.cache.last_key_value().unwrap();
+
+        // Find the main tree last existing record, compare it with the
+        // cache last record, and return it if it's not removed
+        if let Some(tree_last) = tree_last {
+            if cache_last.0 < &tree_last.0 && !self.state.removed.contains(&tree_last.0) {
+                return Ok(Some((tree_last.0.clone(), tree_last.1.clone())));
+            }
+
+            let mut key = tree_last.0.clone();
+            while let Some(record) = self.tree.get_lt(&key)? {
+                // Break if we reach the cache key position
+                if cache_last.0 >= &record.0 {
+                    break;
+                }
+
+                // Check if record is removed
+                if !self.state.removed.contains(&record.0) {
+                    return Ok(Some((record.0.clone(), record.1.clone())));
+                }
+
+                key = record.0;
+            }
+        }
+
+        // Return the cache last record
+        Ok(Some((cache_last.0.clone(), cache_last.1.clone())))
+    }
+
+    /// Retrieve a value from the overlay if it exists.
+    pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
+        // First check if the key was removed in the overlay
+        if self.state.removed.contains(key) {
+            return Ok(None);
+        }
+
+        // Then check the cache
+        if let Some(v) = self.state.cache.get(key) {
+            return Ok(Some(v.clone()));
+        }
+
+        // And finally the main tree
+        self.tree.get(key)
+    }
+
+    /// Insert a key to a new value, returning the last value if it was
+    /// set.
+    pub fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<Option<Vec<u8>>> {
+        // Insert the value into the cache. We then optionally add the
+        // previous value into `prev`.
+        let mut prev = self.state.cache.insert(key.to_vec(), value.to_vec());
+
+        // In case this key was previously removed from the cache, we
+        // have to delete it from the `removed` set.
+        if self.state.removed.contains(key) {
+            self.state.removed.remove(key);
+            // And in that case, a previous value isn't supposed to
+            // exist.
+            return Ok(None);
+        }
+
+        // If cache didn't contain this key previously, and it wasn't
+        // removed either, then check if it's in the main tree.
+        if prev.is_none() {
+            prev = self.tree.get(key)?;
+        }
+
+        Ok(prev)
+    }
+
+    /// Delete a value, if it exists, returning the old value.
+    pub fn remove(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>> {
+        // If it was previously removed, we can just return None
+        if self.state.removed.contains(key) {
+            return Ok(None);
+        }
+
+        // Attempt to remove from cache, and if it wasn't in the cache
+        // before, we have to get the previous value from the tree:
+        let mut prev = self.state.cache.remove(key);
+        if prev.is_none() {
+            prev = self.tree.get(key)?;
+        }
+
+        // Previous value must existed
+        if prev.is_none() {
+            return Err(Error::KeyNotFound(key.to_vec()));
+        }
+
+        // Mark the key as removed
+        self.state.removed.insert(key.to_vec());
+
+        Ok(prev)
+    }
+
+    /// Removes all values from the cache and marks all tree records as
+    /// removed.
+    pub fn clear(&mut self) -> Result<()> {
+        // Retrieve all db's keys to mark them as removed
+        let mut removed_keys = BTreeSet::new();
+        for record in self.tree.iter() {
+            removed_keys.insert(record?.0);
+        }
+
+        // Clear state
+        self.state.cache = BTreeMap::new();
+        self.state.removed = removed_keys;
+
+        Ok(())
+    }
+
+    /// Aggregate all the current overlay changes into a [`Batch`]
+    /// ready for further operation. If there are no changes, return
+    /// `None`.
+    pub fn aggregate(&self) -> Option<Batch> {
+        self.state.aggregate()
+    }
+
+    /// Checkpoint current cache state so we can revert to it, if
+    /// needed.
+    pub fn checkpoint(&mut self) {
+        self.checkpoint = self.state.clone();
+    }
+
+    /// Revert to current cache state checkpoint.
+    pub fn revert_to_checkpoint(&mut self) {
+        self.state = self.checkpoint.clone();
+    }
+
+    /// Calculate differences from provided overlay state changes
+    /// sequence. This can be used when we want to keep track of
+    /// consecutive individual changes performed over the current
+    /// overlay state. If the sequence is empty, current state
+    /// is returned as the diff.
+    pub fn diff(&self, sequence: &[TreeOverlayStateDiff]) -> Result<TreeOverlayStateDiff> {
+        // Grab current state
+        let mut current = TreeOverlayStateDiff::new(&self.tree, &self.state)?;
+
+        // Remove provided diffs sequence
+        for diff in sequence {
+            current.remove_diff(diff);
+        }
+
+        Ok(current)
+    }
+
+    /// Add provided tree overlay state changes from our own.
+    pub fn add_diff(&mut self, diff: &TreeOverlayStateDiff) {
+        self.state.add_diff(diff)
+    }
+
+    /// Remove provided tree overlay state changes from our own.
+    pub fn remove_diff(&mut self, diff: &TreeOverlayStateDiff) {
+        self.state.remove_diff(diff)
+    }
+
+    /// Immutably iterate through the tree overlay.
+    pub fn iter(&self) -> TreeOverlayIter<'_> {
+        TreeOverlayIter::new(self)
+    }
+}
+
+/// Immutable iterator of a [`TreeOverlay`].
+pub struct TreeOverlayIter<'a> {
+    // Reference to the tree overlay.
+    overlay: &'a TreeOverlay,
+    // Iterator over [`Tree`] keys that is being overlayed.
+    #[cfg(feature = "sled-backend")]
+    tree_iter: Peekable<sled::Iter>,
+    #[cfg(feature = "fjall-backend")]
+    tree_iter: std::iter::Enumerate<fjall::Iter>,
+    // Iterator over the overlay's chache keys.
+    cache_iter: Peekable<Keys<'a, Vec<u8>, Vec<u8>>>,
+}
+
+impl<'a> TreeOverlayIter<'a> {
+    fn new(overlay: &'a TreeOverlay) -> Self {
+        Self {
+            overlay,
+            #[cfg(feature = "sled-backend")]
+            tree_iter: overlay.tree.tree().iter().peekable(),
+            #[cfg(feature = "fjall-backend")]
+            tree_iter: overlay.tree.tree().iter().enumerate(),
+            cache_iter: overlay.state.cache.keys().peekable(),
+        }
+    }
+}
+
+impl Iterator for TreeOverlayIter<'_> {
+    type Item = Result<(Vec<u8>, Vec<u8>)>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        // First we peek over the next tree key
+        #[cfg(feature = "sled-backend")]
+        let peek1 = {
+            let peek = self.tree_iter.peek();
+            // Check if an error occured
+            if let Some(Err(e)) = peek {
+                return Some(Err(Error::Sled(e.clone())));
+            }
+            peek
+        };
+        #[cfg(feature = "fjall-backend")]
+        let (position, peek1) = {
+            match self.tree_iter.next() {
+                Some((pos, g)) => match g.key() {
+                    Ok(k) => (pos, Some(k)),
+                    Err(e) => return Some(Err(e.into())),
+                },
+                None => (0, None),
+            }
+        };
+
+        // Peek over the next cache key
+        let peek2 = self.cache_iter.peek();
+
+        // Find the next key we have to grab,
+        // and which iterator to advance.
+        let mut advance_iter: u8 = 0;
+        let next_key = match (&peek1, peek2) {
+            (Some(k1), Some(&k2)) => {
+                // Its safe to unwrap here since we already checked for
+                // errors.
+                #[cfg(feature = "sled-backend")]
+                let (k1, _) = k1.as_ref().unwrap();
+                let k1 = k1.to_vec();
+                match k1.to_vec().cmp(k2) {
+                    Ordering::Equal => Some(k1),
+                    Ordering::Greater => {
+                        advance_iter = 2;
+                        Some(k2.to_vec())
+                    }
+                    Ordering::Less => {
+                        advance_iter = 1;
+                        Some(k1)
+                    }
+                }
+            }
+            (Some(k1), None) => {
+                // Its safe to unwrap here since we already checked for
+                // errors.
+                #[cfg(feature = "sled-backend")]
+                let (k1, _) = k1.as_ref().unwrap();
+                advance_iter = 1;
+                Some(k1.to_vec())
+            }
+            (None, Some(&k2)) => {
+                advance_iter = 2;
+                Some(k2.to_vec())
+            }
+            (None, None) => None,
+        };
+
+        // Check if we have a key to grab
+        let next_key = next_key?;
+
+        // Advance the corresponding iterator
+        match advance_iter {
+            1 => {
+                #[cfg(feature = "sled-backend")]
+                self.tree_iter.next();
+            }
+            2 => {
+                // We have to reset the tree iterator manually since
+                // [`fjall::Guard`] is consuming.
+                #[cfg(feature = "fjall-backend")]
+                if peek1.is_some() {
+                    self.tree_iter = self.overlay.tree.tree().iter().enumerate();
+                    for _ in 0..position {
+                        self.tree_iter.next();
+                    }
+                }
+                self.cache_iter.next();
+            }
+            _ => {
+                #[cfg(feature = "sled-backend")]
+                self.tree_iter.next();
+                self.cache_iter.next();
+            }
+        }
+
+        // Grab the next key value from the overlay
+        match self.overlay.get(&next_key) {
+            Ok(next_value) => match next_value {
+                Some(next_value) => Some(Ok((next_key, next_value))),
+                // If the value doesn't exist, it means it's in the
+                // removed set, so we advance the iterator.
+                None => self.next(),
+            },
+            Err(e) => Some(Err(e)),
+        }
+    }
+}
+
+impl FusedIterator for TreeOverlayIter<'_> {}
+
+/// Define fusion iteration behavior, allowing us to use the
+/// [`TreeOverlayIter`] iterator in loops directly, without using
+/// `.iter()` method of [`TreeOverlay`].
+impl<'a> IntoIterator for &'a TreeOverlay {
+    type Item = Result<(Vec<u8>, Vec<u8>)>;
+
+    type IntoIter = TreeOverlayIter<'a>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}

+ 294 - 0
src/tree_overlay_state.rs

@@ -0,0 +1,294 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::collections::{BTreeMap, BTreeSet};
+
+use crate::{Batch, Result, Tree};
+
+/// Struct representing [`TreeOverlay`] cache state.
+#[derive(Debug, Default, Clone, PartialEq)]
+pub struct TreeOverlayState {
+    /// The cache is the actual overlayed data represented as a [`BTreeMap`].
+    pub cache: BTreeMap<Vec<u8>, Vec<u8>>,
+    /// In `removed`, we keep track of keys that were removed in the overlay.
+    pub removed: BTreeSet<Vec<u8>>,
+}
+
+impl TreeOverlayState {
+    /// Instantiate a new [`TreeOverlayState`].
+    pub fn new() -> Self {
+        Self {
+            cache: BTreeMap::new(),
+            removed: BTreeSet::new(),
+        }
+    }
+
+    /// Aggregate all the current tree overlay state changes into
+    /// a [`Batch`] ready for further operation.
+    /// If there are no changes, return `None`.
+    pub fn aggregate(&self) -> Option<Batch> {
+        if self.cache.is_empty() && self.removed.is_empty() {
+            return None;
+        }
+
+        let mut batch = Batch::new();
+
+        // This kind of first-insert-then-remove operation should be fine
+        // provided it's handled correctly in the above functions.
+        for (k, v) in self.cache.iter() {
+            batch.insert(k, v);
+        }
+
+        for k in self.removed.iter() {
+            batch.remove(k);
+        }
+
+        Some(batch)
+    }
+
+    /// Add provided tree overlay state changes to our own.
+    pub fn add_diff(&mut self, diff: &TreeOverlayStateDiff) {
+        // Add all new keys into cache
+        for (k, v) in diff.cache.iter() {
+            self.removed.remove(k);
+            self.cache.insert(k.clone(), v.1.clone());
+        }
+
+        // Remove dropped keys
+        for k in diff.removed.keys() {
+            self.cache.remove(k);
+            self.removed.insert(k.clone());
+        }
+    }
+
+    /// Remove provided tree overlay state changes from our own.
+    pub fn remove_diff(&mut self, diff: &TreeOverlayStateDiff) {
+        for (k, v) in diff.cache.iter() {
+            // Skip if its not in cache
+            let Some(value) = self.cache.get(k) else {
+                continue;
+            };
+
+            // Check if its value has been modified again
+            if &v.1 != value {
+                continue;
+            }
+
+            self.cache.remove(k);
+        }
+
+        for k in diff.removed.keys() {
+            self.removed.remove(k);
+        }
+    }
+}
+
+impl From<&TreeOverlayStateDiff> for TreeOverlayState {
+    fn from(diff: &TreeOverlayStateDiff) -> Self {
+        let mut cache = BTreeMap::new();
+        let mut removed = BTreeSet::new();
+
+        for (key, value) in diff.cache.iter() {
+            cache.insert(key.clone(), value.1.clone());
+        }
+
+        for key in diff.removed.keys() {
+            removed.insert(key.clone());
+        }
+
+        Self { cache, removed }
+    }
+}
+
+/// Auxilliary type definition for [`TreeOverlayStateDiff`] cache.
+pub type TreeOverlayStateDiffCache = BTreeMap<Vec<u8>, (Option<Vec<u8>>, Vec<u8>)>;
+
+/// Auxilliary struct representing a [`TreeOverlayState`] diff log.
+#[derive(Debug, Default, Clone, PartialEq)]
+pub struct TreeOverlayStateDiff {
+    /// Inserted data represented as a [`BTreeMap`].
+    /// The value contains both the previous key value(if it existed), along
+    /// with the new one.
+    pub cache: TreeOverlayStateDiffCache,
+    /// In `removed`, we keep track of keys that were removed in the overlay,
+    /// along with their value.
+    pub removed: BTreeMap<Vec<u8>, Vec<u8>>,
+}
+
+impl TreeOverlayStateDiff {
+    /// Instantiate a new [`TreeOverlayStateDiff`], over the provided
+    /// [`Tree`] that is being overlayed.
+    pub fn new(tree: &Tree, state: &TreeOverlayState) -> Result<Self> {
+        let mut cache = BTreeMap::new();
+        let mut removed = BTreeMap::new();
+
+        // Set inserted keys
+        for (key, value) in state.cache.iter() {
+            // Grab each key previous value, if it existed
+            let previous = tree.get(key)?;
+            cache.insert(key.clone(), (previous, value.clone()));
+        }
+
+        // Set removed keys
+        for key in state.removed.iter() {
+            // Grab each key last value, if it existed, otherwise
+            // use an empty value as its previous.
+            let previous = tree.get(key)?.unwrap_or_default();
+            removed.insert(key.clone(), previous);
+        }
+
+        Ok(Self { cache, removed })
+    }
+
+    /// Instantiate a new [`TreeOverlayStateDiff`], over the provided
+    /// [`Tree`] that is being dropped. The diff will contain all
+    /// existing tree keys in its cache as inserts, representing the
+    /// last tree state.
+    pub fn new_dropped(tree: &Tree) -> Self {
+        let mut cache = BTreeMap::new();
+
+        // Insert all tree keys
+        for record in tree.iter() {
+            let (key, value) = record.unwrap();
+            cache.insert(key, (None, value));
+        }
+
+        Self {
+            cache,
+            removed: BTreeMap::new(),
+        }
+    }
+
+    /// Aggregate all the tree overlay state changes into
+    /// a [`Batch`] ready for further operation.
+    /// If there are no changes, return `None`.
+    pub fn aggregate(&self) -> Option<Batch> {
+        if self.cache.is_empty() && self.removed.is_empty() {
+            return None;
+        }
+
+        let mut batch = Batch::new();
+
+        // This kind of first-insert-then-remove operation should be fine
+        // provided it's handled correctly in the above functions.
+        for (k, v) in self.cache.iter() {
+            batch.insert(k, &v.1);
+        }
+
+        for k in self.removed.keys() {
+            batch.remove(k);
+        }
+
+        Some(batch)
+    }
+
+    /// Aggregate all the current tree overlay state changes inverse
+    /// actions into a [`Batch`] ready for further operation.
+    /// If there are no changes, return `None`.
+    pub fn revert(&self) -> Option<Batch> {
+        if self.cache.is_empty() && self.removed.is_empty() {
+            return None;
+        }
+
+        let mut batch = Batch::new();
+
+        // This kind of first-insert-then-remove operation should be
+        // fine provided it's handled correctly in the above functions.
+        for (k, v) in self.removed.iter() {
+            batch.insert(k, v);
+        }
+
+        for (k, v) in self.cache.iter() {
+            // If key value has been modified, revert to previous one
+            if let Some(value) = &v.0 {
+                batch.insert(k, value);
+                continue;
+            }
+            batch.remove(k);
+        }
+
+        Some(batch)
+    }
+
+    /// Produces a [`TreeOverlayStateDiff`] containing the inverse
+    /// changes from our own.
+    pub fn inverse(&self) -> Self {
+        let mut diff = Self::default();
+
+        // This kind of first-insert-then-remove operation should be
+        // fine provided it's handled correctly in the above functions.
+        for (k, v) in self.removed.iter() {
+            diff.cache.insert(k.clone(), (None, v.clone()));
+        }
+
+        for (k, v) in self.cache.iter() {
+            // If its value has been modified, flip it
+            if let Some(previous) = &v.0 {
+                diff.cache
+                    .insert(k.clone(), (Some(v.1.clone()), previous.clone()));
+                continue;
+            }
+            diff.removed.insert(k.clone(), v.1.clone());
+        }
+
+        diff
+    }
+
+    /// Remove provided tree overlay state changes from our own.
+    pub fn remove_diff(&mut self, other: &Self) {
+        for (k, v) in other.cache.iter() {
+            // Set as removed if its not in cache
+            let Some(values) = self.cache.get(k) else {
+                self.removed.insert(k.clone(), v.1.clone());
+                continue;
+            };
+
+            // Check if its value has been modified again
+            if v.1 != values.1 {
+                // Set previous value
+                self.cache
+                    .insert(k.clone(), (Some(v.1.clone()), values.1.clone()));
+                continue;
+            }
+
+            self.cache.remove(k);
+        }
+
+        for k in other.removed.keys() {
+            // Update cache key previous, if it exists
+            if let Some(values) = self.cache.get(k) {
+                self.cache.insert(k.clone(), (None, values.1.clone()));
+                continue;
+            }
+
+            self.removed.remove(k);
+        }
+    }
+
+    /// Update our cache key values to the ones in the provided
+    /// tree overlay state changes.
+    pub fn update_values(&mut self, other: &Self) {
+        for (k, v) in other.cache.iter() {
+            self.cache.insert(k.clone(), v.clone());
+        }
+
+        for k in other.removed.keys() {
+            self.cache.remove(k);
+        }
+    }
+}

+ 150 - 0
tests/database_overlay.rs

@@ -0,0 +1,150 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of a [`DatabaseOverlay`] on top of an entire
+//! [`Database`] instance, and perform writes to verify overlay's cache
+//! functionality.
+
+use kvdb_overlay::{Database, DatabaseOverlay, Result};
+
+const TREE_1: &str = "_tree1";
+const TREE_2: &str = "_tree2";
+
+#[test]
+fn database_overlay() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open trees in the overlay
+    overlay.open_tree_default(TREE_1, false)?;
+    overlay.open_tree_default(TREE_2, false)?;
+
+    // Check overlay trees are empty
+    assert!(overlay.is_empty(TREE_1)?);
+    assert!(overlay.is_empty(TREE_2)?);
+
+    // Check last value is `None`
+    assert_eq!(overlay.last(TREE_1)?, None);
+    assert_eq!(overlay.last(TREE_2)?, None);
+
+    // We keep seperate trees for validation
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    let tree_2 = db.open_tree_default(TREE_2)?;
+
+    // Insert some values to the overlay
+    overlay.insert(TREE_1, b"key_a", b"val_a")?;
+    overlay.insert(TREE_1, b"key_b", b"val_b")?;
+    overlay.insert(TREE_1, b"key_c", b"val_c")?;
+
+    overlay.insert(TREE_2, b"key_d", b"val_d")?;
+    overlay.insert(TREE_2, b"key_e", b"val_e")?;
+    overlay.insert(TREE_2, b"key_f", b"val_f")?;
+
+    // Verify they are in the overlay
+    assert_eq!(overlay.get(TREE_1, b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay.get(TREE_1, b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay.get(TREE_1, b"key_c")?, Some(b"val_c".into()));
+
+    assert_eq!(overlay.get(TREE_2, b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(overlay.get(TREE_2, b"key_e")?, Some(b"val_e".into()));
+    assert_eq!(overlay.get(TREE_2, b"key_f")?, Some(b"val_f".into()));
+
+    // Check overlay trees are not empty
+    assert!(!overlay.is_empty(TREE_1)?);
+    assert!(!overlay.is_empty(TREE_2)?);
+
+    // Check their last values
+    assert_eq!(
+        overlay.last(TREE_1)?,
+        Some((b"key_c".into(), b"val_c".into()))
+    );
+    assert_eq!(
+        overlay.last(TREE_2)?,
+        Some((b"key_f".into(), b"val_f".into()))
+    );
+
+    // Verify they are not in the database
+    assert_eq!(tree_1.get(b"key_a")?, None);
+    assert_eq!(tree_1.get(b"key_b")?, None);
+    assert_eq!(tree_1.get(b"key_c")?, None);
+
+    assert_eq!(tree_2.get(b"key_d")?, None);
+    assert_eq!(tree_2.get(b"key_e")?, None);
+    assert_eq!(tree_2.get(b"key_f")?, None);
+
+    // Now execute all tree batches in the overlay
+    overlay.apply()?;
+
+    // Don't forget to flush
+    db.flush_default_mode()?;
+
+    // Verify the database contains keys
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(tree_1.get(b"key_c")?, Some(b"val_c".into()));
+
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_e")?, Some(b"val_e".into()));
+    assert_eq!(tree_2.get(b"key_f")?, Some(b"val_f".into()));
+
+    Ok(())
+}
+
+#[test]
+fn database_overlay_iteration() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize tree
+    let tree = db.open_tree_default(TREE_1)?;
+    tree.insert(b"key_a", b"val_a")?;
+    tree.insert(b"key_c", b"val_c")?;
+    tree.insert(b"key_e", b"val_e")?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open tree in the overlay
+    overlay.open_tree_default(TREE_1, false)?;
+
+    // Insert some values to the overlay
+    overlay.insert(TREE_1, b"key_b", b"val_b")?;
+    overlay.insert(TREE_1, b"key_d", b"val_d")?;
+    overlay.insert(TREE_1, b"key_e", b"val_ee")?;
+    overlay.insert(TREE_1, b"key_f", b"val_f")?;
+
+    // Remove some values from the overlay
+    overlay.remove(TREE_1, b"key_c")?;
+    overlay.remove(TREE_1, b"key_d")?;
+
+    // Iterate overlay to verify sequence
+    let expected_sequence = [
+        (b"key_a".to_vec(), b"val_a".to_vec()),
+        (b"key_b".to_vec(), b"val_b".to_vec()),
+        (b"key_e".to_vec(), b"val_ee".to_vec()),
+        (b"key_f".to_vec(), b"val_f".to_vec()),
+    ];
+    for (index, record) in overlay.iter(TREE_1)?.enumerate() {
+        assert_eq!(record?, expected_sequence[index]);
+    }
+
+    Ok(())
+}

+ 105 - 0
tests/database_overlay_checkpoint.rs

@@ -0,0 +1,105 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of a [`DatabaseOverlay`] on top of an entire
+//! [`Database`] instance, and perform checkpoints and writes to verify
+//! overlay's cache checkpoint functionality.
+
+use kvdb_overlay::{Database, DatabaseOverlay, Result};
+
+const TREE: &str = "_tree";
+const NEW_TREE: &str = "_new_tree";
+
+#[test]
+fn database_overlay_checkpoint() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open tree in the overlay
+    overlay.open_tree_default(TREE, false)?;
+
+    // We keep seperate trees for validation
+    let tree = db.open_tree_default(TREE)?;
+
+    // Insert some values to the overlay
+    overlay.insert(TREE, b"key_a", b"val_a")?;
+    overlay.insert(TREE, b"key_b", b"val_b")?;
+    overlay.insert(TREE, b"key_c", b"val_c")?;
+
+    // Verify they are in the overlay
+    assert_eq!(overlay.get(TREE, b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay.get(TREE, b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay.get(TREE, b"key_c")?, Some(b"val_c".into()));
+
+    // Verify they are not in the database
+    assert_eq!(tree.get(b"key_a")?, None);
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, None);
+
+    // Now we create an overlay checkpoint
+    overlay.checkpoint();
+
+    // We add some more values to the overlay
+    overlay.insert(TREE, b"key_d", b"val_d")?;
+    overlay.insert(TREE, b"key_e", b"val_e")?;
+    overlay.insert(TREE, b"key_f", b"val_f")?;
+
+    // Verify they are in the overlay
+    assert_eq!(overlay.get(TREE, b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(overlay.get(TREE, b"key_e")?, Some(b"val_e".into()));
+    assert_eq!(overlay.get(TREE, b"key_f")?, Some(b"val_f".into()));
+
+    // Verify they are not in the database
+    assert_eq!(tree.get(b"key_d")?, None);
+    assert_eq!(tree.get(b"key_e")?, None);
+    assert_eq!(tree.get(b"key_f")?, None);
+
+    // We also create a new tree
+    overlay.open_tree_default(NEW_TREE, false)?;
+
+    // We assume something went wrong, so we revert to last checkpoint
+    overlay.revert_to_checkpoint();
+
+    // And drop the new tree we created
+    db.drop_tree(NEW_TREE)?;
+
+    // Now execute all tree batches in the overlay
+    overlay.apply()?;
+
+    // Don't forget to flush
+    db.flush_default_mode()?;
+
+    // Verify the database contains pre-checkpoint keys
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+
+    // Verify the database doesn't contains keys after checkpoint
+    assert_eq!(tree.get(b"key_d")?, None);
+    assert_eq!(tree.get(b"key_e")?, None);
+    assert_eq!(tree.get(b"key_f")?, None);
+
+    // Verify the database doesn't contain the new tree we created
+    // after checkpoint
+    assert!(!db.tree_names()?.contains(&NEW_TREE.into()));
+
+    Ok(())
+}

+ 124 - 0
tests/database_overlay_clone.rs

@@ -0,0 +1,124 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of a [`DatabaseOverlay`] on top of an entire
+//! [`Database`] instance, and clone it to verify writes on the cloned
+//! overlay's cache do not affect the original one.
+
+use kvdb_overlay::{Database, DatabaseOverlay, Result};
+
+const TREE: &str = "_tree";
+
+#[test]
+fn database_overlay_clone() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open tree in the overlay
+    overlay.open_tree_default(TREE, false)?;
+
+    // Check overlay tree is empty
+    assert!(overlay.is_empty(TREE)?);
+
+    // Check last value is `None`
+    assert_eq!(overlay.last(TREE)?, None);
+
+    // We keep seperate tree for validation
+    let tree = db.open_tree_default(TREE)?;
+
+    // Insert some values to the overlay
+    overlay.insert(TREE, b"key_a", b"val_a")?;
+    overlay.insert(TREE, b"key_b", b"val_b")?;
+    overlay.insert(TREE, b"key_c", b"val_c")?;
+
+    // Verify they are in the overlay
+    assert_eq!(overlay.get(TREE, b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay.get(TREE, b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay.get(TREE, b"key_c")?, Some(b"val_c".into()));
+
+    // Check overlay tree is not empty
+    assert!(!overlay.is_empty(TREE)?);
+
+    // Check its last value
+    assert_eq!(
+        overlay.last(TREE)?,
+        Some((b"key_c".into(), b"val_c".into()))
+    );
+
+    // Verify they are not in the database
+    assert_eq!(tree.get(b"key_a")?, None);
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, None);
+
+    // Clone the overlay
+    let mut overlay_clone = overlay.clone();
+
+    // Check cloned overlay tree is not empty
+    assert!(!overlay_clone.is_empty(TREE)?);
+
+    // Check its last value
+    assert_eq!(
+        overlay_clone.last(TREE)?,
+        Some((b"key_c".into(), b"val_c".into()))
+    );
+
+    // Insert some values to the cloned overlay
+    overlay_clone.insert(TREE, b"key_d", b"val_d")?;
+    overlay_clone.insert(TREE, b"key_e", b"val_e")?;
+    overlay_clone.insert(TREE, b"key_f", b"val_f")?;
+
+    // Verify all records are in the cloned overlay
+    assert_eq!(overlay_clone.get(TREE, b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay_clone.get(TREE, b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay_clone.get(TREE, b"key_c")?, Some(b"val_c".into()));
+    assert_eq!(overlay_clone.get(TREE, b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(overlay_clone.get(TREE, b"key_e")?, Some(b"val_e".into()));
+    assert_eq!(overlay_clone.get(TREE, b"key_f")?, Some(b"val_f".into()));
+
+    // Check its last values
+    assert_eq!(
+        overlay_clone.last(TREE)?,
+        Some((b"key_f".into(), b"val_f".into()))
+    );
+
+    // Verify they are not in original overlay or the database
+    assert_eq!(tree.get(b"key_d")?, None);
+    assert_eq!(tree.get(b"key_e")?, None);
+    assert_eq!(tree.get(b"key_f")?, None);
+    assert_eq!(overlay.get(TREE, b"key_d")?, None);
+    assert_eq!(overlay.get(TREE, b"key_e")?, None);
+    assert_eq!(overlay.get(TREE, b"key_f")?, None);
+
+    // We finished processing the cloned overlay, so we can
+    // discard it and apply all tree baches of the original
+    // overlay.
+    overlay.apply()?;
+
+    // Don't forget to flush
+    db.flush_default_mode()?;
+
+    // Verify the database contains keys
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+
+    Ok(())
+}

+ 80 - 0
tests/database_overlay_remove_tree.rs

@@ -0,0 +1,80 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of a [`DatabaseOverlay`] on top of an entire
+//! [`Database`] instance, and perform writes to verify overlay's cache
+//! functionality.
+
+use kvdb_overlay::{Database, DatabaseOverlay, Result};
+
+const TREE_1: &str = "_tree1";
+const TREE_2: &str = "_tree2";
+
+#[test]
+fn database_overlay_remove_tree() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Create tree in the database and insert some values
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    tree_1.insert(b"key_a", b"val_a")?;
+    tree_1.insert(b"key_b", b"val_b")?;
+    tree_1.insert(b"key_c", b"val_c")?;
+
+    // Don't forget to flush
+    db.flush_default_mode()?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open tree in the overlay
+    overlay.open_tree_default(TREE_1, false)?;
+
+    // Verify values are in the overlay
+    assert_eq!(overlay.get(TREE_1, b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay.get(TREE_1, b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay.get(TREE_1, b"key_c")?, Some(b"val_c".into()));
+
+    // Drop tree
+    overlay.drop_tree(TREE_1)?;
+
+    // Try to drop the tree again
+    assert!(overlay.drop_tree(TREE_1).is_err());
+
+    // Try to drop a non existing tree
+    assert!(overlay.drop_tree(TREE_2).is_err());
+
+    // Open the new tree
+    overlay.open_tree_default(TREE_2, false)?;
+
+    // Drop the new tree
+    overlay.drop_tree(TREE_2)?;
+
+    // Now execute all tree batches in the overlay
+    overlay.apply()?;
+
+    // Don't forget to flush
+    db.flush_default_mode()?;
+
+    // Verify the database doesn't contain the trees
+    let db_tree_names = db.tree_names()?;
+    assert!(!db_tree_names.contains(&TREE_1.into()));
+    assert!(!db_tree_names.contains(&TREE_2.into()));
+
+    Ok(())
+}

+ 1777 - 0
tests/database_overlay_state.rs

@@ -0,0 +1,1777 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of a [`DatabaseOverlay`] on top of an entire
+//! [`Database`] instance, and perform diffs and writes to verify
+//! overlay's cache diff functionality.
+
+use kvdb_overlay::{Database, DatabaseOverlay, Result};
+
+const TREE_1: &str = "_tree1";
+const TREE_2: &str = "_tree2";
+const TREE_3: &str = "_tree3";
+const TREE_4: &str = "_tree4";
+const TREE_5: &str = "_tree5";
+const TREE_6: &str = "_tree6";
+
+#[test]
+fn database_overlay_state() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize trees with some values
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    tree_1.insert(b"key_a", b"val_a")?;
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    tree_4.insert(b"key_g", b"val_g")?;
+    tree_4.insert(b"key_j", b"val_j")?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open trees in the overlay
+    overlay.open_tree_default(TREE_1, false)?;
+    overlay.open_tree_default(TREE_3, false)?;
+    overlay.open_tree_default(TREE_4, false)?;
+    overlay.open_tree_default(TREE_6, false)?;
+
+    // Make a vector to keep track of changes
+    let mut sequence = vec![];
+
+    // Perform some changes and grab their differences
+    overlay.insert(TREE_1, b"key_b", b"val_b")?;
+    overlay.insert(TREE_3, b"key_i", b"val_i")?;
+    overlay.insert(TREE_4, b"key_k", b"val_k")?;
+    overlay.remove(TREE_4, b"key_g")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.insert(TREE_1, b"key_b", b"val_bb")?;
+    overlay.remove(TREE_1, b"key_a")?;
+    overlay.open_tree_default(TREE_2, false)?;
+    overlay.insert(TREE_2, b"key_d", b"val_d")?;
+    overlay.insert(TREE_2, b"key_e", b"val_e")?;
+    overlay.drop_tree(TREE_3)?;
+    overlay.insert(TREE_4, b"key_k", b"val_kk")?;
+    overlay.open_tree_default(TREE_5, false)?;
+    overlay.insert(TREE_5, b"key_h", b"val_h")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.insert(TREE_1, b"key_a", b"val_a")?;
+    overlay.remove(TREE_1, b"key_b")?;
+    overlay.insert(TREE_1, b"key_c", b"val_c")?;
+    overlay.remove(TREE_2, b"key_e")?;
+    overlay.insert(TREE_2, b"key_f", b"val_f")?;
+    overlay.insert(TREE_4, b"key_l", b"val_l")?;
+    overlay.drop_tree(TREE_4)?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    // Verify overlay has the correct state
+    assert_eq!(overlay.state.initial_tree_names.len(), 2);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(overlay.state.new_tree_names, [TREE_6, TREE_2, TREE_5]);
+    assert_eq!(overlay.state.caches.len(), 4);
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".to_vec())
+    );
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_c".to_vec())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".into())
+    );
+    let tree_6_cache = overlay.state.caches.get(TREE_6).unwrap();
+    assert!(tree_6_cache.state.cache.is_empty());
+    assert!(tree_6_cache.state.removed.is_empty());
+    let tree_2_cache = overlay.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_d".as_slice()),
+        Some(&b"val_d".to_vec())
+    );
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".to_vec())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_5_cache = overlay.state.caches.get(TREE_5).unwrap();
+    assert_eq!(tree_5_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_5_cache.state.cache.get(b"key_h".as_slice()),
+        Some(&b"val_h".to_vec())
+    );
+    assert!(tree_5_cache.state.removed.is_empty());
+    assert_eq!(overlay.state.dropped_trees.len(), 2);
+    let dropped_tree_3_cache = overlay.state.dropped_trees.get(TREE_3).unwrap();
+    assert!(dropped_tree_3_cache.cache.is_empty());
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    let dropped_tree_4_cache = overlay.state.dropped_trees.get(TREE_4).unwrap();
+    assert_eq!(dropped_tree_4_cache.cache.len(), 2);
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_g".as_slice()),
+        Some(&(None, b"val_g".into()))
+    );
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_j".as_slice()),
+        Some(&(None, b"val_j".into()))
+    );
+    assert!(dropped_tree_4_cache.removed.is_empty());
+
+    // Verify diffs sequence is correct
+    assert_eq!(sequence.len(), 3);
+
+    assert_eq!(sequence[0].initial_tree_names.len(), 2);
+    assert!(sequence[0].initial_tree_names.contains(&TREE_1.into()));
+    assert!(sequence[0].initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(sequence[0].caches.len(), 4);
+    let (tree_1_cache, drop) = sequence[0].caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.cache.len(), 1);
+    assert_eq!(
+        tree_1_cache.cache.get(b"key_b".as_slice()),
+        Some(&(None, b"val_b".to_vec()))
+    );
+    assert!(tree_1_cache.removed.is_empty());
+    assert!(!drop);
+    let (tree_3_cache, drop) = sequence[0].caches.get(TREE_3).unwrap();
+    assert_eq!(tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".to_vec()))
+    );
+    assert!(tree_3_cache.removed.is_empty());
+    assert!(!drop);
+    let (tree_4_cache, drop) = sequence[0].caches.get(TREE_4).unwrap();
+    assert_eq!(tree_4_cache.cache.len(), 1);
+    assert_eq!(
+        tree_4_cache.cache.get(b"key_k".as_slice()),
+        Some(&(None, b"val_k".to_vec()))
+    );
+    assert_eq!(tree_4_cache.removed.len(), 1);
+    assert_eq!(
+        tree_4_cache.removed.get(b"key_g".as_slice()),
+        Some(&b"val_g".into())
+    );
+    assert!(!drop);
+    let (tree_6_cache, drop) = sequence[0].caches.get(TREE_6).unwrap();
+    assert!(tree_6_cache.cache.is_empty());
+    assert!(tree_6_cache.removed.is_empty());
+    assert!(!drop);
+    assert!(sequence[0].dropped_trees.is_empty());
+    assert_eq!(sequence[0], sequence[0].inverse().inverse());
+
+    assert_eq!(sequence[1].initial_tree_names.len(), 4);
+    assert!(sequence[1].initial_tree_names.contains(&TREE_1.into()));
+    assert!(sequence[1].initial_tree_names.contains(&TREE_4.into()));
+    assert!(sequence[1].initial_tree_names.contains(&TREE_6.into()));
+    assert!(sequence[1].initial_tree_names.contains(&TREE_3.into()));
+    assert_eq!(sequence[1].caches.len(), 4);
+    let (tree_1_cache, drop) = sequence[1].caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.cache.len(), 1);
+    assert_eq!(
+        tree_1_cache.cache.get(b"key_b".as_slice()),
+        Some(&(Some(b"val_b".into()), b"val_bb".into()))
+    );
+    assert_eq!(tree_1_cache.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.removed.get(b"key_a".as_slice()),
+        Some(&b"val_a".into())
+    );
+    assert!(!drop);
+    let (tree_2_cache, drop) = sequence[1].caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.cache.len(), 2);
+    assert_eq!(
+        tree_2_cache.cache.get(b"key_d".as_slice()),
+        Some(&(None, b"val_d".into()))
+    );
+    assert_eq!(
+        tree_2_cache.cache.get(b"key_e".as_slice()),
+        Some(&(None, b"val_e".into()))
+    );
+    assert!(tree_2_cache.removed.is_empty());
+    assert!(!drop);
+    let (tree_4_cache, drop) = sequence[1].caches.get(TREE_4).unwrap();
+    assert_eq!(tree_4_cache.cache.len(), 1);
+    assert_eq!(
+        tree_4_cache.cache.get(b"key_k".as_slice()),
+        Some(&(Some(b"val_k".into()), b"val_kk".into()))
+    );
+    assert!(tree_4_cache.removed.is_empty());
+    assert!(!drop);
+    let (tree_5_cache, drop) = sequence[1].caches.get(TREE_5).unwrap();
+    assert_eq!(tree_5_cache.cache.len(), 1);
+    assert_eq!(
+        tree_5_cache.cache.get(b"key_h".as_slice()),
+        Some(&(None, b"val_h".into()))
+    );
+    assert!(tree_5_cache.removed.is_empty());
+    assert!(!drop);
+    assert_eq!(sequence[1].dropped_trees.len(), 1);
+    let (dropped_tree_3_cache, restored) = sequence[1].dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    assert!(!restored);
+    assert_eq!(sequence[1], sequence[1].inverse().inverse());
+
+    assert_eq!(sequence[2].initial_tree_names.len(), 5);
+    assert!(sequence[2].initial_tree_names.contains(&TREE_1.into()));
+    assert!(sequence[2].initial_tree_names.contains(&TREE_4.into()));
+    assert!(sequence[2].initial_tree_names.contains(&TREE_6.into()));
+    assert!(sequence[2].initial_tree_names.contains(&TREE_2.into()));
+    assert!(sequence[2].initial_tree_names.contains(&TREE_5.into()));
+    assert_eq!(sequence[2].caches.len(), 2);
+    let (tree_1_cache, drop) = sequence[2].caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.cache.get(b"key_a".as_slice()),
+        Some(&(None, b"val_a".into()))
+    );
+    assert_eq!(
+        tree_1_cache.cache.get(b"key_c".as_slice()),
+        Some(&(None, b"val_c".into()))
+    );
+    assert_eq!(tree_1_cache.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.removed.get(b"key_b".as_slice()),
+        Some(&b"val_bb".into())
+    );
+    assert!(!drop);
+    let (tree_2_cache, drop) = sequence[2].caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.cache.len(), 1);
+    assert_eq!(
+        tree_2_cache.cache.get(b"key_f".as_slice()),
+        Some(&(None, b"val_f".into()))
+    );
+    assert_eq!(tree_2_cache.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.removed.get(b"key_e".as_slice()),
+        Some(&b"val_e".into())
+    );
+    assert!(!drop);
+    assert_eq!(sequence[2].dropped_trees.len(), 1);
+    let (dropped_tree_4_cache, restored) = sequence[2].dropped_trees.get(TREE_4).unwrap();
+    assert_eq!(dropped_tree_4_cache.cache.len(), 2);
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_j".as_slice()),
+        Some(&(None, b"val_j".into()))
+    );
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_k".as_slice()),
+        Some(&(Some(b"val_k".into()), b"val_kk".into()))
+    );
+    assert!(dropped_tree_4_cache.removed.is_empty());
+    assert!(!restored);
+    assert_eq!(sequence[2], sequence[2].inverse().inverse());
+
+    // Now we are going to apply each diff and check that the database
+    // and the overlay have been mutated accordingly.
+    // Don't forget to flush.
+    overlay.apply_diff(&sequence[0])?;
+    db.flush_default_mode()?;
+
+    // All trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 6);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_3.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert!(tree_2.is_empty()?);
+    let tree_3 = db.open_tree_default(TREE_3)?;
+    assert_eq!(tree_3.len()?, 1);
+    assert_eq!(tree_3.get(b"key_i")?, Some(b"val_i".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 2);
+    assert_eq!(tree_4.get(b"key_j")?, Some(b"val_j".into()));
+    assert_eq!(tree_4.get(b"key_k")?, Some(b"val_k".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert!(tree_5.is_empty()?);
+    let tree_6 = db.open_tree_default(TREE_6)?;
+    assert!(tree_6.is_empty()?);
+
+    assert_eq!(overlay.state.initial_tree_names.len(), 4);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_3.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_6.into()));
+    assert_eq!(overlay.state.new_tree_names, [TREE_2, TREE_5]);
+    // Tree 6 was stale so it should have been closed
+    assert_eq!(overlay.state.caches.len(), 3);
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".into())
+    );
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_c".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".into())
+    );
+    let tree_2_cache = overlay.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_5_cache = overlay.state.caches.get(TREE_5).unwrap();
+    assert_eq!(tree_5_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_5_cache.state.cache.get(b"key_h".as_slice()),
+        Some(&b"val_h".into())
+    );
+    assert!(tree_5_cache.state.removed.is_empty());
+    assert_eq!(overlay.state.dropped_trees.len(), 2);
+    let dropped_tree_3_cache = overlay.state.dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    let dropped_tree_4_cache = overlay.state.dropped_trees.get(TREE_4).unwrap();
+    assert_eq!(dropped_tree_4_cache.cache.len(), 2);
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_j".as_slice()),
+        Some(&(None, b"val_j".into()))
+    );
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_k".as_slice()),
+        Some(&(None, b"val_k".into()))
+    );
+    assert!(dropped_tree_4_cache.removed.is_empty());
+
+    overlay.apply_diff(&sequence[1])?;
+    db.flush_default_mode()?;
+
+    // All trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 5);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 1);
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_bb".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 2);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_e")?, Some(b"val_e".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 2);
+    assert_eq!(tree_4.get(b"key_j")?, Some(b"val_j".into()));
+    assert_eq!(tree_4.get(b"key_k")?, Some(b"val_kk".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert_eq!(tree_5.len()?, 1);
+    assert_eq!(tree_5.get(b"key_h")?, Some(b"val_h".into()));
+    let tree_6 = db.open_tree_default(TREE_6)?;
+    assert!(tree_6.is_empty()?);
+
+    assert_eq!(overlay.state.initial_tree_names.len(), 5);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_5.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_6.into()));
+    assert!(overlay.state.new_tree_names.is_empty());
+    // Tree 5 was stale so it should have been closed
+    assert_eq!(overlay.state.caches.len(), 2);
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".into())
+    );
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_c".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".into())
+    );
+    let tree_2_cache = overlay.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    assert_eq!(overlay.state.dropped_trees.len(), 1);
+    let dropped_tree_4_cache = overlay.state.dropped_trees.get(TREE_4).unwrap();
+    assert_eq!(dropped_tree_4_cache.cache.len(), 2);
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_j".as_slice()),
+        Some(&(None, b"val_j".into()))
+    );
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_k".as_slice()),
+        Some(&(Some(b"val_k".into()), b"val_kk".into()))
+    );
+    assert!(dropped_tree_4_cache.removed.is_empty());
+
+    overlay.apply_diff(&sequence[2])?;
+    db.flush_default_mode()?;
+
+    // All trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 4);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_c")?, Some(b"val_c".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 2);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_f")?, Some(b"val_f".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert_eq!(tree_5.len()?, 1);
+    assert_eq!(tree_5.get(b"key_h")?, Some(b"val_h".into()));
+    let tree_6 = db.open_tree_default(TREE_6)?;
+    assert!(tree_6.is_empty()?);
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, therefore its safe to keep using it
+    assert_eq!(overlay.state.initial_tree_names.len(), 4);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_5.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_6.into()));
+    assert!(overlay.state.new_tree_names.is_empty());
+    assert!(overlay.state.caches.is_empty());
+    assert!(overlay.state.dropped_trees.is_empty());
+
+    let diff = overlay.diff(&[])?;
+    assert!(diff.caches.is_empty());
+    assert!(diff.dropped_trees.is_empty());
+
+    // We are going to make some changes that we want to revert
+    // using the corresponding inverse diff
+    overlay.open_tree_default(TREE_1, false)?;
+    overlay.insert(TREE_1, b"key_a", b"val_aa")?;
+    overlay.insert(TREE_1, b"key_b", b"val_b")?;
+    overlay.remove(TREE_1, b"key_c")?;
+    overlay.open_tree_default(TREE_2, false)?;
+    overlay.remove(TREE_2, b"key_d")?;
+    overlay.insert(TREE_2, b"key_f", b"val_ff")?;
+    overlay.drop_tree(TREE_5)?;
+
+    // Grab the diff, apply it and verify the database state
+    let diff = overlay.diff(&[])?;
+    overlay.apply_diff(&diff)?;
+    db.flush_default_mode()?;
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 3);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_aa".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 1);
+    assert_eq!(tree_2.get(b"key_f")?, Some(b"val_ff".into()));
+    let tree_6 = db.open_tree_default(TREE_6)?;
+    assert!(tree_6.is_empty()?);
+
+    // Now we grab the diff inverse, apply it and verity the database
+    // state.
+    overlay.apply_diff(&diff.inverse())?;
+    db.flush_default_mode()?;
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 4);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_c")?, Some(b"val_c".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 2);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_f")?, Some(b"val_f".into()));
+    let tree_6 = db.open_tree_default(TREE_6)?;
+    assert!(tree_6.is_empty()?);
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert_eq!(tree_5.len()?, 1);
+    assert_eq!(tree_5.get(b"key_h")?, Some(b"val_h".into()));
+
+    // Now we are going to revert the diffs sequence going backwards
+    // and verify the database state mutates accordingly
+    overlay.apply_diff(&sequence[2].inverse())?;
+    db.flush_default_mode()?;
+
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 5);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 1);
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_bb".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 2);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_e")?, Some(b"val_e".into()));
+    let tree_6 = db.open_tree_default(TREE_6)?;
+    assert!(tree_6.is_empty()?);
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 2);
+    assert_eq!(tree_4.get(b"key_j")?, Some(b"val_j".into()));
+    assert_eq!(tree_4.get(b"key_k")?, Some(b"val_kk".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert_eq!(tree_5.len()?, 1);
+    assert_eq!(tree_5.get(b"key_h")?, Some(b"val_h".into()));
+
+    overlay.apply_diff(&sequence[1].inverse())?;
+    db.flush_default_mode()?;
+
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 4);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+    assert!(db_tree_names.contains(&TREE_3.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_6 = db.open_tree_default(TREE_6)?;
+    assert!(tree_6.is_empty()?);
+    let tree_3 = db.open_tree_default(TREE_3)?;
+    assert_eq!(tree_3.len()?, 1);
+    assert_eq!(tree_3.get(b"key_i")?, Some(b"val_i".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 2);
+    assert_eq!(tree_4.get(b"key_j")?, Some(b"val_j".into()));
+    assert_eq!(tree_4.get(b"key_k")?, Some(b"val_k".into()));
+
+    overlay.apply_diff(&sequence[0].inverse())?;
+    db.flush_default_mode()?;
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, therefore its safe to keep using it
+    assert_eq!(overlay.state.initial_tree_names.len(), 2);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay.state.new_tree_names.is_empty());
+    assert!(overlay.state.caches.is_empty());
+    assert!(overlay.state.dropped_trees.is_empty());
+
+    let diff = overlay.diff(&[])?;
+    assert!(diff.caches.is_empty());
+    assert!(diff.dropped_trees.is_empty());
+
+    // The database has now reverted to its original state
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 2);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 1);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 2);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+    assert_eq!(tree_4.get(b"key_j")?, Some(b"val_j".into()));
+
+    Ok(())
+}
+
+#[test]
+fn database_overlay_rebuild_state() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize trees with some values
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    tree_1.insert(b"key_a", b"val_a")?;
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    tree_4.insert(b"key_g", b"val_g")?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open trees in the overlay
+    overlay.open_tree_default(TREE_1, false)?;
+    overlay.open_tree_default(TREE_3, false)?;
+
+    // Make a vector to keep track of changes
+    let mut sequence = vec![];
+
+    // Perform some changes and grab their differences
+    overlay.insert(TREE_1, b"key_b", b"val_b")?;
+    overlay.insert(TREE_3, b"key_i", b"val_i")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.insert(TREE_1, b"key_b", b"val_bb")?;
+    overlay.remove(TREE_1, b"key_a")?;
+    overlay.open_tree_default(TREE_2, false)?;
+    overlay.insert(TREE_2, b"key_d", b"val_d")?;
+    overlay.insert(TREE_2, b"key_e", b"val_e")?;
+    overlay.drop_tree(TREE_3)?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.insert(TREE_1, b"key_a", b"val_a")?;
+    overlay.remove(TREE_1, b"key_b")?;
+    overlay.insert(TREE_1, b"key_c", b"val_c")?;
+    overlay.remove(TREE_2, b"key_e")?;
+    overlay.insert(TREE_2, b"key_f", b"val_f")?;
+    overlay.drop_tree(TREE_4)?;
+    overlay.open_tree_default(TREE_5, false)?;
+    overlay.insert(TREE_5, b"key_h", b"val_h")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    // Create a different overlay to rebuild
+    // the previous one using the changes sequence
+    let mut overlay2 = DatabaseOverlay::new(&db, vec![])?;
+    // All trees should be present in the database
+    assert_eq!(overlay2.state.initial_tree_names.len(), 5);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_3.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_5.into()));
+    assert!(overlay2.state.new_tree_names.is_empty());
+    assert!(overlay2.state.caches.is_empty());
+    assert!(overlay2.state.dropped_trees.is_empty());
+
+    // Add each diff from the sequence and verify
+    // overlay has been mutated accordingly
+    overlay2.add_diff(&sequence[0])?;
+    assert_eq!(overlay2.state.initial_tree_names.len(), 2);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(overlay2.state.new_tree_names, [TREE_3]);
+    assert_eq!(overlay2.state.caches.len(), 2);
+    let tree_1_cache = overlay2.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_b".into())
+    );
+    assert!(tree_1_cache.state.removed.is_empty());
+    let tree_3_cache = overlay2.state.caches.get(TREE_3).unwrap();
+    assert_eq!(tree_3_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_3_cache.state.cache.get(b"key_i".as_slice()),
+        Some(&b"val_i".into())
+    );
+    assert!(tree_3_cache.state.removed.is_empty());
+    assert!(overlay2.state.dropped_trees.is_empty());
+
+    overlay2.add_diff(&sequence[1])?;
+    assert_eq!(overlay2.state.initial_tree_names.len(), 2);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(overlay2.state.new_tree_names, [TREE_2]);
+    assert_eq!(overlay2.state.caches.len(), 2);
+    let tree_1_cache = overlay2.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_bb".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_a".as_slice()),
+        Some(&b"key_a".into())
+    );
+    let tree_2_cache = overlay2.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_e".as_slice()),
+        Some(&b"val_e".into())
+    );
+    assert!(tree_2_cache.state.removed.is_empty());
+    assert_eq!(overlay2.state.dropped_trees.len(), 1);
+    let dropped_tree_3_cache = overlay2.state.dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+
+    // Deviate and create some new records in the overlay
+    overlay2.insert(TREE_1, b"key_c", b"val_cc")?;
+    overlay2.remove(TREE_2, b"key_e")?;
+    overlay2.open_tree_default(TREE_4, false)?;
+    overlay2.insert(TREE_4, b"key_f", b"val_f")?;
+    overlay2.open_tree_default(TREE_6, false)?;
+    overlay2.insert(TREE_6, b"key_h", b"val_h")?;
+    overlay2.drop_tree(TREE_6)?;
+
+    // Verify overlay2 has the correct state
+    assert_eq!(overlay2.state.initial_tree_names.len(), 2);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(overlay2.state.new_tree_names, [TREE_2]);
+    assert_eq!(overlay2.state.caches.len(), 3);
+    let tree_1_cache = overlay2.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_bb".into())
+    );
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_cc".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_a".as_slice()),
+        Some(&b"key_a".into())
+    );
+    let tree_2_cache = overlay2.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_4_cache = overlay2.state.caches.get(TREE_4).unwrap();
+    assert_eq!(tree_4_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_4_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert!(tree_4_cache.state.removed.is_empty());
+    assert_eq!(overlay2.state.dropped_trees.len(), 2);
+    let dropped_tree_3_cache = overlay2.state.dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    let dropped_tree_6_cache = overlay2.state.dropped_trees.get(TREE_6).unwrap();
+    assert!(dropped_tree_6_cache.cache.is_empty());
+    assert!(dropped_tree_6_cache.removed.is_empty());
+
+    // Now we are going to apply each diff and check that the database
+    // and the overlays have been mutated accordingly.
+    // Don't forget to flush.
+    overlay.apply_diff(&sequence[0])?;
+    overlay2.remove_diff(&sequence[0]);
+    db.flush_default_mode()?;
+
+    // All trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 6);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_3.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert!(tree_2.is_empty()?);
+    let tree_3 = db.open_tree_default(TREE_3)?;
+    assert_eq!(tree_3.len()?, 1);
+    assert_eq!(tree_3.get(b"key_i")?, Some(b"val_i".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert!(tree_5.is_empty()?);
+    let tree_6 = db.open_tree_default(TREE_6)?;
+    assert!(tree_6.is_empty()?);
+
+    assert_eq!(overlay.state.initial_tree_names.len(), 3);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_3.into()));
+    assert_eq!(overlay.state.new_tree_names, [TREE_2, TREE_5]);
+    assert_eq!(overlay.state.caches.len(), 3);
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".into())
+    );
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_c".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".into())
+    );
+    let tree_2_cache = overlay.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_5_cache = overlay.state.caches.get(TREE_5).unwrap();
+    assert_eq!(tree_5_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_5_cache.state.cache.get(b"key_h".as_slice()),
+        Some(&b"val_h".into())
+    );
+    assert!(tree_5_cache.state.removed.is_empty());
+    assert_eq!(overlay.state.dropped_trees.len(), 2);
+    let dropped_tree_3_cache = overlay.state.dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    let dropped_tree_4_cache = overlay.state.dropped_trees.get(TREE_4).unwrap();
+    assert_eq!(dropped_tree_4_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_g".as_slice()),
+        Some(&(None, b"val_g".into()))
+    );
+    assert!(dropped_tree_4_cache.removed.is_empty());
+
+    assert_eq!(overlay2.state.initial_tree_names.len(), 3);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_3.into()));
+    assert_eq!(overlay2.state.new_tree_names, [TREE_2]);
+    assert_eq!(overlay2.state.caches.len(), 3);
+    let tree_1_cache = overlay2.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_bb".into())
+    );
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_cc".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_a".as_slice()),
+        Some(&b"key_a".into())
+    );
+    let tree_2_cache = overlay2.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_4_cache = overlay2.state.caches.get(TREE_4).unwrap();
+    assert_eq!(tree_4_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_4_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert!(tree_4_cache.state.removed.is_empty());
+    assert_eq!(overlay2.state.dropped_trees.len(), 2);
+    let dropped_tree_3_cache = overlay2.state.dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    let dropped_tree_6_cache = overlay2.state.dropped_trees.get(TREE_6).unwrap();
+    assert!(dropped_tree_6_cache.cache.is_empty());
+    assert!(dropped_tree_6_cache.removed.is_empty());
+
+    overlay.apply_diff(&sequence[1])?;
+    overlay2.remove_diff(&sequence[1]);
+    db.flush_default_mode()?;
+
+    // All trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 5);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+    assert!(db_tree_names.contains(&TREE_6.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 1);
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_bb".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 2);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_e")?, Some(b"val_e".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+
+    assert_eq!(overlay.state.initial_tree_names.len(), 3);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(overlay.state.new_tree_names, [TREE_5]);
+    assert_eq!(overlay.state.caches.len(), 3);
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".into())
+    );
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_c".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".into())
+    );
+    let tree_2_cache = overlay.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_5_cache = overlay.state.caches.get(TREE_5).unwrap();
+    assert_eq!(tree_5_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_5_cache.state.cache.get(b"key_h".as_slice()),
+        Some(&b"val_h".into())
+    );
+    assert!(tree_5_cache.state.removed.is_empty());
+    assert_eq!(overlay.state.dropped_trees.len(), 1);
+    let dropped_tree_4_cache = overlay.state.dropped_trees.get(TREE_4).unwrap();
+    assert_eq!(dropped_tree_4_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_4_cache.cache.get(b"key_g".as_slice()),
+        Some(&(None, b"val_g".into()))
+    );
+    assert!(dropped_tree_4_cache.removed.is_empty());
+
+    assert_eq!(overlay2.state.initial_tree_names.len(), 3);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay2.state.new_tree_names.is_empty());
+    assert_eq!(overlay2.state.caches.len(), 3);
+    let tree_1_cache = overlay2.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_cc".into())
+    );
+    assert!(tree_1_cache.state.removed.is_empty());
+    let tree_2_cache = overlay2.state.caches.get(TREE_2).unwrap();
+    assert!(tree_2_cache.state.cache.is_empty());
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_4_cache = overlay2.state.caches.get(TREE_4).unwrap();
+    assert_eq!(tree_4_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_4_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert!(tree_4_cache.state.removed.is_empty());
+    assert_eq!(overlay2.state.dropped_trees.len(), 1);
+    let dropped_tree_6_cache = overlay2.state.dropped_trees.get(TREE_6).unwrap();
+    assert!(dropped_tree_6_cache.cache.is_empty());
+    assert!(dropped_tree_6_cache.removed.is_empty());
+
+    // We chose to follow the second overlay, so we apply its diff
+    sequence[2] = overlay2.diff(&[])?;
+    overlay2.apply_diff(&sequence[2])?;
+    db.flush_default_mode()?;
+
+    // Since we used overlay2, we must drop the new trees from original
+    // overlay that are not present now, and stop using it.
+    db.drop_tree(TREE_5)?;
+
+    // All remaining trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 3);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_bb".into()));
+    assert_eq!(tree_1.get(b"key_c")?, Some(b"val_cc".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 1);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 2);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+    assert_eq!(tree_4.get(b"key_f")?, Some(b"val_f".into()));
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, therefore its safe to keep using it
+    assert_eq!(overlay2.state.initial_tree_names.len(), 3);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay2.state.new_tree_names.is_empty());
+    assert!(overlay2.state.caches.is_empty());
+    assert!(overlay2.state.dropped_trees.is_empty());
+
+    let diff = overlay2.diff(&[])?;
+    assert_eq!(diff.initial_tree_names.len(), 3);
+    assert!(diff.initial_tree_names.contains(&TREE_1.into()));
+    assert!(diff.initial_tree_names.contains(&TREE_2.into()));
+    assert!(diff.initial_tree_names.contains(&TREE_4.into()));
+    assert!(diff.caches.is_empty());
+    assert!(diff.dropped_trees.is_empty());
+
+    // Now we are going to revert the diffs sequence going backwards
+    // and verify overlay state mutates accordingly
+    overlay2.add_diff(&sequence[2].inverse())?;
+    assert_eq!(overlay2.state.initial_tree_names.len(), 3);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay2.state.new_tree_names.is_empty());
+    assert_eq!(overlay2.state.caches.len(), 3);
+    let tree_1_cache = overlay2.state.caches.get(TREE_1).unwrap();
+    assert!(tree_1_cache.state.cache.is_empty());
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_c".as_slice()),
+        Some(&b"key_c".into())
+    );
+    let tree_2_cache = overlay2.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_e".as_slice()),
+        Some(&b"val_e".into())
+    );
+    assert!(tree_2_cache.state.removed.is_empty());
+    let tree_4_cache = overlay2.state.caches.get(TREE_4).unwrap();
+    assert!(tree_4_cache.state.cache.is_empty());
+    assert_eq!(tree_4_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_4_cache.state.removed.get(b"key_f".as_slice()),
+        Some(&b"key_f".into())
+    );
+    assert!(overlay2.state.dropped_trees.is_empty());
+
+    overlay2.add_diff(&sequence[1].inverse())?;
+    assert_eq!(overlay2.state.initial_tree_names.len(), 2);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(overlay2.state.new_tree_names, [TREE_3]);
+    assert_eq!(overlay2.state.caches.len(), 3);
+    let tree_1_cache = overlay2.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".into())
+    );
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_b".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_c".as_slice()),
+        Some(&b"key_c".into())
+    );
+    let tree_4_cache = overlay2.state.caches.get(TREE_4).unwrap();
+    assert!(tree_4_cache.state.cache.is_empty());
+    assert_eq!(tree_4_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_4_cache.state.removed.get(b"key_f".as_slice()),
+        Some(&b"key_f".into())
+    );
+    let tree_3_cache = overlay2.state.caches.get(TREE_3).unwrap();
+    assert_eq!(tree_3_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_3_cache.state.cache.get(b"key_i".as_slice()),
+        Some(&b"val_i".into())
+    );
+    assert!(tree_3_cache.state.removed.is_empty());
+    assert_eq!(overlay2.state.dropped_trees.len(), 1);
+    let dropped_tree_2_cache = overlay2.state.dropped_trees.get(TREE_2).unwrap();
+    assert!(dropped_tree_2_cache.cache.is_empty());
+    assert_eq!(dropped_tree_2_cache.removed.len(), 2);
+    assert_eq!(
+        dropped_tree_2_cache.removed.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(
+        dropped_tree_2_cache.removed.get(b"key_e".as_slice()),
+        Some(&b"val_e".into())
+    );
+
+    overlay2.add_diff(&sequence[0].inverse())?;
+    assert_eq!(overlay2.state.initial_tree_names.len(), 2);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay2.state.new_tree_names.is_empty());
+    assert_eq!(overlay2.state.caches.len(), 2);
+    let tree_1_cache = overlay2.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".into())
+    );
+    assert_eq!(tree_1_cache.state.removed.len(), 2);
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".into())
+    );
+    assert_eq!(
+        tree_1_cache.state.removed.get(b"key_c".as_slice()),
+        Some(&b"key_c".into())
+    );
+    let tree_4_cache = overlay2.state.caches.get(TREE_4).unwrap();
+    assert!(tree_4_cache.state.cache.is_empty());
+    assert_eq!(tree_4_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_4_cache.state.removed.get(b"key_f".as_slice()),
+        Some(&b"key_f".into())
+    );
+    assert_eq!(overlay2.state.dropped_trees.len(), 2);
+    let dropped_tree_2_cache = overlay2.state.dropped_trees.get(TREE_2).unwrap();
+    assert!(dropped_tree_2_cache.cache.is_empty());
+    assert_eq!(dropped_tree_2_cache.removed.len(), 2);
+    assert_eq!(
+        dropped_tree_2_cache.removed.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(
+        dropped_tree_2_cache.removed.get(b"key_e".as_slice()),
+        Some(&b"val_e".into())
+    );
+    let dropped_tree_3_cache = overlay2.state.dropped_trees.get(TREE_3).unwrap();
+    assert!(dropped_tree_3_cache.cache.is_empty());
+    assert_eq!(dropped_tree_3_cache.removed.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.removed.get(b"key_i".as_slice()),
+        Some(&b"val_i".into())
+    );
+
+    // We are now going to apply the overlay and remove the complete diff
+    let diff = overlay2.diff(&[])?;
+    overlay2.apply()?;
+    overlay2.remove_diff(&diff);
+    db.flush_default_mode()?;
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, therefore its safe to keep using it
+    assert_eq!(overlay2.state.initial_tree_names.len(), 2);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay2.state.new_tree_names.is_empty());
+    assert!(overlay2.state.caches.is_empty());
+    assert!(overlay2.state.dropped_trees.is_empty());
+
+    let diff = overlay2.diff(&[])?;
+    assert!(diff.caches.is_empty());
+    assert!(diff.dropped_trees.is_empty());
+
+    // Database has now reverted to its original state
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 2);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 1);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+
+    Ok(())
+}
+
+#[test]
+fn database_overlay_protected_trees() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize trees with some values
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    tree_1.insert(b"key_a", b"val_a")?;
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    tree_4.insert(b"key_g", b"val_g")?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![TREE_1.into(), TREE_4.into()])?;
+
+    // Open trees in the overlay
+    overlay.open_tree_default(TREE_1, false)?;
+    overlay.open_tree_default(TREE_3, false)?;
+
+    // Try to remove protected trees
+    assert!(overlay.drop_tree(TREE_1).is_err());
+    assert!(overlay.drop_tree(TREE_4).is_err());
+
+    // Make a vector to keep track of changes
+    let mut sequence = vec![];
+
+    // Perform some changes and grab their differences
+    overlay.insert(TREE_1, b"key_b", b"val_b")?;
+    overlay.insert(TREE_3, b"key_i", b"val_i")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.open_tree_default(TREE_2, false)?;
+    overlay.insert(TREE_2, b"key_d", b"val_d")?;
+    overlay.insert(TREE_2, b"key_e", b"val_e")?;
+    overlay.open_tree_default(TREE_5, false)?;
+    overlay.insert(TREE_5, b"key_h", b"val_h")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.drop_tree(TREE_3)?;
+    overlay.remove(TREE_2, b"key_e")?;
+    overlay.insert(TREE_2, b"key_f", b"val_f")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    // Verify overlay has the correct state
+    assert_eq!(overlay.state.initial_tree_names.len(), 2);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(overlay.state.new_tree_names, [TREE_2, TREE_5]);
+    assert_eq!(overlay.state.caches.len(), 3);
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_1_cache.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_b".into())
+    );
+    assert!(tree_1_cache.state.removed.is_empty());
+    let tree_2_cache = overlay.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_5_cache = overlay.state.caches.get(TREE_5).unwrap();
+    assert_eq!(tree_5_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_5_cache.state.cache.get(b"key_h".as_slice()),
+        Some(&b"val_h".into())
+    );
+    assert!(tree_5_cache.state.removed.is_empty());
+    assert_eq!(overlay.state.dropped_trees.len(), 1);
+    let dropped_tree_3_cache = overlay.state.dropped_trees.get(TREE_3).unwrap();
+    assert!(dropped_tree_3_cache.cache.is_empty());
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    assert_eq!(overlay.state.protected_tree_names.len(), 2);
+    assert!(overlay.state.protected_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.protected_tree_names.contains(&TREE_4.into()));
+
+    // Verify diffs sequence is correct
+    assert_eq!(sequence.len(), 3);
+
+    assert_eq!(sequence[0].initial_tree_names.len(), 2);
+    assert!(sequence[0].initial_tree_names.contains(&TREE_1.into()));
+    assert!(sequence[0].initial_tree_names.contains(&TREE_4.into()));
+    assert_eq!(sequence[0].caches.len(), 2);
+    let (tree_1_cache, drop) = sequence[0].caches.get(TREE_1).unwrap();
+    assert_eq!(tree_1_cache.cache.len(), 1);
+    assert_eq!(
+        tree_1_cache.cache.get(b"key_b".as_slice()),
+        Some(&(None, b"val_b".into()))
+    );
+    assert!(tree_1_cache.removed.is_empty());
+    assert!(!drop);
+    let (tree_3_cache, drop) = sequence[0].caches.get(TREE_3).unwrap();
+    assert_eq!(tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(tree_3_cache.removed.is_empty());
+    assert!(!drop);
+    assert!(sequence[0].dropped_trees.is_empty());
+    assert_eq!(sequence[0], sequence[0].inverse().inverse());
+
+    assert_eq!(sequence[1].initial_tree_names.len(), 3);
+    assert!(sequence[1].initial_tree_names.contains(&TREE_1.into()));
+    assert!(sequence[1].initial_tree_names.contains(&TREE_4.into()));
+    assert!(sequence[1].initial_tree_names.contains(&TREE_3.into()));
+    assert_eq!(sequence[1].caches.len(), 2);
+    let (tree_2_cache, drop) = sequence[1].caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.cache.len(), 2);
+    assert_eq!(
+        tree_2_cache.cache.get(b"key_d".as_slice()),
+        Some(&(None, b"val_d".into()))
+    );
+    assert_eq!(
+        tree_2_cache.cache.get(b"key_e".as_slice()),
+        Some(&(None, b"val_e".into()))
+    );
+    assert!(tree_2_cache.removed.is_empty());
+    assert!(!drop);
+    let (tree_5_cache, drop) = sequence[1].caches.get(TREE_5).unwrap();
+    assert_eq!(tree_5_cache.cache.len(), 1);
+    assert_eq!(
+        tree_5_cache.cache.get(b"key_h".as_slice()),
+        Some(&(None, b"val_h".into()))
+    );
+    assert!(tree_5_cache.removed.is_empty());
+    assert!(!drop);
+    assert!(sequence[1].dropped_trees.is_empty());
+    assert_eq!(sequence[1], sequence[1].inverse().inverse());
+
+    assert_eq!(sequence[2].initial_tree_names.len(), 5);
+    assert!(sequence[2].initial_tree_names.contains(&TREE_1.into()));
+    assert!(sequence[2].initial_tree_names.contains(&TREE_4.into()));
+    assert!(sequence[2].initial_tree_names.contains(&TREE_3.into()));
+    assert!(sequence[2].initial_tree_names.contains(&TREE_2.into()));
+    assert!(sequence[2].initial_tree_names.contains(&TREE_5.into()));
+    assert_eq!(sequence[2].caches.len(), 1);
+    let (tree_2_cache, drop) = sequence[2].caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.cache.len(), 1);
+    assert_eq!(
+        tree_2_cache.cache.get(b"key_f".as_slice()),
+        Some(&(None, b"val_f".into()))
+    );
+    assert_eq!(tree_2_cache.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.removed.get(b"key_e".as_slice()),
+        Some(&b"val_e".into())
+    );
+    assert!(!drop);
+    assert_eq!(sequence[2].dropped_trees.len(), 1);
+    let (dropped_tree_3_cache, restored) = sequence[2].dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    assert!(!restored);
+    assert_eq!(sequence[2], sequence[2].inverse().inverse());
+
+    // Now we are going to apply each diff and check that the database
+    // and the overlay have been mutated accordingly.
+    // Don't forget to flush.
+    overlay.apply_diff(&sequence[0])?;
+    db.flush_default_mode()?;
+
+    // All trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 5);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_3.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert!(tree_2.is_empty()?);
+    let tree_3 = db.open_tree_default(TREE_3)?;
+    assert_eq!(tree_3.len()?, 1);
+    assert_eq!(tree_3.get(b"key_i")?, Some(b"val_i".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert!(tree_5.is_empty()?);
+
+    assert_eq!(overlay.state.initial_tree_names.len(), 3);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_3.into()));
+    assert_eq!(overlay.state.new_tree_names, [TREE_2, TREE_5]);
+    assert_eq!(overlay.state.caches.len(), 3);
+    // Tree 1 here became stale, but since its protected it just got reset
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert!(tree_1_cache.state.cache.is_empty());
+    assert!(tree_1_cache.state.removed.is_empty());
+    let tree_2_cache = overlay.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 2);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_d".as_slice()),
+        Some(&b"val_d".into())
+    );
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    let tree_5_cache = overlay.state.caches.get(TREE_5).unwrap();
+    assert_eq!(tree_5_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_5_cache.state.cache.get(b"key_h".as_slice()),
+        Some(&b"val_h".into())
+    );
+    assert!(tree_5_cache.state.removed.is_empty());
+    assert_eq!(overlay.state.dropped_trees.len(), 1);
+    let dropped_tree_3_cache = overlay.state.dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    assert_eq!(overlay.state.protected_tree_names.len(), 2);
+    assert!(overlay.state.protected_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.protected_tree_names.contains(&TREE_4.into()));
+
+    overlay.apply_diff(&sequence[1])?;
+    db.flush_default_mode()?;
+
+    // All trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 5);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_3.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 2);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_e")?, Some(b"val_e".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert_eq!(tree_5.len()?, 1);
+    assert_eq!(tree_5.get(b"key_h")?, Some(b"val_h".into()));
+
+    assert_eq!(overlay.state.initial_tree_names.len(), 5);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_3.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_5.into()));
+    assert!(overlay.state.new_tree_names.is_empty());
+    // Tree 5 was stale so it should have been closed
+    assert_eq!(overlay.state.caches.len(), 2);
+    // Tree 1 reference stays alive
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert!(tree_1_cache.state.cache.is_empty());
+    assert!(tree_1_cache.state.removed.is_empty());
+    let tree_2_cache = overlay.state.caches.get(TREE_2).unwrap();
+    assert_eq!(tree_2_cache.state.cache.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.cache.get(b"key_f".as_slice()),
+        Some(&b"val_f".into())
+    );
+    assert_eq!(tree_2_cache.state.removed.len(), 1);
+    assert_eq!(
+        tree_2_cache.state.removed.get(b"key_e".as_slice()),
+        Some(&b"key_e".into())
+    );
+    assert_eq!(overlay.state.dropped_trees.len(), 1);
+    let dropped_tree_3_cache = overlay.state.dropped_trees.get(TREE_3).unwrap();
+    assert_eq!(dropped_tree_3_cache.cache.len(), 1);
+    assert_eq!(
+        dropped_tree_3_cache.cache.get(b"key_i".as_slice()),
+        Some(&(None, b"val_i".into()))
+    );
+    assert!(dropped_tree_3_cache.removed.is_empty());
+    assert_eq!(overlay.state.protected_tree_names.len(), 2);
+    assert!(overlay.state.protected_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.protected_tree_names.contains(&TREE_4.into()));
+
+    overlay.apply_diff(&sequence[2])?;
+    db.flush_default_mode()?;
+
+    // All trees should be present in the database
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 4);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 2);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_f")?, Some(b"val_f".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert_eq!(tree_5.len()?, 1);
+    assert_eq!(tree_5.get(b"key_h")?, Some(b"val_h".into()));
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, just the protected opened references,
+    // therefore its safe to keep using it
+    assert_eq!(overlay.state.initial_tree_names.len(), 4);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_5.into()));
+    assert!(overlay.state.new_tree_names.is_empty());
+    // Tree 1 reference stays alive
+    assert_eq!(overlay.state.caches.len(), 1);
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert!(tree_1_cache.state.cache.is_empty());
+    assert!(tree_1_cache.state.removed.is_empty());
+    assert!(overlay.state.dropped_trees.is_empty());
+    assert_eq!(overlay.state.protected_tree_names.len(), 2);
+    assert!(overlay.state.protected_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.protected_tree_names.contains(&TREE_4.into()));
+
+    let diff = overlay.diff(&[])?;
+    // Even if Tree 1 reference is alive, it produces no diff over the
+    // tree.
+    assert!(diff.caches.is_empty());
+    assert!(diff.dropped_trees.is_empty());
+
+    // Now we are going to create an overlay where Tree 1 is not
+    // protected, drop it, produce its diff and try to apply it to the
+    // original overlay.
+    let mut overlay2 = DatabaseOverlay::new(&db, vec![])?;
+    overlay2.drop_tree(TREE_1)?;
+    assert_eq!(overlay2.state.initial_tree_names.len(), 4);
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_2.into()));
+    assert!(overlay2.state.initial_tree_names.contains(&TREE_5.into()));
+    assert!(overlay2.state.new_tree_names.is_empty());
+    assert!(overlay2.state.caches.is_empty());
+    assert_eq!(overlay2.state.dropped_trees.len(), 1);
+    let dropped_tree_1_cache = overlay2.state.dropped_trees.get(TREE_1).unwrap();
+    assert_eq!(dropped_tree_1_cache.cache.len(), 2);
+    assert_eq!(
+        dropped_tree_1_cache.cache.get(b"key_a".as_slice()),
+        Some(&(None, b"val_a".into()))
+    );
+    assert_eq!(
+        dropped_tree_1_cache.cache.get(b"key_b".as_slice()),
+        Some(&(None, b"val_b".into()))
+    );
+    assert!(dropped_tree_1_cache.removed.is_empty());
+    assert!(overlay2.state.protected_tree_names.is_empty());
+    let diff = overlay2.diff(&[])?;
+    assert!(diff.caches.is_empty());
+    assert_eq!(diff.dropped_trees.len(), 1);
+    let (dropped_tree_1_cache, restored) = diff.dropped_trees.get(TREE_1).unwrap();
+    assert_eq!(dropped_tree_1_cache.cache.len(), 2);
+    assert_eq!(
+        dropped_tree_1_cache.cache.get(b"key_a".as_slice()),
+        Some(&(None, b"val_a".into()))
+    );
+    assert_eq!(
+        dropped_tree_1_cache.cache.get(b"key_b".as_slice()),
+        Some(&(None, b"val_b".into()))
+    );
+    assert!(dropped_tree_1_cache.removed.is_empty());
+    assert!(!restored);
+    assert_eq!(diff, diff.inverse().inverse());
+    assert!(overlay.apply_diff(&diff).is_err());
+
+    // Now we are going to revert the diffs sequence going backwards
+    // and verify the database state mutates accordingly
+    overlay.apply_diff(&sequence[2].inverse())?;
+    db.flush_default_mode()?;
+
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 5);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_2.into()));
+    assert!(db_tree_names.contains(&TREE_3.into()));
+    assert!(db_tree_names.contains(&TREE_5.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    assert_eq!(tree_2.len()?, 2);
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_e")?, Some(b"val_e".into()));
+    let tree_3 = db.open_tree_default(TREE_3)?;
+    assert_eq!(tree_3.len()?, 1);
+    assert_eq!(tree_3.get(b"key_i")?, Some(b"val_i".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+    let tree_5 = db.open_tree_default(TREE_5)?;
+    assert_eq!(tree_5.len()?, 1);
+    assert_eq!(tree_5.get(b"key_h")?, Some(b"val_h".into()));
+
+    overlay.apply_diff(&sequence[1].inverse())?;
+    db.flush_default_mode()?;
+
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 3);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+    assert!(db_tree_names.contains(&TREE_3.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 2);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    let tree_3 = db.open_tree_default(TREE_3)?;
+    assert_eq!(tree_3.len()?, 1);
+    assert_eq!(tree_3.get(b"key_i")?, Some(b"val_i".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+
+    overlay.apply_diff(&sequence[0].inverse())?;
+    db.flush_default_mode()?;
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, therefore its safe to keep using it
+    assert_eq!(overlay.state.initial_tree_names.len(), 2);
+    assert!(overlay.state.initial_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.initial_tree_names.contains(&TREE_4.into()));
+    assert!(overlay.state.new_tree_names.is_empty());
+    // Tree 1 reference stays alive
+    assert_eq!(overlay.state.caches.len(), 1);
+    let tree_1_cache = overlay.state.caches.get(TREE_1).unwrap();
+    assert!(tree_1_cache.state.cache.is_empty());
+    assert!(tree_1_cache.state.removed.is_empty());
+    assert!(overlay.state.dropped_trees.is_empty());
+    assert_eq!(overlay.state.protected_tree_names.len(), 2);
+    assert!(overlay.state.protected_tree_names.contains(&TREE_1.into()));
+    assert!(overlay.state.protected_tree_names.contains(&TREE_4.into()));
+
+    let diff = overlay.diff(&[])?;
+    assert!(diff.caches.is_empty());
+    assert!(diff.dropped_trees.is_empty());
+
+    // The database has now reverted to its original state
+    let db_tree_names = db.tree_names()?;
+    assert_eq!(db_tree_names.len(), 2);
+    assert!(db_tree_names.contains(&TREE_1.into()));
+    assert!(db_tree_names.contains(&TREE_4.into()));
+
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    assert_eq!(tree_1.len()?, 1);
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    let tree_4 = db.open_tree_default(TREE_4)?;
+    assert_eq!(tree_4.len()?, 1);
+    assert_eq!(tree_4.get(b"key_g")?, Some(b"val_g".into()));
+
+    Ok(())
+}

+ 127 - 0
tests/new_tree_remove.rs

@@ -0,0 +1,127 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of a [`DatabaseOverlay`] on top of an entire
+//! [`Database`] instance, generate a new tree that doesn't exist in
+//! the database, perform writes to verify overlay's cache
+//! functionality, and verify that scratching everything will not write
+//! the new tree.
+
+use kvdb_overlay::{Database, DatabaseOverlay, Result};
+
+const TREE: &str = "_tree";
+
+#[test]
+fn new_tree_remove() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize overlay
+    let mut overlay = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open tree in the overlay
+    overlay.open_tree_default(TREE, false)?;
+
+    // We keep seperate tree for validation
+    let tree = db.open_tree_default(TREE)?;
+
+    // Insert some values to the overlay
+    overlay.insert(TREE, b"key_a", b"val_a")?;
+    overlay.insert(TREE, b"key_b", b"val_b")?;
+    overlay.insert(TREE, b"key_c", b"val_c")?;
+
+    // Verify they are in the overlay
+    assert_eq!(overlay.get(TREE, b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay.get(TREE, b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay.get(TREE, b"key_c")?, Some(b"val_c".into()));
+
+    // Verify they are not in the database
+    assert_eq!(tree.get(b"key_a")?, None);
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, None);
+
+    // Now we asume something happened and want to scratch everything
+    overlay.purge_new_trees()?;
+
+    // Don't forget to flush
+    db.flush_default_mode()?;
+
+    // Verify the database doesn't contain the tree
+    assert!(!db.tree_names()?.contains(&TREE.into()));
+
+    Ok(())
+}
+
+#[test]
+fn new_tree_remove_multiple_overlays() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize overlays
+    let mut overlay0 = DatabaseOverlay::new(&db, vec![])?;
+    let mut overlay1 = DatabaseOverlay::new(&db, vec![])?;
+
+    // Open tree in the overlays
+    overlay0.open_tree_default(TREE, false)?;
+    overlay1.open_tree_default(TREE, false)?;
+
+    // We keep seperate tree for validation
+    let tree = db.open_tree_default(TREE)?;
+
+    // Insert some values to the overlays
+    overlay0.insert(TREE, b"key_a", b"val_a")?;
+    overlay0.insert(TREE, b"key_b", b"val_b")?;
+    overlay0.insert(TREE, b"key_c", b"val_c")?;
+    overlay1.insert(TREE, b"key_a", b"val_a")?;
+    overlay1.insert(TREE, b"key_b", b"val_b")?;
+    overlay1.insert(TREE, b"key_c", b"val_c")?;
+
+    // Verify they are in the overlays
+    assert_eq!(overlay0.get(TREE, b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay0.get(TREE, b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay0.get(TREE, b"key_c")?, Some(b"val_c".into()));
+    assert_eq!(overlay1.get(TREE, b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay1.get(TREE, b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay1.get(TREE, b"key_c")?, Some(b"val_c".into()));
+
+    // Verify they are not in the database
+    assert_eq!(tree.get(b"key_a")?, None);
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, None);
+
+    // Now we asume something happened and want to scratch everything
+    // in overlay0
+    overlay0.purge_new_trees()?;
+
+    // Now execute all tree batches in the overlay1
+    overlay1.apply()?;
+
+    // Don't forget to flush
+    db.flush_default_mode()?;
+
+    // Verify the database contain the tree and the keys
+    assert!(db.tree_names()?.contains(&TREE.into()));
+    // We need to re-open the tree since we removed it when we
+    // scratched overlay0 (overlay0.purge_new_trees())
+    let tree = db.open_tree_default(TREE)?;
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+
+    Ok(())
+}

+ 207 - 0
tests/tree_overlay.rs

@@ -0,0 +1,207 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of two [`TreeOverlay`] on top of two [`Tree`]
+//! instances, and perform writes to verify overlay's cache
+//! functionality.
+
+use kvdb_overlay::{Database, Result, TreeOverlay};
+
+const TREE_1: &str = "_tree1";
+const TREE_2: &str = "_tree2";
+
+#[test]
+fn tree_overlay() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize trees and their overlays
+    let tree_1 = db.open_tree_default(TREE_1)?;
+    let tree_2 = db.open_tree_default(TREE_2)?;
+    let mut overlay_1 = TreeOverlay::new(&tree_1);
+    let mut overlay_2 = TreeOverlay::new(&tree_2);
+
+    // Check overlays are empty
+    assert!(overlay_1.is_empty()?);
+    assert!(overlay_2.is_empty()?);
+
+    // Check last value is `None`
+    assert_eq!(overlay_1.last()?, None);
+    assert_eq!(overlay_1.last()?, None);
+
+    // Insert some values to the overlays
+    overlay_1.insert(b"key_a", b"val_a")?;
+    overlay_1.insert(b"key_b", b"val_b")?;
+    overlay_1.insert(b"key_c", b"val_c")?;
+
+    overlay_2.insert(b"key_d", b"val_d")?;
+    overlay_2.insert(b"key_e", b"val_e")?;
+    overlay_2.insert(b"key_f", b"val_f")?;
+
+    // Verify they are in the overlays
+    assert_eq!(overlay_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay_1.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay_1.get(b"key_c")?, Some(b"val_c".into()));
+
+    assert_eq!(overlay_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(overlay_2.get(b"key_e")?, Some(b"val_e".into()));
+    assert_eq!(overlay_2.get(b"key_f")?, Some(b"val_f".into()));
+
+    // Check overlays are not empty
+    assert!(!overlay_1.is_empty()?);
+    assert!(!overlay_2.is_empty()?);
+
+    // Check their last values
+    assert_eq!(overlay_1.last()?, Some((b"key_c".into(), b"val_c".into())));
+    assert_eq!(overlay_2.last()?, Some((b"key_f".into(), b"val_f".into())));
+
+    // Verify they are not in the database
+    assert_eq!(tree_1.get(b"key_a")?, None);
+    assert_eq!(tree_1.get(b"key_b")?, None);
+    assert_eq!(tree_1.get(b"key_c")?, None);
+
+    assert_eq!(tree_2.get(b"key_d")?, None);
+    assert_eq!(tree_2.get(b"key_e")?, None);
+    assert_eq!(tree_2.get(b"key_f")?, None);
+
+    // Now we write all changes to the database
+    db.write_tree_overlays_changes(&[&overlay_1, &overlay_2])?;
+    db.flush_default_mode()?;
+
+    // Verify database contains keys
+    assert_eq!(tree_1.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree_1.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(tree_1.get(b"key_c")?, Some(b"val_c".into()));
+
+    assert_eq!(tree_2.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(tree_2.get(b"key_e")?, Some(b"val_e".into()));
+    assert_eq!(tree_2.get(b"key_f")?, Some(b"val_f".into()));
+
+    Ok(())
+}
+
+#[test]
+fn tree_overlay_last() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize tree and its overlay
+    let tree = db.open_tree_default(TREE_1)?;
+    let mut overlay = TreeOverlay::new(&tree);
+    assert!(overlay.is_empty()?);
+
+    // Check last is None
+    assert_eq!(overlay.last()?, None);
+
+    // Insert a value to the tree
+    tree.insert(b"key_a", b"val_a")?;
+
+    // Check last is the last tree key
+    let last = overlay.last()?.unwrap();
+    assert_eq!(last.0, b"key_a");
+    assert_eq!(last.1, b"val_a");
+
+    // Remove the key from the overlay and check
+    // last is None
+    overlay.remove(b"key_a")?;
+    assert_eq!(overlay.last()?, None);
+
+    // Remove value from the tree
+    tree.remove(b"key_a")?;
+
+    // Insert key in overlay and check its last
+    overlay.insert(b"key_a", b"val_a")?;
+    assert!(tree.is_empty()?);
+    let last = overlay.last()?.unwrap();
+    assert_eq!(last.0, b"key_a");
+    assert_eq!(last.1, b"val_a");
+
+    // Insert a key in the tree that is supposed to be last
+    tree.insert(b"key_b", b"val_b")?;
+    let last = overlay.last()?.unwrap();
+    assert_eq!(last.0, b"key_b");
+    assert_eq!(last.1, b"val_b");
+
+    // Remove the key from the overlay and check
+    // last is the correct one
+    overlay.remove(b"key_b")?;
+    let last = overlay.last()?.unwrap();
+    assert_eq!(last.0, b"key_a");
+    assert_eq!(last.1, b"val_a");
+
+    // Reset the state
+    tree.remove(b"key_b")?;
+    tree.insert(b"key_a", b"val_a")?;
+    tree.insert(b"key_c", b"val_c")?;
+    tree.insert(b"key_d", b"val_d")?;
+
+    let mut overlay = TreeOverlay::new(&tree);
+    overlay.insert(b"key_b", b"val_b")?;
+    overlay.remove(b"key_d")?;
+
+    // Check last is the correct one
+    let last = overlay.last()?.unwrap();
+    assert_eq!(last.0, b"key_c");
+    assert_eq!(last.1, b"val_c");
+
+    Ok(())
+}
+
+#[test]
+fn tree_overlay_iteration() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize tree and its overlay
+    let tree = db.open_tree_default(TREE_1)?;
+    tree.insert(b"key_a", b"val_a")?;
+    tree.insert(b"key_c", b"val_c")?;
+    tree.insert(b"key_e", b"val_e")?;
+    let mut overlay = TreeOverlay::new(&tree);
+
+    // Insert some values to the overlay
+    overlay.insert(b"key_b", b"val_b")?;
+    overlay.insert(b"key_d", b"val_d")?;
+    overlay.insert(b"key_e", b"val_ee")?;
+    overlay.insert(b"key_f", b"val_f")?;
+
+    // Remove some values from the overlay
+    overlay.remove(b"key_c")?;
+    overlay.remove(b"key_d")?;
+
+    // Iterate overlay to verify sequence
+    let expected_sequence = [
+        (b"key_a".to_vec(), b"val_a".to_vec()),
+        (b"key_b".to_vec(), b"val_b".to_vec()),
+        (b"key_e".to_vec(), b"val_ee".to_vec()),
+        (b"key_f".to_vec(), b"val_f".to_vec()),
+    ];
+    for (index, record) in overlay.iter().enumerate() {
+        assert_eq!(record?, expected_sequence[index]);
+    }
+
+    // We can even iterate without calling .iter()
+    let mut index = 0;
+    #[allow(clippy::explicit_counter_loop)]
+    for record in &overlay {
+        assert_eq!(record?, expected_sequence[index]);
+        index += 1;
+    }
+
+    Ok(())
+}

+ 87 - 0
tests/tree_overlay_checkpoint.rs

@@ -0,0 +1,87 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of a [`TreeOverlay`] on top of a [`Tree`]
+//! instance, and perform checkpoints and writes to verify overlay's
+//! cache checkpoint functionality.
+
+use kvdb_overlay::{Database, Result, TreeOverlay};
+
+const TREE: &str = "_tree";
+
+#[test]
+fn tree_overlay_checkpoint() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize tree and its overlay
+    let tree = db.open_tree_default(TREE)?;
+    let mut overlay = TreeOverlay::new(&tree);
+
+    // Insert some values to the overlay
+    overlay.insert(b"key_a", b"val_a")?;
+    overlay.insert(b"key_b", b"val_b")?;
+    overlay.insert(b"key_c", b"val_c")?;
+
+    // Verify they are in the overlay
+    assert_eq!(overlay.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(overlay.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(overlay.get(b"key_c")?, Some(b"val_c".into()));
+
+    // Verify they are not in the database
+    assert_eq!(tree.get(b"key_a")?, None);
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, None);
+
+    // Now we create an overlay checkpoint
+    overlay.checkpoint();
+
+    // We add some more values to the overlay
+    overlay.insert(b"key_d", b"val_d")?;
+    overlay.insert(b"key_e", b"val_e")?;
+    overlay.insert(b"key_f", b"val_f")?;
+
+    // Verify they are in the overlay
+    assert_eq!(overlay.get(b"key_d")?, Some(b"val_d".into()));
+    assert_eq!(overlay.get(b"key_e")?, Some(b"val_e".into()));
+    assert_eq!(overlay.get(b"key_f")?, Some(b"val_f".into()));
+
+    // Verify they are not in the database
+    assert_eq!(tree.get(b"key_d")?, None);
+    assert_eq!(tree.get(b"key_e")?, None);
+    assert_eq!(tree.get(b"key_f")?, None);
+
+    // We assume something went wrong, so we revert to last checkpoint
+    overlay.revert_to_checkpoint();
+
+    // Now we write it to the database
+    db.write_tree_overlays_changes(&[&overlay])?;
+    db.flush_default_mode()?;
+
+    // Verify database contains pre-checkpoint keys
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+
+    // Verify database doesn't contains keys after checkpoint
+    assert_eq!(tree.get(b"key_d")?, None);
+    assert_eq!(tree.get(b"key_e")?, None);
+    assert_eq!(tree.get(b"key_f")?, None);
+
+    Ok(())
+}

+ 505 - 0
tests/tree_overlay_state.rs

@@ -0,0 +1,505 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2026-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+//! Simulate the creation of a [`TreeOverlay`] on top of a [`Tree`]
+//! instance, and perform diffs and writes to verify overlay's cache
+//! diff functionality.
+
+use kvdb_overlay::{Database, Result, TreeOverlay};
+
+const TREE: &str = "_tree";
+
+#[test]
+fn tree_overlay_state() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize tree with some values and its overlay
+    let tree = db.open_tree_default(TREE)?;
+    tree.insert(b"key_a", b"val_a")?;
+    let mut overlay = TreeOverlay::new(&tree);
+    assert!(!overlay.is_empty()?);
+
+    // Make a vector to keep track of changes
+    let mut sequence = vec![];
+
+    // Perform some changes and grab their differences
+    overlay.insert(b"key_b", b"val_b")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.insert(b"key_b", b"val_bb")?;
+    overlay.remove(b"key_a")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.insert(b"key_a", b"val_a")?;
+    overlay.remove(b"key_b")?;
+    overlay.insert(b"key_c", b"val_c")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    // Verify overlay has the correct state
+    assert_eq!(overlay.state.cache.len(), 2);
+    assert_eq!(
+        overlay.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".to_vec())
+    );
+    assert_eq!(
+        overlay.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_c".to_vec())
+    );
+    assert_eq!(overlay.state.removed.len(), 1);
+    assert_eq!(
+        overlay.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".to_vec())
+    );
+
+    // Verify diffs sequence is correct
+    assert_eq!(sequence.len(), 3);
+
+    assert_eq!(sequence[0].cache.len(), 1);
+    assert_eq!(
+        sequence[0].cache.get(b"key_b".as_slice()),
+        Some(&(None, b"val_b".to_vec()))
+    );
+    assert!(sequence[0].removed.is_empty());
+    assert_eq!(sequence[0], sequence[0].inverse().inverse());
+
+    assert_eq!(sequence[1].cache.len(), 1);
+    assert_eq!(
+        sequence[1].cache.get(b"key_b".as_slice()),
+        Some(&(Some(b"val_b".to_vec()), b"val_bb".into()))
+    );
+    assert_eq!(sequence[1].removed.len(), 1);
+    assert_eq!(
+        sequence[1].removed.get(b"key_a".as_slice()),
+        Some(&b"val_a".to_vec())
+    );
+    assert_eq!(sequence[1], sequence[1].inverse().inverse());
+
+    assert_eq!(sequence[2].cache.len(), 2);
+    assert_eq!(
+        sequence[2].cache.get(b"key_a".as_slice()),
+        Some(&(None, b"val_a".to_vec()))
+    );
+    assert_eq!(
+        sequence[2].cache.get(b"key_c".as_slice()),
+        Some(&(None, b"val_c".to_vec()))
+    );
+    assert_eq!(sequence[2].removed.len(), 1);
+    assert_eq!(
+        sequence[2].removed.get(b"key_b".as_slice()),
+        Some(&b"val_bb".to_vec())
+    );
+    assert_eq!(sequence[2], sequence[2].inverse().inverse());
+
+    // Now we are going to apply each diff and check that the database
+    // has been mutated accordingly
+    db.write_tree_overlays_diff(&tree, &sequence[0], false)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+    overlay.remove_diff(&sequence[0]);
+
+    db.write_tree_overlays_diff(&tree, &sequence[1], false)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 1);
+    assert_eq!(tree.get(b"key_a")?, None);
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_bb".into()));
+    overlay.remove_diff(&sequence[1]);
+
+    // Since we removed the diffs, current overlay diff must be
+    // the same as the last diff in the sequence
+    let diff = overlay.diff(&[])?;
+    assert_eq!(diff, sequence[2]);
+    // Therefore we can safely use its batch
+    db.write_tree_overlays_changes(&[&overlay])?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+    overlay.remove_diff(&sequence[2]);
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, therefore its safe to keep using it
+    let diff = overlay.diff(&[])?;
+    assert!(diff.cache.is_empty());
+    assert!(diff.removed.is_empty());
+
+    // We are going to make some changes that we want to revert
+    // using the corresponding diff
+    overlay.insert(b"key_a", b"val_aa")?;
+    overlay.insert(b"key_b", b"val_b")?;
+    overlay.remove(b"key_c")?;
+
+    // Grab the diff, apply it and verify tree state
+    let diff = overlay.diff(&[])?;
+    db.write_tree_overlays_diff(&tree, &diff, false)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_aa".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+    assert_eq!(tree.get(b"key_c")?, None);
+
+    // Now we grab the diff revert batch, apply it and verity tree state
+    db.write_tree_overlays_diff(&tree, &diff, true)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+
+    // Now we are going to revert the diffs sequence going backwards
+    // and verify tree state mutates accordingly
+    db.write_tree_overlays_diff(&tree, &sequence[2], true)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 1);
+    assert_eq!(tree.get(b"key_a")?, None);
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_bb".into()));
+
+    db.write_tree_overlays_diff(&tree, &sequence[1], true)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+
+    db.write_tree_overlays_diff(&tree, &sequence[0], true)?;
+    db.flush_default_mode()?;
+
+    // Tree has now reverted to its original state
+    assert_eq!(tree.len()?, 1);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+
+    Ok(())
+}
+
+#[test]
+fn tree_overlay_rebuild_state() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize tree with some values and its overlay
+    let tree = db.open_tree_default(TREE)?;
+    tree.insert(b"key_a", b"val_a")?;
+    let mut overlay = TreeOverlay::new(&tree);
+    assert!(!overlay.is_empty()?);
+
+    // Make two vectors to keep track of changes
+    let mut sequence = vec![];
+    let mut state_sequence = vec![];
+
+    // Perform some changes and grab their differences
+    overlay.insert(b"key_b", b"val_b")?;
+    sequence.push(overlay.diff(&sequence)?);
+    state_sequence.push(overlay.clone());
+
+    overlay.insert(b"key_b", b"val_bb")?;
+    overlay.remove(b"key_a")?;
+    sequence.push(overlay.diff(&sequence)?);
+    state_sequence.push(overlay.clone());
+
+    overlay.insert(b"key_a", b"val_a")?;
+    overlay.remove(b"key_b")?;
+    overlay.insert(b"key_c", b"val_c")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    // Create a different overlay to rebuild
+    // the previous one using the changes sequence
+    let mut overlay2 = TreeOverlay::new(&tree);
+    assert!(!overlay2.is_empty()?);
+
+    // Add each diff from the sequence and verify
+    // overlay has been mutated accordingly
+    overlay2.add_diff(&sequence[0]);
+    assert_eq!(overlay2.state.cache.len(), 1);
+    assert_eq!(
+        overlay2.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_b".to_vec())
+    );
+    assert!(overlay2.state.removed.is_empty());
+    assert_eq!(state_sequence[0].state, overlay2.state);
+
+    overlay2.add_diff(&sequence[1]);
+    assert_eq!(overlay2.state.cache.len(), 1);
+    assert_eq!(
+        overlay2.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_bb".to_vec())
+    );
+    assert_eq!(overlay2.state.removed.len(), 1);
+    assert_eq!(
+        overlay2.state.removed.get(b"key_a".as_slice()),
+        Some(&b"key_a".to_vec())
+    );
+    assert_eq!(state_sequence[1].state, overlay2.state);
+
+    overlay2.add_diff(&sequence[2]);
+    assert_eq!(overlay2.state.cache.len(), 2);
+    assert_eq!(
+        overlay2.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".to_vec())
+    );
+    assert_eq!(
+        overlay2.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_c".to_vec())
+    );
+    assert_eq!(overlay2.state.removed.len(), 1);
+    assert_eq!(
+        overlay2.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".to_vec())
+    );
+    assert_eq!(overlay.state, overlay2.state);
+
+    // Now we are going to apply each diff and check that the database
+    // has been mutated accordingly
+    db.write_tree_overlays_diff(&tree, &sequence[0], false)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+    overlay.remove_diff(&sequence[0]);
+
+    db.write_tree_overlays_diff(&tree, &sequence[1], false)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 1);
+    assert_eq!(tree.get(b"key_a")?, None);
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_bb".into()));
+    overlay.remove_diff(&sequence[1]);
+
+    // Since we removed the diffs, current overlay diff must be
+    // the same as the last diff in the sequence
+    let diff = overlay.diff(&[])?;
+    assert_eq!(diff, sequence[2]);
+    // Therefore we can safely use its batch
+    db.write_tree_overlays_changes(&[&overlay])?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+    overlay.remove_diff(&sequence[2]);
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, therefore its safe to keep using it
+    let diff = overlay.diff(&[])?;
+    assert!(diff.cache.is_empty());
+    assert!(diff.removed.is_empty());
+
+    // Now we are going to add all the inverse diffs in the overlay,
+    // in reverse
+    overlay.add_diff(&sequence[2].inverse());
+    assert_eq!(overlay.state.cache.len(), 1);
+    assert_eq!(
+        overlay.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_bb".to_vec())
+    );
+    assert_eq!(overlay.state.removed.len(), 2);
+    assert_eq!(
+        overlay.state.removed.get(b"key_a".as_slice()),
+        Some(&b"key_a".to_vec())
+    );
+    assert_eq!(
+        overlay.state.removed.get(b"key_c".as_slice()),
+        Some(&b"key_c".to_vec())
+    );
+
+    overlay.add_diff(&sequence[1].inverse());
+    assert_eq!(overlay.state.cache.len(), 2);
+    assert_eq!(
+        overlay.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".to_vec())
+    );
+    assert_eq!(
+        overlay.state.cache.get(b"key_b".as_slice()),
+        Some(&b"val_b".to_vec())
+    );
+    assert_eq!(overlay.state.removed.len(), 1);
+    assert_eq!(
+        overlay.state.removed.get(b"key_c".as_slice()),
+        Some(&b"key_c".to_vec())
+    );
+
+    overlay.add_diff(&sequence[0].inverse());
+    assert_eq!(overlay.state.cache.len(), 1);
+    assert_eq!(
+        overlay.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".to_vec())
+    );
+    assert_eq!(overlay.state.removed.len(), 2);
+    assert_eq!(
+        overlay.state.removed.get(b"key_b".as_slice()),
+        Some(&b"key_b".to_vec())
+    );
+    assert_eq!(
+        overlay.state.removed.get(b"key_c".as_slice()),
+        Some(&b"key_c".to_vec())
+    );
+
+    // Now we are going to apply the overlay and verify that the
+    // Tree has now reverted to its original state
+    db.write_tree_overlays_changes(&[&overlay])?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 1);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+
+    Ok(())
+}
+
+#[test]
+fn tree_overlay_clear_state() -> Result<()> {
+    // Initialize database
+    let (db, _folder) = Database::open_temp()?;
+
+    // Initialize tree with some values and its overlay
+    let tree = db.open_tree_default(TREE)?;
+    tree.insert(b"key_a", b"val_a")?;
+    let mut overlay = TreeOverlay::new(&tree);
+    assert!(!overlay.is_empty()?);
+
+    // Make a vector to keep track of changes
+    let mut sequence = vec![];
+
+    // Perform some changes and grab their differences
+    overlay.insert(b"key_b", b"val_b")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.clear()?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    overlay.insert(b"key_a", b"val_a")?;
+    assert!(overlay.remove(b"key_b").is_err());
+    overlay.insert(b"key_c", b"val_c")?;
+    sequence.push(overlay.diff(&sequence)?);
+
+    // Verify overlay has the correct state
+    assert_eq!(overlay.state.cache.len(), 2);
+    assert_eq!(
+        overlay.state.cache.get(b"key_a".as_slice()),
+        Some(&b"val_a".into())
+    );
+    assert_eq!(
+        overlay.state.cache.get(b"key_c".as_slice()),
+        Some(&b"val_c".into())
+    );
+    assert!(overlay.state.removed.is_empty());
+
+    // Verify diffs sequence is correct
+    assert_eq!(sequence.len(), 3);
+
+    assert_eq!(sequence[0].cache.len(), 1);
+    assert_eq!(
+        sequence[0].cache.get(b"key_b".as_slice()),
+        Some(&(None, b"val_b".to_vec()))
+    );
+    assert!(sequence[0].removed.is_empty());
+    assert_eq!(sequence[0], sequence[0].inverse().inverse());
+
+    assert!(sequence[1].cache.is_empty());
+    assert_eq!(sequence[1].removed.len(), 2);
+    assert_eq!(
+        sequence[1].removed.get(b"key_a".as_slice()),
+        Some(&b"val_a".to_vec())
+    );
+    assert_eq!(
+        sequence[1].removed.get(b"key_b".as_slice()),
+        Some(&b"val_b".to_vec())
+    );
+    assert_eq!(sequence[1], sequence[1].inverse().inverse());
+
+    assert_eq!(sequence[2].cache.len(), 2);
+    assert_eq!(
+        sequence[2].cache.get(b"key_a".as_slice()),
+        Some(&(None, b"val_a".to_vec()))
+    );
+    assert_eq!(
+        sequence[2].cache.get(b"key_c".as_slice()),
+        Some(&(None, b"val_c".to_vec()))
+    );
+    assert!(sequence[2].removed.is_empty());
+    assert_eq!(sequence[2], sequence[2].inverse().inverse());
+
+    // Now we are going to apply each diff and check that the database
+    // has been mutated accordingly
+    db.write_tree_overlays_diff(&tree, &sequence[0], false)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+    overlay.remove_diff(&sequence[0]);
+
+    db.write_tree_overlays_diff(&tree, &sequence[1], false)?;
+    db.flush_default_mode()?;
+    assert!(tree.is_empty()?);
+    overlay.remove_diff(&sequence[1]);
+
+    // Since we removed the diffs, current overlay diff must be
+    // the same as the last diff in the sequence
+    let diff = overlay.diff(&[])?;
+    assert_eq!(diff, sequence[2]);
+    // Therefore we can safely use its batch
+    db.write_tree_overlays_changes(&[&overlay])?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+    overlay.remove_diff(&sequence[2]);
+
+    // Since we removed everything, current overlay must not have
+    // diffs over the tree, therefore its safe to keep using it
+    let diff = overlay.diff(&[])?;
+    assert!(diff.cache.is_empty());
+    assert!(diff.removed.is_empty());
+
+    // We are going to make some changes that we want to revert
+    // using the corresponding diff
+    overlay.clear()?;
+
+    // Grab the diff, apply it and verify tree state
+    let diff = overlay.diff(&[])?;
+    db.write_tree_overlays_diff(&tree, &diff, false)?;
+    db.flush_default_mode()?;
+    assert!(tree.is_empty()?);
+
+    // Now we grab the diff revert batch, apply it and verity tree state
+    db.write_tree_overlays_diff(&tree, &diff, true)?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, None);
+    assert_eq!(tree.get(b"key_c")?, Some(b"val_c".into()));
+
+    // Now we are going to revert the diffs sequence going backwards
+    // and verify tree state mutates accordingly
+    db.write_tree_overlays_diff(&tree, &sequence[2], true)?;
+    db.flush_default_mode()?;
+    assert!(tree.is_empty()?);
+
+    db.write_tree_overlays_diff(&tree, &sequence[1], true)?;
+    db.flush_default_mode()?;
+    assert_eq!(tree.len()?, 2);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+    assert_eq!(tree.get(b"key_b")?, Some(b"val_b".into()));
+
+    db.write_tree_overlays_diff(&tree, &sequence[0], true)?;
+    db.flush_default_mode()?;
+
+    // Tree has now reverted to its original state
+    assert_eq!(tree.len()?, 1);
+    assert_eq!(tree.get(b"key_a")?, Some(b"val_a".into()));
+
+    Ok(())
+}