├── .gitignore ├── Cargo.toml ├── LICENSE.md ├── README.md ├── build.rs ├── doc └── mdbx.md ├── src ├── erigon │ ├── macros.rs │ ├── mod.rs │ ├── models │ │ ├── account.rs │ │ ├── block.rs │ │ ├── log.rs │ │ ├── mod.rs │ │ └── transaction.rs │ ├── tables.rs │ └── utils │ │ ├── consts.rs │ │ └── mod.rs ├── kv │ ├── mod.rs │ ├── tables.rs │ └── traits.rs ├── lib.rs └── txgen.rs └── test └── contracts └── Store.sol /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /data 4 | /build 5 | src/bindings 6 | notes.rs 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erigon-db" 3 | version = "0.1.0" 4 | authors = ["gio256 "] 5 | license = "Apache-2.0" 6 | edition = "2021" 7 | readme = "README.md" 8 | 9 | [lib] 10 | name = "erigon_db" 11 | path = "src/lib.rs" 12 | [[bin]] 13 | name = "txgen" 14 | path = "src/txgen.rs" 15 | required-features = ["txgen"] 16 | 17 | [dependencies] 18 | arrayvec = "0.7" 19 | arrayref = "0.3" 20 | bytes = { version = "1", features = ["serde"] } 21 | roaring = "0.9" 22 | derive_more = "0.99" 23 | eyre = "0.6.5" 24 | ethereum-types = { version = "0.13", features = ["codec"] } 25 | hex-literal = "0.3" 26 | mdbx = { package = "libmdbx", version = "0.1" } 27 | fastrlp = { version = "0.1.2", features = [ "derive", "ethereum-types", "std" ] } 28 | serde = { version = "1", default-features = false, features = ["derive"] } 29 | serde_json = "1" 30 | tiny-keccak = "2.0" 31 | seq-macro = "0.3" 32 | 33 | tokio = { version = "1.20", features = ["macros", "rt-multi-thread"], optional = true } 34 | ethers = { git = "https://github.com/gakonst/ethers-rs", optional = true } 35 | hex = { version = "0.4.3", default-features = false, features = ["std"], optional = true } 36 | paste = { version = "1.0.6", optional = true } 37 | serde_cbor = "0.11.2" 38 | 39 | [dev-dependencies] 40 | tempfile = "3" 41 | once_cell = "1" 42 | hex = { version = "0.4.3", default-features = false, features = ["std"] } 43 | 44 | [build-dependencies] 45 | ethers = { git = "https://github.com/gakonst/ethers-rs", features = ["ethers-solc", "abigen"] } 46 | eyre = "0.6.6" 47 | semver = "1.0.4" 48 | serde_json = "1.0.64" 49 | Inflector = "0.11" 50 | hex = { version = "0.4.3", default-features = false, features = ["std"] } 51 | 52 | [features] 53 | txgen = ["tokio", "ethers", "hex", "paste"] 54 | ethers-types = ["ethers"] 55 | 56 | [patch.crates-io] 57 | libmdbx = { git = "https://github.com/gio256/libmdbx-rs", branch = "develop" } 58 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Overview 2 | Fully typed access to the [Erigon](https://github.com/ledgerwatch/erigon) database in rust. 3 | 4 | ```rust 5 | use erigon_db::{Erigon, env_open}; 6 | use ethereum_types::Address; 7 | 8 | fn main() -> eyre::Result<()> { 9 | let path = std::path::Path::new(env!("ERIGON_CHAINDATA")); 10 | 11 | // Open an mdbx environment and begin a read-only database transaction 12 | let env = env_open(path)?; 13 | let db = Erigon::begin(&env)?; 14 | 15 | // get the canonical head block header 16 | let head_hash = db.read_head_header_hash()?.unwrap(); 17 | let head_num = db.read_header_number(head_hash)?.unwrap(); 18 | let header = db.read_header((head_num, head_hash))?.unwrap(); 19 | 20 | // get the current state of an account 21 | let contract: Address = "0x0d4c6c6605a729a379216c93e919711a081beba2".parse()?; 22 | let account = db.read_account(contract)?.unwrap(); 23 | let bytecode = db.read_code(account.codehash)?.unwrap(); 24 | 25 | // get all of the contract's populated storage slots 26 | // (incarnation is an Erigon-specific value related to removal/revival of contracts) 27 | for read in db.walk_storage(contract, account.incarnation, None)? { 28 | let (slot, value) = read?; 29 | println!("The value at slot {} is {}", slot, value); 30 | } 31 | 32 | // get the state of the account at block 100 33 | let old_account = db.read_account_hist(contract, 100)?.unwrap_or_default(); 34 | 35 | Ok(()) 36 | } 37 | ``` 38 | 39 | # Acknowledgements 40 | Much of this code has been taken from the [Akula](https://github.com/akula-bft/akula) Ethereum client in order to enable its use with the stable rust toolchain. 41 | In particular, it repurposes many of Akula's [`kv`](https://github.com/akula-bft/akula/blob/master/src/kv/mod.rs) utilities and abstractions for working with `libmdbx` and Ethereum data. 42 | These abstractions are extremely high-quality in my opinion, so the primary modifications were increasing the strictness and expressiveness of the accessor types and tailoring to Erigon's data representations and database layout. 43 | 44 | ## Resources 45 | - Erigon has an excellent [doc](https://github.com/ledgerwatch/erigon/blob/devel/docs/programmers_guide/db_walkthrough.MD) walking through their database layout, though it may not match the current implementation in some places. 46 | - Erigon's [`core/rawdb/accessors_*.go`](https://github.com/ledgerwatch/erigon/blob/f9d7cb5ca9e8a135a76ddcb6fa4ee526ea383554/core/rawdb/accessors_chain.go#L39) contains many of their low-level database interactions. 47 | - Some [comments](https://github.com/ledgerwatch/erigon/blob/devel/docs/programmers_guide/db_faq.md) from Erigon on the advisability of this endeavor. 48 | - For some brief info on mdbx, see [doc/mdbx.md](./doc/mdbx.md). 49 | - The Erigon database layout is defined in [src/erigon/tables.rs](./src/erigon/tables.rs), and you can see how these tables are read in [src/erigon/mod.rs](./src/erigon/mod.rs), namely the `read_*` methods. 50 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use eyre::{eyre, Result}; 2 | use std::{env, fs, path::PathBuf}; 3 | 4 | use ethers::{ 5 | contract::Abigen, 6 | solc::{ConfigurableArtifacts, Project, ProjectCompileOutput, ProjectPathsConfig, Solc}, 7 | }; 8 | use inflector::Inflector; 9 | use semver::{Version, VersionReq}; 10 | 11 | const SOLC_VERSION_REQ: &str = "^0.8.0"; 12 | const COMPILE_PATH: &str = "test/contracts"; 13 | 14 | fn main() -> Result<()> { 15 | if env::var_os("CARGO_FEATURE_TXGEN").is_none() { 16 | return Ok(()); 17 | } 18 | 19 | let contracts_to_bind = vec!["Store", "Factory"]; 20 | 21 | let base_dir = std::env::current_dir()?; 22 | let build_dir = mkdir(base_dir.join("build")); 23 | let bindings_dir = mkdir(base_dir.join("src/bindings")); 24 | 25 | let output = compile(base_dir.join(COMPILE_PATH))?; 26 | 27 | let mut bindings = String::new(); 28 | for name in contracts_to_bind { 29 | let contract = output.find_first(name).ok_or_else(|| { 30 | eyre!( 31 | "Could Not bind contract {}. Compiler output not found.", 32 | name 33 | ) 34 | })?; 35 | 36 | // write bytecode to build dir if binding a non-abstract contract 37 | if let Some(bin) = &contract.bytecode { 38 | fs::write( 39 | &build_dir.join(name.to_snake_case().to_owned() + ".bin"), 40 | hex::encode(bin.object.clone()), 41 | )?; 42 | } 43 | 44 | // generate bindings from the abi 45 | let abi = serde_json::to_string( 46 | contract 47 | .abi 48 | .as_ref() 49 | .expect("tried to bind a contract with no abi"), 50 | )?; 51 | 52 | let mod_name = name.to_snake_case(); 53 | Abigen::new(name, abi) 54 | .map_err(|e| eyre!("new abigen failure: {}", e))? 55 | .generate() 56 | .map_err(|e| eyre!("abigen failure: {}", e))? 57 | .write_to_file(bindings_dir.join(mod_name.clone() + ".rs")) 58 | .map_err(|e| eyre!("failed to write bindings: {}", e))?; 59 | 60 | bindings.push_str(&format!("pub mod {};\n", mod_name)); 61 | } 62 | 63 | fs::write(bindings_dir.join("mod.rs"), bindings)?; 64 | 65 | // Pass build_dir to env as SOLC_BUILD_DIR 66 | println!( 67 | "cargo:rustc-env=SOLC_BUILD_DIR={}", 68 | build_dir.into_os_string().into_string().unwrap() 69 | ); 70 | 71 | Ok(()) 72 | } 73 | 74 | fn compile(dir: PathBuf) -> Result> { 75 | let solc = Solc::default(); 76 | check_solc(solc.version().expect("No solc version")); 77 | 78 | let paths = ProjectPathsConfig::builder().sources(dir).build()?; 79 | let project = Project::builder() 80 | .paths(paths) 81 | .solc(solc) 82 | .no_artifacts() 83 | .build()?; 84 | 85 | // tell cargo to rerun build script if contracts change 86 | project.rerun_if_sources_changed(); 87 | 88 | let output = project.compile()?; 89 | if output.has_compiler_errors() { 90 | eyre::bail!(output.to_string()) 91 | } else { 92 | Ok(output) 93 | } 94 | } 95 | 96 | fn check_solc(version: Version) { 97 | let req = VersionReq::parse(SOLC_VERSION_REQ).expect("Cannot parse SOLC_VERSION_REQ"); 98 | if !req.matches(&version) { 99 | println!("cargo:warning=solc version mismatch. Using local solc executable, version: {}. Expected: {}", version, req); 100 | } 101 | } 102 | 103 | fn mkdir(dir: PathBuf) -> PathBuf { 104 | if !dir.exists() { 105 | fs::create_dir(&dir) 106 | .unwrap_or_else(|_| panic!("could not create dir: {}", dir.to_string_lossy())); 107 | } 108 | dir 109 | } 110 | -------------------------------------------------------------------------------- /doc/mdbx.md: -------------------------------------------------------------------------------- 1 | # MDBX primer 2 | An mdbx interaction can be thought of in three layers: one environment, one or more transactions, then one or more databases and/or cursors. 3 | 4 | #### Environment 5 | Opening an environment opens or creates the storage file, manages file locks, and initializes the specified configuration. You should be careful not to open the same mdbx environment more than once from the same process. If your process needs to access the database from multiple threads, you must share the same environment between them or mdbx will return `MDBX_BUSY`. 6 | 7 | #### Transactions 8 | From an environment, you create a transaction. Note that a transaction is needed even for read-only access in order to ensure a consistent view of the data. A read-write transaction must be committed to flush changes to the db, but `Drop` impls take care of this for us in rust. 9 | 10 | The rust bindings this crate uses to interact with mdbx set [`MDBX_NOTLS`](https://github.com/vorot93/libmdbx-rs/blob/b69d3d988ad7afaa4070c83480b0b48572f93929/src/flags.rs#L158) by default, which prevents issues with opening multiple transactions across process-managed threads or passing read-only transactions across OS threads. However, you should in general avoid opening overlapping transactions on the same thread or sending a read-write transaction across threads. Also be wary of long-running read transactions, as they can create database bloat and impact performance. 11 | 12 | #### Databases / Tables 13 | From a transaction, you can create or open one or more named databases. These databases are referred to in this crate as tables, and they represent a logical separation of different key-value spaces within the environment. Once opened, a database handle, or dbi, can be shared across transactions and cursors and need not ever be closed. This crate makes an effort to enable related optimizations in a type-safe way by associating each [`TableHandle`] with an implementer of the [`DbName`] and [`DbFlags`] traits. A `TableHandle` can be shared across transactions, but a [`Table`] can never be accessed without a matching `TableHandle`. 14 | 15 | [`TableHandle`]: `crate::kv::tables::TableHandle` 16 | [`DbName`]: `crate::kv::traits::DbName` 17 | [`DbFlags`]: `crate::kv::traits::DbFlags` 18 | [`Table`]: `crate::kv::traits::Table` 19 | 20 | #### Cursors 21 | A transaction can be used to `get()` or `put()` individual key-value pairs. 22 | For more complex interactions, you can also generate a cursor from a transaction. 23 | A cursor is a stateful accessor that can be positioned, repositioned, and used to efficiently traverse key-value pairs in a table. 24 | 25 | For an example of why cursors are useful, it is important to know that mdbx entries are sorted by their keys using a byte by byte [lexicographic order](https://cplusplus.com/reference/algorithm/lexicographical_compare/) (haskell [translation](https://en.wikipedia.org/wiki/Lexicographic_order#Monoid_of_words)). 26 | This is the reason for the `SeekKey` associated type for [`Table`]. 27 | If the `Key` for the table is the concatenation of the block number and the block hash, then seeking by block number `N` will position the cursor at the first value in the table with a key that begins with `N`. 28 | You can then use the cursor to yield successive key-value pairs until you get a key that starts with a different block number. 29 | 30 | [`Table`]: `crate::kv::traits::Table` 31 | 32 | 33 | ## Dupsort 34 | -------------------------------------------------------------------------------- /src/erigon/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! declare_tuple { 2 | ($name:ident($($t:ty),+)) => { 3 | #[derive( 4 | Clone, 5 | Copy, 6 | Debug, 7 | PartialEq, 8 | Eq, 9 | Default, 10 | ::derive_more::From, 11 | ::serde::Serialize, 12 | ::serde::Deserialize, 13 | ::fastrlp::RlpEncodable, 14 | ::fastrlp::RlpDecodable, 15 | )] 16 | pub struct $name($(pub $t),+); 17 | } 18 | } 19 | pub(crate) use declare_tuple; 20 | 21 | macro_rules! size_tuple_aux { 22 | ($t0:ty) => { 23 | pub const MIN_SIZE: usize = 0; 24 | pub const SIZE_T0: usize = std::mem::size_of::<$t0>(); 25 | }; 26 | ($t0:ty, $t1:ty) => { 27 | pub const MIN_SIZE: usize = Self::SIZE_T0; 28 | pub const SIZE_T0: usize = std::mem::size_of::<$t0>(); 29 | pub const SIZE_T1: usize = std::mem::size_of::<$t1>(); 30 | }; 31 | ($t0:ty, $t1:ty, $t2:ty) => { 32 | pub const MIN_SIZE: usize = Self::SIZE_T0 + Self::SIZE_T1; 33 | pub const SIZE_T0: usize = std::mem::size_of::<$t0>(); 34 | pub const SIZE_T1: usize = std::mem::size_of::<$t1>(); 35 | pub const SIZE_T2: usize = std::mem::size_of::<$t2>(); 36 | }; 37 | ($t0:ty, $t1:ty, $t2:ty, $t3:ty) => { 38 | pub const MIN_SIZE: usize = Self::SIZE_T0 + Self::SIZE_T1 + Self::SIZE_T2; 39 | pub const SIZE_T0: usize = std::mem::size_of::<$t0>(); 40 | pub const SIZE_T1: usize = std::mem::size_of::<$t1>(); 41 | pub const SIZE_T2: usize = std::mem::size_of::<$t2>(); 42 | pub const SIZE_T3: usize = std::mem::size_of::<$t3>(); 43 | }; 44 | } 45 | pub(crate) use size_tuple_aux; 46 | 47 | macro_rules! size_tuple { 48 | ($name:ident($($t:ty),+)) => { 49 | impl $name { 50 | pub const SIZE: usize = 0 $(+ std::mem::size_of::<$t>())+; 51 | $crate::erigon::macros::size_tuple_aux!($($t),+); 52 | } 53 | } 54 | } 55 | pub(crate) use size_tuple; 56 | 57 | macro_rules! impl_encode_tuple { 58 | ($name:ident($($t:ty),+), $n:literal) => { 59 | impl $crate::kv::traits::TableEncode for $name { 60 | type Encoded = $crate::kv::tables::VariableVec<{ Self::SIZE }>; 61 | fn encode(self) -> Self::Encoded { 62 | let mut out = Self::Encoded::default(); 63 | ::seq_macro::seq! { N in 0..=$n { 64 | out.try_extend_from_slice(&$crate::kv::traits::TableEncode::encode(self.N)).unwrap(); 65 | }} 66 | out 67 | } 68 | } 69 | } 70 | } 71 | pub(crate) use impl_encode_tuple; 72 | 73 | macro_rules! impl_decode_tuple { 74 | ($name:ident($($t:ty),+), $n:literal) => { 75 | impl $crate::kv::traits::TableDecode for $name { 76 | fn decode(b: &[u8]) -> ::eyre::Result { 77 | if b.len() > Self::SIZE { 78 | return Err( 79 | $crate::kv::tables::TooLong::<{ Self::SIZE }> { got: b.len() }.into(), 80 | ); 81 | } 82 | if b.len() < Self::MIN_SIZE { 83 | return Err($crate::kv::tables::TooShort::<{ Self::MIN_SIZE }> { 84 | got: b.len(), 85 | } 86 | .into()); 87 | } 88 | let remainder = b; 89 | ::seq_macro::seq! { N in 0..$n { 90 | #( let (b~N, remainder) = remainder.split_at(Self::SIZE_T~N); )* 91 | 92 | Ok(Self( 93 | #( $crate::kv::traits::TableDecode::decode(b~N)?,)* 94 | $crate::kv::traits::TableDecode::decode(remainder)?, 95 | )) 96 | }} 97 | } 98 | } 99 | }; 100 | } 101 | pub(crate) use impl_decode_tuple; 102 | 103 | macro_rules! make_tuple_key { 104 | ($name:ident($($t:ty),+), $n:literal) => { 105 | $crate::erigon::macros::declare_tuple!($name($($t),+)); 106 | $crate::erigon::macros::size_tuple!($name($($t),+)); 107 | $crate::erigon::macros::impl_encode_tuple!($name($($t),+), $n); 108 | $crate::erigon::macros::impl_decode_tuple!($name($($t),+), $n); 109 | } 110 | } 111 | pub(crate) use make_tuple_key; 112 | 113 | /// tuple_key! generates a tuple struct for a table key or table value that wraps 114 | /// one or more types. It also generates implementations of `TableEncode` and 115 | /// `TableDecode`, allowing the new type to be encoded to and decoded from the 116 | /// raw bytes stored in the database. 117 | /// 118 | /// For a single-element wrapper type, the encoding is just the encoding of the 119 | /// inner type. For an n-tuple with n > 1, the encoding is the concatenation of 120 | /// the encodings of each of the elements. 121 | macro_rules! tuple_key { 122 | ($name:ident($t0:ty)) => { 123 | $crate::erigon::macros::make_tuple_key!($name($t0), 0); 124 | }; 125 | ($name:ident($t0:ty, $t1:ty)) => { 126 | $crate::erigon::macros::make_tuple_key!($name($t0, $t1), 1); 127 | }; 128 | ($name:ident($t0:ty, $t1:ty, $t2:ty)) => { 129 | $crate::erigon::macros::make_tuple_key!($name($t0, $t1, $t2), 2); 130 | }; 131 | } 132 | pub(crate) use tuple_key; 133 | 134 | /// constant_key! declares a table key type whose encoding is always the same string. 135 | macro_rules! constant_key { 136 | ($name:ident, $encoded:ident) => { 137 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 138 | pub struct $name; 139 | 140 | impl $crate::kv::traits::TableEncode for $name { 141 | type Encoded = Vec; 142 | fn encode(self) -> Self::Encoded { 143 | String::from(stringify!($encoded)).into_bytes() 144 | } 145 | } 146 | }; 147 | ($name:ident) => { 148 | $crate::erigon::macros::constant_key!($name, $name); 149 | }; 150 | } 151 | pub(crate) use constant_key; 152 | 153 | /// rlp_table_value! implements TableEncode and TableDecode for any value that 154 | /// is stored in its rlp-encoded form. 155 | macro_rules! rlp_table_value { 156 | ($t:ty) => { 157 | impl $crate::kv::traits::TableEncode for $t { 158 | type Encoded = ::bytes::Bytes; 159 | fn encode(self) -> Self::Encoded { 160 | let mut buf = ::bytes::BytesMut::new(); 161 | ::fastrlp::Encodable::encode(&self, &mut buf); 162 | buf.into() 163 | } 164 | } 165 | impl $crate::kv::traits::TableDecode for $t { 166 | fn decode(mut b: &[u8]) -> ::eyre::Result { 167 | ::fastrlp::Decodable::decode(&mut b).map_err(From::from) 168 | } 169 | } 170 | }; 171 | } 172 | pub(crate) use rlp_table_value; 173 | 174 | macro_rules! impl_from { 175 | ($type:ty, $other:ty) => { 176 | impl From<$type> for $other { 177 | #[inline(always)] 178 | fn from(x: $type) -> $other { 179 | x.0 as $other 180 | } 181 | } 182 | }; 183 | } 184 | pub(crate) use impl_from; 185 | 186 | /// bytes_wrapper! declares a newtype wrapper around a type whose encoding function is 187 | /// the identity (e.g. the byte-encoded form of a bytes::Bytes type is itself). 188 | macro_rules! bytes_wrapper { 189 | ($name:ident($t:ty)) => { 190 | #[derive( 191 | Debug, 192 | Clone, 193 | PartialEq, 194 | Eq, 195 | Default, 196 | ::derive_more::Deref, 197 | ::derive_more::DerefMut, 198 | ::derive_more::From, 199 | ::serde::Serialize, 200 | ::serde::Deserialize, 201 | )] 202 | #[serde(transparent)] 203 | #[repr(transparent)] 204 | pub struct $name(pub $t); 205 | 206 | impl $crate::kv::traits::TableEncode for $name { 207 | type Encoded = $t; 208 | fn encode(self) -> Self::Encoded { 209 | self.0 210 | } 211 | } 212 | 213 | impl $crate::kv::traits::TableDecode for $name { 214 | fn decode(b: &[u8]) -> ::eyre::Result { 215 | $crate::kv::traits::TableDecode::decode(b).map(Self) 216 | } 217 | } 218 | }; 219 | } 220 | pub(crate) use bytes_wrapper; 221 | 222 | macro_rules! decl_u64_wrapper { 223 | ($ty:ident) => { 224 | #[derive( 225 | Debug, 226 | Clone, 227 | Copy, 228 | Default, 229 | PartialEq, 230 | Eq, 231 | PartialOrd, 232 | Ord, 233 | Hash, 234 | ::derive_more::Deref, 235 | ::derive_more::DerefMut, 236 | ::derive_more::Display, 237 | ::derive_more::From, 238 | ::derive_more::FromStr, 239 | ::serde::Serialize, 240 | ::serde::Deserialize, 241 | ::fastrlp::RlpEncodableWrapper, 242 | ::fastrlp::RlpDecodableWrapper, 243 | ::fastrlp::RlpMaxEncodedLen, 244 | )] 245 | #[serde(transparent)] 246 | #[repr(transparent)] 247 | pub struct $ty(pub u64); 248 | 249 | $crate::erigon::macros::impl_from!($ty, u64); 250 | $crate::erigon::macros::impl_from!($ty, usize); 251 | }; 252 | } 253 | pub(crate) use decl_u64_wrapper; 254 | 255 | macro_rules! u64_table_key { 256 | ($ty:ident) => { 257 | impl $crate::kv::traits::TableEncode for $ty { 258 | type Encoded = [u8; 8]; 259 | 260 | fn encode(self) -> Self::Encoded { 261 | self.to_be_bytes() 262 | } 263 | } 264 | 265 | impl $crate::kv::traits::TableDecode for $ty { 266 | fn decode(b: &[u8]) -> ::eyre::Result { 267 | match b.len() { 268 | 8 => Ok(u64::from_be_bytes(*::arrayref::array_ref!(&*b, 0, 8)).into()), 269 | other => Err($crate::kv::tables::InvalidLength::<8> { got: other }.into()), 270 | } 271 | } 272 | } 273 | }; 274 | } 275 | pub(crate) use u64_table_key; 276 | 277 | macro_rules! u64_wrapper { 278 | ($ty:ident) => { 279 | $crate::erigon::macros::decl_u64_wrapper!($ty); 280 | $crate::erigon::macros::u64_table_key!($ty); 281 | }; 282 | } 283 | pub(crate) use u64_wrapper; 284 | 285 | macro_rules! decl_u256_wrapper { 286 | ($ty:ident) => { 287 | #[derive( 288 | Debug, 289 | Clone, 290 | Copy, 291 | Default, 292 | PartialEq, 293 | Eq, 294 | PartialOrd, 295 | Ord, 296 | Hash, 297 | ::derive_more::Deref, 298 | ::derive_more::DerefMut, 299 | ::derive_more::Display, 300 | ::derive_more::From, 301 | ::derive_more::FromStr, 302 | ::serde::Serialize, 303 | ::serde::Deserialize, 304 | ::fastrlp::RlpEncodable, 305 | ::fastrlp::RlpDecodableWrapper, 306 | )] 307 | #[serde(transparent)] 308 | #[repr(transparent)] 309 | pub struct $ty(pub U256); 310 | 311 | $crate::erigon::macros::impl_from!($ty, U256); 312 | }; 313 | } 314 | pub(crate) use decl_u256_wrapper; 315 | 316 | macro_rules! cbor_wrapper { 317 | ($name:ident($t:ty)) => { 318 | #[derive( 319 | Debug, 320 | Clone, 321 | PartialEq, 322 | Eq, 323 | Default, 324 | ::derive_more::Deref, 325 | ::derive_more::DerefMut, 326 | ::derive_more::From, 327 | ::serde::Serialize, 328 | ::serde::Deserialize, 329 | )] 330 | #[serde(transparent)] 331 | #[repr(transparent)] 332 | pub struct $name(pub $t); 333 | 334 | impl $crate::kv::traits::TableEncode for $name { 335 | type Encoded = Vec; 336 | fn encode(self) -> Self::Encoded { 337 | ::serde_cbor::to_vec(&self.0) 338 | .expect(concat!("failed to encode ", stringify!($name))) 339 | } 340 | } 341 | 342 | impl $crate::kv::traits::TableDecode for $name { 343 | fn decode(b: &[u8]) -> ::eyre::Result { 344 | ::serde_cbor::from_slice(b).map_err(From::from).map(Self) 345 | } 346 | } 347 | }; 348 | } 349 | pub(crate) use cbor_wrapper; 350 | -------------------------------------------------------------------------------- /src/erigon/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::kv::{ 2 | traits::{DefaultFlags, Mode, Table}, 3 | EnvFlags, MdbxCursor, MdbxEnv, MdbxTx, 4 | }; 5 | use ethereum_types::{Address, H256, U256}; 6 | use eyre::{eyre, Result}; 7 | use mdbx::{TransactionKind, RO, RW}; 8 | 9 | mod macros; 10 | pub mod models; 11 | pub mod tables; 12 | mod utils; 13 | 14 | use utils::consts as C; 15 | 16 | use models::*; 17 | use tables::*; 18 | 19 | pub const NUM_TABLES: usize = 50; 20 | // https://github.com/ledgerwatch/erigon-lib/blob/625c9f5385d209dc2abfadedf6e4b3914a26ed3e/kv/mdbx/kv_mdbx.go#L154 21 | pub const ENV_FLAGS: EnvFlags = EnvFlags { 22 | no_rdahead: true, 23 | coalesce: true, 24 | accede: true, 25 | no_sub_dir: false, 26 | exclusive: false, 27 | no_meminit: false, 28 | liforeclaim: false, 29 | }; 30 | 31 | /// Open an mdbx env with Erigon-specific configuration. 32 | pub fn env_open(path: &std::path::Path) -> Result> { 33 | MdbxEnv::::open(path, NUM_TABLES, ENV_FLAGS) 34 | } 35 | 36 | /// Erigon wraps an `MdbxTx` and provides Erigon-specific access methods. 37 | pub struct Erigon<'env, K: TransactionKind>(pub MdbxTx<'env, K>); 38 | 39 | impl<'env> Erigon<'env, RO> { 40 | pub fn begin(env: &'env MdbxEnv) -> Result { 41 | env.begin().map(Self) 42 | } 43 | } 44 | impl<'env> Erigon<'env, RW> { 45 | pub fn begin_rw(env: &'env MdbxEnv) -> Result { 46 | env.begin_rw().map(Self) 47 | } 48 | } 49 | impl<'env, K: TransactionKind> Erigon<'env, K> { 50 | pub fn new(inner: MdbxTx<'env, K>) -> Self { 51 | Self(inner) 52 | } 53 | } 54 | 55 | impl<'env, K: Mode> Erigon<'env, K> { 56 | /// Opens and reads from the db table with the table's default flags 57 | pub fn read<'tx, T>(&'tx self, key: T::Key) -> Result> 58 | where 59 | T: Table<'tx> + DefaultFlags, 60 | { 61 | self.0.get::(self.0.open_db()?, key) 62 | } 63 | /// Opens a table with the table's default flags and creates a cursor into 64 | /// the opened table. 65 | pub fn cursor<'tx, T>(&'tx self) -> Result> 66 | where 67 | T: Table<'tx> + DefaultFlags, 68 | { 69 | self.0.cursor::(self.0.open_db()?) 70 | } 71 | 72 | /// Returns the hash of the current canonical head header. 73 | pub fn read_head_header_hash(&self) -> Result> { 74 | self.read::(LastHeaderKey) 75 | } 76 | 77 | /// Returns the hash of the current canonical head block. 78 | pub fn read_head_block_hash(&self) -> Result> { 79 | self.read::(LastBlockKey) 80 | } 81 | 82 | /// Returns the incarnation of the account when it was last deleted. 83 | pub fn read_incarnation(&self, adr: Address) -> Result> { 84 | self.read::(adr) 85 | } 86 | 87 | /// Returns the decoded account data as stored in the PlainState table. 88 | pub fn read_account(&self, adr: Address) -> Result> { 89 | self.read::(adr) 90 | } 91 | 92 | /// Returns the number of the block containing the specified transaction. 93 | pub fn read_transaction_block_number(&self, hash: H256) -> Result> { 94 | self.read::(hash) 95 | } 96 | 97 | /// Returns the block header identified by the (block number, block hash) key 98 | pub fn read_header(&self, key: impl Into) -> Result> { 99 | self.read::
(key.into()) 100 | } 101 | 102 | /// Returns header total difficulty 103 | pub fn read_total_difficulty( 104 | &self, 105 | key: impl Into, 106 | ) -> Result> { 107 | self.read::(key.into()) 108 | } 109 | 110 | /// Returns the decoding of the body as stored in the BlockBody table 111 | pub fn read_body_for_storage( 112 | &self, 113 | key: impl Into, 114 | ) -> Result> { 115 | let key = key.into(); 116 | self.read::(key)? 117 | .map(|mut body| { 118 | // Skip 1 system tx at the beginning of the block and 1 at the end 119 | // https://github.com/ledgerwatch/erigon/blob/f56d4c5881822e70f65927ade76ef05bfacb1df4/core/rawdb/accessors_chain.go#L602-L605 120 | // https://github.com/ledgerwatch/erigon-lib/blob/625c9f5385d209dc2abfadedf6e4b3914a26ed3e/kv/tables.go#L28 121 | body.base_tx_id += 1; 122 | body.tx_amount = body.tx_amount.checked_sub(2).ok_or_else(|| { 123 | eyre!( 124 | "Block body has too few txs: {}. HeaderKey: {:?}", 125 | body.tx_amount, 126 | key, 127 | ) 128 | })?; 129 | Ok(body) 130 | }) 131 | .transpose() 132 | } 133 | 134 | /// Returns the header number assigned to a hash. 135 | pub fn read_header_number(&self, hash: H256) -> Result> { 136 | self.read::(hash) 137 | } 138 | 139 | /// Returns the number of the current canonical block header. 140 | pub fn read_head_block_number(&self) -> Result> { 141 | let hash = self.read_head_header_hash()?.ok_or(eyre!("No value"))?; 142 | self.read_header_number(hash) 143 | } 144 | 145 | /// Returns the signers of each transaction in the block. 146 | pub fn read_senders(&self, key: impl Into) -> Result>> { 147 | self.read::(key.into()) 148 | } 149 | 150 | /// Returns the hash assigned to a canonical block number. 151 | pub fn read_canonical_hash(&self, num: impl Into) -> Result> { 152 | self.read::(num.into()) 153 | } 154 | 155 | /// Determines whether a header with the given hash is on the canonical chain. 156 | pub fn is_canonical_hash(&self, hash: H256) -> Result { 157 | let num = self.read_header_number(hash)?.ok_or(eyre!("No value"))?; 158 | let canon = self.read_canonical_hash(num)?.ok_or(eyre!("No value"))?; 159 | Ok(canon != Default::default() && canon == hash) 160 | } 161 | 162 | /// Returns the value of the storage for account `adr` indexed by `slot`. 163 | pub fn read_storage( 164 | &self, 165 | adr: Address, 166 | inc: impl Into, 167 | slot: H256, 168 | ) -> Result> { 169 | let bucket = StorageKey(adr, inc.into()); 170 | let mut cur = self.cursor::()?; 171 | cur.seek_dup(bucket, slot) 172 | .map(|kv| kv.and_then(|(k, v)| if k == slot { Some(v) } else { None })) 173 | } 174 | 175 | /// Returns an iterator over all of the storage (key, value) pairs for the 176 | /// given address and account incarnation. If a start_slot is provided, the 177 | /// iterator will begin at the smallest slot >= start_slot. 178 | pub fn walk_storage( 179 | &self, 180 | adr: Address, 181 | inc: impl Into, 182 | start_slot: Option, 183 | ) -> Result>> { 184 | let key = StorageKey(adr, inc.into()); 185 | self.cursor::()?.walk_dup(key, start_slot.unwrap_or_default()) 186 | } 187 | 188 | /// Returns the code associated with the given codehash. 189 | pub fn read_code(&self, codehash: H256) -> Result> { 190 | if codehash == C::EMPTY_HASH { 191 | return Ok(Default::default()); 192 | } 193 | self.read::(codehash) 194 | } 195 | 196 | /// Returns the codehash at the `adr` with incarnation `inc` 197 | pub fn read_codehash(&self, adr: Address, inc: impl Into) -> Result> { 198 | let key = PlainCodeKey(adr, inc.into()); 199 | self.read::(key) 200 | } 201 | 202 | pub fn walk_txs_canonical( 203 | &self, 204 | start_key: Option, 205 | ) -> Result>> { 206 | self.cursor::()? 207 | .walk(start_key.unwrap_or_default()) 208 | } 209 | 210 | pub fn walk_txs_noncanonical( 211 | &self, 212 | start_key: Option, 213 | ) -> Result>> { 214 | self.cursor::()? 215 | .walk(start_key.unwrap_or_default()) 216 | } 217 | 218 | // The `AccountChangeSet` table at block `N` stores the state of all accounts 219 | // changed in block `N` *before* block `N` changed them. 220 | // 221 | // The state of an account *after* the most recent change is always stored in the `PlainState` table. 222 | // 223 | // If Account A was changed in block 5 and again in block 25, the state of A for any 224 | // block `[5, 25)` is stored in the `AccountChangeSet` table addressed by the 225 | // block number 25. If we want to find the state of account `A` at block `B`, 226 | // we first use the `AccountHistory` table to figure out which block to look for 227 | // in the `AccountChangeSet` table. That is, we look for the smallest 228 | // block >= `B` in which account `A` was changed, then we lookup the state 229 | // of account `A` immediately before that change in the `AccountChangeSet` table. 230 | // 231 | // The `AccountHistory` table stores a roaring bitmap of the block numbers 232 | // in which account `A` was changed. We search the bitmap for the smallest 233 | // block number it contains which is `>= B`, then we read the state of account 234 | // `A` at this block from the `AccountChangeSet` table. 235 | // 236 | // The confusing thing is, the block number in `AccountHistory` seems to 237 | // be basically unused. For account `A`, every time a change is made, the 238 | // bitmap stored at key `(A, u64::MAX)` is updated. Presumably this is used to 239 | // grow the bitmap, and that's why akula and erigon both do some crazy mapping 240 | // over the bitmap tables 241 | // 242 | // Notes: 243 | // - `AccountHistory` and `StorageHistory` are written [here](https://github.com/ledgerwatch/erigon/blob/f9d7cb5ca9e8a135a76ddcb6fa4ee526ea383554/core/state/db_state_writer.go#L179). 244 | // - `GetAsOf()` Erigon implementation [here](https://github.com/ledgerwatch/erigon/blob/f9d7cb5ca9e8a135a76ddcb6fa4ee526ea383554/core/state/history.go#L19). 245 | // 246 | /// Returns the state of account `adr` at the given block number. 247 | pub fn read_account_hist( 248 | &self, 249 | adr: Address, 250 | block: impl Into, 251 | ) -> Result> { 252 | let block = block.into(); 253 | let mut hist_cur = self.cursor::()?; 254 | let (_, bitmap) = hist_cur 255 | .seek((adr, block).into())? 256 | .ok_or(eyre!("No value"))?; 257 | let cs_block = match utils::find_gte(bitmap, *block) { 258 | Some(changeset) => BlockNumber(changeset), 259 | _ => return Ok(None), 260 | }; 261 | let mut cs_cur = self.cursor::()?; 262 | if let Some(AccountCSVal(k, mut acct)) = cs_cur.seek_dup(cs_block, adr)? { 263 | if k == adr { 264 | // recover the codehash 265 | if *acct.incarnation > 0 && acct.codehash == Default::default() { 266 | acct.codehash = self 267 | .read_codehash(adr, acct.incarnation)? 268 | .ok_or(eyre!("No value"))? 269 | } 270 | return Ok(Some(acct)); 271 | } 272 | } 273 | Ok(None) 274 | } 275 | 276 | 277 | /// Returns the value of an address's storage at the given block number. Returns `None` if the state 278 | /// is not found in history (e.g., if it's in the PlainState table instead). 279 | pub fn read_storage_hist( 280 | &self, 281 | adr: Address, 282 | inc: impl Into, 283 | slot: H256, 284 | block: impl Into, 285 | ) -> Result> { 286 | let block = block.into(); 287 | let mut hist_cur = self.cursor::()?; 288 | let (_, bitmap) = hist_cur 289 | .seek((adr, slot, block).into())? 290 | .ok_or(eyre!("No value"))?; 291 | let cs_block = match utils::find_gte(bitmap, *block) { 292 | Some(changeset) => BlockNumber(changeset), 293 | _ => return Ok(None), 294 | }; 295 | let cs_key = (cs_block, adr, inc.into()).into(); 296 | let mut cs_cur = self.cursor::()?; 297 | if let Some(StorageCSVal(k, v)) = cs_cur.seek_dup(cs_key, slot)? { 298 | if k == slot { 299 | return Ok(Some(v)); 300 | } 301 | } 302 | Ok(None) 303 | } 304 | } 305 | 306 | impl<'env> Erigon<'env, mdbx::RW> { 307 | /// Opens and writes to the db table with the table's default flags. 308 | pub fn write<'tx, T>(&'tx self, key: T::Key, val: T::Value) -> Result<()> 309 | where 310 | T: Table<'tx> + DefaultFlags, 311 | { 312 | self.0.put::(self.0.open_db()?, key, val) 313 | } 314 | 315 | pub fn write_head_header_hash(&self, v: H256) -> Result<()> { 316 | self.write::(LastHeaderKey, v) 317 | } 318 | pub fn write_head_block_hash(&self, v: H256) -> Result<()> { 319 | self.write::(LastBlockKey, v) 320 | } 321 | pub fn write_incarnation(&self, k: Address, v: Incarnation) -> Result<()> { 322 | self.write::(k, v) 323 | } 324 | pub fn write_account(&self, k: Address, v: Account) -> Result<()> { 325 | self.write::(k, v) 326 | } 327 | pub fn write_transaction_block_number(&self, k: H256, v: U256) -> Result<()> { 328 | self.write::(k, v) 329 | } 330 | pub fn write_header_number(&self, k: H256, v: BlockNumber) -> Result<()> { 331 | self.write::(k, v) 332 | } 333 | pub fn write_header(&self, k: HeaderKey, v: BlockHeader) -> Result<()> { 334 | self.write::
(k, v) 335 | } 336 | pub fn write_body_for_storage(&self, k: HeaderKey, v: BodyForStorage) -> Result<()> { 337 | self.write::(k, v) 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/erigon/models/account.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use ethereum_types::{H256, U256}; 3 | use eyre::Result; 4 | use fastrlp::{RlpDecodable, RlpEncodable}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::{ 8 | erigon::{ 9 | utils::{consts::*, *}, 10 | Incarnation, 11 | }, 12 | kv::traits::{TableDecode, TableEncode}, 13 | }; 14 | 15 | #[derive( 16 | Clone, Copy, Debug, PartialEq, Eq, Default, Deserialize, Serialize, RlpEncodable, RlpDecodable, 17 | )] 18 | pub struct Account { 19 | pub nonce: u64, 20 | pub incarnation: Incarnation, 21 | pub balance: U256, 22 | pub codehash: H256, // hash of the bytecode 23 | } 24 | 25 | impl TableDecode for Account { 26 | fn decode(mut buf: &[u8]) -> Result { 27 | let mut acct = Self::default(); 28 | 29 | if buf.is_empty() { 30 | return Ok(acct); 31 | } 32 | 33 | let fieldset = buf.get_u8(); 34 | 35 | // has nonce 36 | if fieldset & 1 > 0 { 37 | acct.nonce = take_u64_rlp(&mut buf)?; 38 | } 39 | 40 | // has balance 41 | if fieldset & 2 > 0 { 42 | let bal_len = buf.get_u8(); 43 | acct.balance = buf[..bal_len.into()].into(); 44 | buf.advance(bal_len.into()); 45 | } 46 | 47 | // has incarnation 48 | if fieldset & 4 > 0 { 49 | acct.incarnation = take_u64_rlp(&mut buf)?.into(); 50 | } 51 | 52 | // has codehash 53 | if fieldset & 8 > 0 { 54 | let len: usize = buf.get_u8().into(); 55 | if len != KECCAK_LENGTH { 56 | eyre::bail!( 57 | "codehash should be {} bytes long. Got {} instead", 58 | KECCAK_LENGTH, 59 | len 60 | ); 61 | } 62 | acct.codehash = H256::from_slice(&buf[..KECCAK_LENGTH]); 63 | buf.advance(KECCAK_LENGTH) 64 | } 65 | Ok(acct) 66 | } 67 | } 68 | //TODO: dummy impl as we only need to decode for now, but need the trait bound 69 | impl TableEncode for Account { 70 | type Encoded = Vec; 71 | fn encode(self) -> Self::Encoded { 72 | unreachable!("Can't encode Account") 73 | } 74 | } 75 | 76 | impl Account { 77 | pub fn new() -> Self { 78 | Self::default() 79 | } 80 | pub fn nonce(mut self, nonce: u64) -> Self { 81 | self.nonce = nonce; 82 | self 83 | } 84 | pub fn incarnation(mut self, inc: Incarnation) -> Self { 85 | self.incarnation = inc; 86 | self 87 | } 88 | pub fn balance(mut self, bal: U256) -> Self { 89 | self.balance = bal; 90 | self 91 | } 92 | pub fn codehash(mut self, hash: H256) -> Self { 93 | self.codehash = hash; 94 | self 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/erigon/models/block.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use ethereum_types::{Address, Bloom, H256, H64, U256}; 3 | use eyre::Result; 4 | use fastrlp::{BufMut, Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::erigon::{macros::*, utils::consts::*, Rlp}; 8 | 9 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RlpEncodable, RlpDecodable)] 10 | pub struct BodyForStorage { 11 | pub base_tx_id: u64, 12 | pub tx_amount: u32, 13 | pub uncles: Vec, 14 | } 15 | rlp_table_value!(BodyForStorage); 16 | 17 | #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] 18 | pub struct BlockHeader { 19 | pub parent_hash: H256, 20 | pub uncle_hash: H256, 21 | pub coinbase: Address, 22 | pub root: H256, 23 | pub tx_hash: H256, 24 | pub receipts_hash: H256, 25 | pub bloom: Bloom, 26 | pub difficulty: U256, 27 | pub number: U256, // TODO: erigon stores as big.Int, then casts, which returns 0 if > u64 (technically big.Int says result is undefined) 28 | pub gas_limit: u64, 29 | pub gas_used: u64, 30 | pub time: u64, 31 | pub extra: Bytes, 32 | pub mix_digest: H256, 33 | pub nonce: H64, 34 | pub base_fee: Option, 35 | pub seal: Option, 36 | } 37 | rlp_table_value!(BlockHeader); 38 | 39 | impl BlockHeader { 40 | fn rlp_header(&self) -> fastrlp::Header { 41 | let mut rlp_head = fastrlp::Header { 42 | list: true, 43 | payload_length: 0, 44 | }; 45 | 46 | rlp_head.payload_length += KECCAK_LENGTH + 1; // parent_hash 47 | rlp_head.payload_length += KECCAK_LENGTH + 1; // ommers_hash 48 | rlp_head.payload_length += ADDRESS_LENGTH + 1; // beneficiary 49 | rlp_head.payload_length += KECCAK_LENGTH + 1; // state_root 50 | rlp_head.payload_length += KECCAK_LENGTH + 1; // transactions_root 51 | rlp_head.payload_length += KECCAK_LENGTH + 1; // receipts_root 52 | rlp_head.payload_length += BLOOM_BYTE_LENGTH + fastrlp::length_of_length(BLOOM_BYTE_LENGTH); // logs_bloom 53 | rlp_head.payload_length += self.difficulty.length(); // difficulty 54 | rlp_head.payload_length += self.number.length(); // block height 55 | rlp_head.payload_length += self.gas_limit.length(); // gas_limit 56 | rlp_head.payload_length += self.gas_used.length(); // gas_used 57 | rlp_head.payload_length += self.time.length(); // timestamp 58 | rlp_head.payload_length += self.extra.length(); // extra_data 59 | 60 | rlp_head.payload_length += KECCAK_LENGTH + 1; // mix_hash 61 | rlp_head.payload_length += 8 + 1; // nonce 62 | 63 | if let Some(base_fee) = self.base_fee { 64 | rlp_head.payload_length += base_fee.length(); 65 | } 66 | 67 | rlp_head 68 | } 69 | } 70 | 71 | impl Encodable for BlockHeader { 72 | fn encode(&self, out: &mut dyn BufMut) { 73 | self.rlp_header().encode(out); 74 | Encodable::encode(&self.parent_hash, out); 75 | Encodable::encode(&self.uncle_hash, out); 76 | Encodable::encode(&self.coinbase, out); 77 | Encodable::encode(&self.root, out); 78 | Encodable::encode(&self.tx_hash, out); 79 | Encodable::encode(&self.receipts_hash, out); 80 | Encodable::encode(&self.bloom, out); 81 | Encodable::encode(&self.difficulty, out); 82 | Encodable::encode(&self.number, out); 83 | Encodable::encode(&self.gas_limit, out); 84 | Encodable::encode(&self.gas_used, out); 85 | Encodable::encode(&self.time, out); 86 | Encodable::encode(&self.extra, out); 87 | Encodable::encode(&self.mix_digest, out); 88 | Encodable::encode(&self.nonce, out); 89 | if let Some(base_fee) = self.base_fee { 90 | Encodable::encode(&base_fee, out); 91 | } 92 | } 93 | fn length(&self) -> usize { 94 | let rlp_head = self.rlp_header(); 95 | fastrlp::length_of_length(rlp_head.payload_length) + rlp_head.payload_length 96 | } 97 | } 98 | 99 | // https://github.com/ledgerwatch/erigon/blob/156da607e7495d709c141aec40f66a2556d35dc0/core/types/block.go#L430 100 | impl Decodable for BlockHeader { 101 | fn decode(buf: &mut &[u8]) -> Result { 102 | let rlp_head = fastrlp::Header::decode(buf)?; 103 | if !rlp_head.list { 104 | return Err(DecodeError::UnexpectedString); 105 | } 106 | let rest = buf.len() - rlp_head.payload_length; 107 | let parent_hash = Decodable::decode(buf)?; 108 | let uncle_hash = Decodable::decode(buf)?; 109 | let coinbase = Decodable::decode(buf)?; 110 | let root = Decodable::decode(buf)?; 111 | let tx_hash = Decodable::decode(buf)?; 112 | let receipts_hash = Decodable::decode(buf)?; 113 | let bloom = Decodable::decode(buf)?; 114 | let difficulty = Decodable::decode(buf)?; 115 | let number = Decodable::decode(buf)?; 116 | let gas_limit = Decodable::decode(buf)?; 117 | let gas_used = Decodable::decode(buf)?; 118 | let time = Decodable::decode(buf)?; 119 | let extra = Decodable::decode(buf)?; 120 | 121 | // TODO: seal fields 122 | let seal = None; 123 | let mix_digest = Decodable::decode(buf)?; 124 | let nonce = Decodable::decode(buf)?; 125 | let base_fee = if buf.len() > rest { 126 | Some(Decodable::decode(buf)?) 127 | } else { 128 | None 129 | }; 130 | 131 | Ok(Self { 132 | parent_hash, 133 | uncle_hash, 134 | coinbase, 135 | root, 136 | tx_hash, 137 | receipts_hash, 138 | bloom, 139 | difficulty, 140 | number, 141 | gas_limit, 142 | gas_used, 143 | time, 144 | extra, 145 | mix_digest, 146 | nonce, 147 | base_fee, 148 | seal, 149 | }) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/erigon/models/log.rs: -------------------------------------------------------------------------------- 1 | use crate::erigon::{ 2 | macros::{cbor_wrapper, tuple_key}, 3 | models::BlockNumber, 4 | }; 5 | use bytes::Bytes; 6 | use ethereum_types::{Address, H256}; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | cbor_wrapper!(CborReceipts(Option>)); 10 | 11 | // blocknum||log_index_in_tx 12 | tuple_key!(LogsKey(BlockNumber, u32)); 13 | cbor_wrapper!(CborLogs(Option>)); 14 | 15 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] 16 | pub struct CborLog { 17 | address: Address, 18 | topics: Vec, 19 | data: Bytes, 20 | // block_number: u64, 21 | // tx_hash: H256, 22 | // tx_index: usize, 23 | // block_hash: H256, 24 | // index: usize, 25 | // removed: bool, 26 | } 27 | 28 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] 29 | pub struct CborReceipt { 30 | tx_type: u8, //omitempty 31 | post_state: Option, 32 | status: u64, 33 | cumulative_gas_used: u64, 34 | } 35 | -------------------------------------------------------------------------------- /src/erigon/models/mod.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use ethereum_types::{Address, H256, U256}; 3 | use eyre::Result; 4 | 5 | use crate::{ 6 | erigon::{macros::*, utils::*}, 7 | kv::{ 8 | tables::VariableVec, 9 | traits::{TableDecode, TableEncode}, 10 | }, 11 | }; 12 | 13 | pub mod transaction; 14 | pub use transaction::Transaction; 15 | pub mod block; 16 | pub use block::*; 17 | pub mod account; 18 | pub use account::*; 19 | pub mod log; 20 | pub use log::*; 21 | 22 | use crate::erigon::utils::consts::*; 23 | 24 | // the LastHeader table stores only one key, bytes("LastHeader") 25 | constant_key!(LastHeaderKey, LastHeader); 26 | // the LastBlock table stores only one key, bytes("LastBlock") 27 | constant_key!(LastBlockKey, LastBlock); 28 | 29 | // u64 newtype aliases 30 | u64_wrapper!(BlockNumber); 31 | u64_wrapper!(Incarnation); 32 | u64_wrapper!(TxIndex); 33 | 34 | // blocknum||blockhash 35 | tuple_key!(HeaderKey(BlockNumber, H256)); 36 | tuple_key!(AccountHistKey(Address, BlockNumber)); 37 | tuple_key!(StorageKey(Address, Incarnation)); 38 | 39 | // values for the StorageChangeSet table. slot||value 40 | tuple_key!(StorageCSVal(H256, U256)); 41 | // blocknum||address||incarnation 42 | tuple_key!(StorageCSKey(BlockNumber, StorageKey)); 43 | impl From<(B, A, I)> for StorageCSKey 44 | where 45 | B: Into, 46 | (A, I): Into, 47 | { 48 | fn from(src: (B, A, I)) -> Self { 49 | Self(src.0.into(), (src.1, src.2).into()) 50 | } 51 | } 52 | 53 | // values for the AccountChangeSet table. address||encode(account) 54 | tuple_key!(AccountCSVal(Address, Account)); 55 | 56 | // address||storage_slot||block_number 57 | tuple_key!(StorageHistKey(Address, H256, BlockNumber)); 58 | // address||incarnation 59 | tuple_key!(PlainCodeKey(Address, Incarnation)); 60 | 61 | // keccak(address)||incarnation 62 | tuple_key!(ContractCodeKey(H256, Incarnation)); 63 | impl ContractCodeKey { 64 | pub fn make(who: Address, inc: impl Into) -> Self { 65 | Self(keccak256(who).into(), inc.into()) 66 | } 67 | } 68 | 69 | // keccak(address)||incarnation||keccak(storage_key) 70 | tuple_key!(HashStorageKey(H256, Incarnation, H256)); 71 | impl HashStorageKey { 72 | pub fn make(who: Address, inc: impl Into, key: H256) -> Self { 73 | Self(keccak256(who).into(), inc.into(), keccak256(key).into()) 74 | } 75 | } 76 | 77 | // The Issuance table also stores the amount burnt, prefixing the encoded block number with "burnt" 78 | // bytes("burnt")||blocknum 79 | declare_tuple!(BurntKey(BlockNumber)); 80 | size_tuple!(BurntKey(BlockNumber)); 81 | impl TableEncode for BurntKey { 82 | type Encoded = VariableVec<{ Self::SIZE + 5 }>; 83 | fn encode(self) -> Self::Encoded { 84 | let mut out = Self::Encoded::default(); 85 | let prefix = Bytes::from(&b"burnt"[..]); 86 | out.try_extend_from_slice(&prefix).unwrap(); 87 | out.try_extend_from_slice(&self.0.encode()).unwrap(); 88 | out 89 | } 90 | } 91 | 92 | bytes_wrapper!(Rlp(Bytes)); 93 | bytes_wrapper!(Bytecode(Bytes)); 94 | 95 | decl_u256_wrapper!(TotalDifficulty); 96 | rlp_table_value!(TotalDifficulty); 97 | 98 | // The TxSender table stores addresses with no serialization format (new address every 20 bytes) 99 | impl TableEncode for Vec
{ 100 | type Encoded = Vec; 101 | 102 | fn encode(self) -> Self::Encoded { 103 | let mut v = Vec::with_capacity(self.len() * ADDRESS_LENGTH); 104 | for addr in self { 105 | v.extend_from_slice(&addr.encode()); 106 | } 107 | v 108 | } 109 | } 110 | 111 | impl TableDecode for Vec
{ 112 | fn decode(b: &[u8]) -> Result { 113 | if b.len() % ADDRESS_LENGTH != 0 { 114 | eyre::bail!("Slice len should be divisible by {}", ADDRESS_LENGTH); 115 | } 116 | 117 | let mut v = Vec::with_capacity(b.len() / ADDRESS_LENGTH); 118 | for i in 0..b.len() / ADDRESS_LENGTH { 119 | let offset = i * ADDRESS_LENGTH; 120 | v.push(TableDecode::decode(&b[offset..offset + ADDRESS_LENGTH])?); 121 | } 122 | 123 | Ok(v) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/erigon/models/transaction.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes, BytesMut}; 2 | use ethereum_types::{Address, H256, U256}; 3 | use fastrlp::{BufMut, Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable}; 4 | use serde::{Deserialize, Serialize}; 5 | use std::borrow::Cow; 6 | 7 | use crate::erigon::{ 8 | macros::decl_u256_wrapper, 9 | utils::{consts as C, keccak256}, 10 | }; 11 | 12 | // https://github.com/akula-bft/akula/blob/e5af0ab9cea24c7ff4713b1e61c60a918abc6fef/src/models/transaction.rs#L41 13 | /// The `to` address in an rlp-encoded transaction is either the 1-byte encoded length 14 | /// of the string (0x80 + 0x14 bytes = 0x94), or it is the 1-byte encoded length of 15 | /// the empty string (0x80) if the transaction is creating a contract. TxAction 16 | /// is used to implement this encoding/decoding scheme. 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 18 | pub enum TxAction { 19 | Call(Address), 20 | Create, 21 | } 22 | impl From for Option
{ 23 | fn from(src: TxAction) -> Self { 24 | match src { 25 | TxAction::Call(adr) => Some(adr), 26 | TxAction::Create => None, 27 | } 28 | } 29 | } 30 | impl From> for TxAction { 31 | fn from(src: Option
) -> Self { 32 | match src { 33 | Some(adr) => Self::Call(adr), 34 | None => Self::Create, 35 | } 36 | } 37 | } 38 | 39 | impl Encodable for TxAction { 40 | fn length(&self) -> usize { 41 | match self { 42 | Self::Call(_) => 1 + C::ADDRESS_LENGTH, 43 | Self::Create => 1, 44 | } 45 | } 46 | 47 | fn encode(&self, out: &mut dyn BufMut) { 48 | match self { 49 | Self::Call(adr) => { 50 | fastrlp::Header { 51 | list: false, 52 | payload_length: C::ADDRESS_LENGTH, 53 | } 54 | .encode(out); 55 | out.put_slice(adr.as_bytes()); 56 | } 57 | Self::Create => { 58 | out.put_u8(fastrlp::EMPTY_STRING_CODE); 59 | } 60 | } 61 | } 62 | } 63 | 64 | impl Decodable for TxAction { 65 | fn decode(buf: &mut &[u8]) -> Result { 66 | if buf.is_empty() { 67 | return Err(DecodeError::InputTooShort); 68 | } 69 | const RLP_ADDRESS_CODE: u8 = fastrlp::EMPTY_STRING_CODE + C::ADDRESS_LENGTH as u8; 70 | 71 | Ok(match buf.get_u8() { 72 | fastrlp::EMPTY_STRING_CODE => Self::Create, 73 | RLP_ADDRESS_CODE => { 74 | let slice = buf 75 | .get(..C::ADDRESS_LENGTH) 76 | .ok_or(DecodeError::InputTooShort)?; 77 | buf.advance(C::ADDRESS_LENGTH); 78 | Self::Call(Address::from_slice(slice)) 79 | } 80 | _ => return Err(DecodeError::UnexpectedLength), 81 | }) 82 | } 83 | } 84 | 85 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RlpDecodable, RlpEncodable)] 86 | pub struct AccessTuple { 87 | pub address: Address, 88 | pub slots: Vec, 89 | } 90 | pub type AccessList = Vec; 91 | 92 | // For legacy transactions, v is packed with the Eip155 chain id 93 | decl_u256_wrapper!(VPackChainId); 94 | 95 | impl VPackChainId { 96 | // Eip155 defines v as either {0,1} + 27 (no chain id) OR {0,1} + chain_id * 2 + 35 97 | pub fn derive_chain_id(&self) -> Option { 98 | if self.0 == U256::from(27) || self.0 == U256::from(28) { 99 | None 100 | } else { 101 | Some( 102 | (self 103 | .0 104 | .checked_sub(35.into()) 105 | .expect("invalid eip155 chainid")) 106 | / 2, 107 | ) 108 | } 109 | } 110 | //TODO 111 | pub fn derive_v(&self) -> U256 { 112 | if let Some(chain_id) = self.derive_chain_id() { 113 | self.0 - (chain_id * 2 + 35) 114 | } else { 115 | self.0 116 | } 117 | } 118 | } 119 | 120 | // rlp([nonce, gas_price, gas_limit, to, value, data, v, r, s]) 121 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RlpDecodable, RlpEncodable)] 122 | pub struct LegacyTx { 123 | pub nonce: u64, 124 | pub gas_price: U256, 125 | pub gas: u64, 126 | pub to: TxAction, 127 | pub value: U256, 128 | pub data: Bytes, 129 | pub v: VPackChainId, 130 | pub r: U256, 131 | pub s: U256, 132 | } 133 | 134 | // Eip2930 transaction 135 | // 0x01 || rlp([chain_id, nonce, gas_price, gas_limit, to, value, data, access_list, sig_y_parity, sig_r, sig_s]) 136 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RlpDecodable, RlpEncodable)] 137 | pub struct AccessListTx { 138 | pub chain_id: U256, 139 | pub nonce: u64, 140 | pub gas_price: U256, 141 | pub gas: u64, 142 | pub to: TxAction, 143 | pub value: U256, 144 | pub data: Bytes, 145 | pub access_list: AccessList, 146 | pub v: U256, 147 | pub r: U256, 148 | pub s: U256, 149 | } 150 | 151 | // Eip1559 transaction 152 | // 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, sig_y_parity, sig_r, sig_s]) 153 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RlpDecodable, RlpEncodable)] 154 | pub struct DynamicFeeTx { 155 | pub chain_id: U256, 156 | pub nonce: u64, 157 | pub tip: U256, 158 | pub fee_cap: U256, 159 | pub gas: u64, 160 | pub to: TxAction, 161 | pub value: U256, 162 | pub data: Bytes, 163 | pub access_list: AccessList, 164 | pub v: U256, 165 | pub r: U256, 166 | pub s: U256, 167 | } 168 | 169 | crate::erigon::macros::rlp_table_value!(Transaction); 170 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 171 | pub enum Transaction { 172 | Legacy(LegacyTx), 173 | AccessList(AccessListTx), 174 | DynamicFee(DynamicFeeTx), 175 | } 176 | 177 | impl DynamicFeeTx { 178 | pub const TYPE: u8 = 0x02; 179 | } 180 | impl AccessListTx { 181 | pub const TYPE: u8 = 0x01; 182 | } 183 | 184 | impl Decodable for Transaction { 185 | fn decode(buf: &mut &[u8]) -> Result { 186 | // if input is rlp encoded as a list, interpret as a legacy transaction 187 | // rlp([nonce, gas_price, gas_limit, to, value, data, v, r, s]) 188 | if buf[0] >= 0xc0 { 189 | return Decodable::decode(buf).map(Self::Legacy); 190 | } 191 | // strip string length and length of length 192 | fastrlp::Header::decode(buf)?; 193 | 194 | // Eip2718 Typed Transaction. TransactionType || TransactionPayload 195 | match buf.get_u8() { 196 | AccessListTx::TYPE => Decodable::decode(buf).map(Self::AccessList), 197 | DynamicFeeTx::TYPE => Decodable::decode(buf).map(Self::DynamicFee), 198 | _ => Err(DecodeError::Custom("Unknown transaction type")), 199 | } 200 | } 201 | } 202 | 203 | impl Encodable for Transaction { 204 | fn encode(&self, out: &mut dyn BufMut) { 205 | match self { 206 | Self::Legacy(tx) => tx.encode(out), 207 | Self::AccessList(tx) => tx.encode(out), 208 | Self::DynamicFee(tx) => tx.encode(out), 209 | } 210 | } 211 | } 212 | 213 | impl Transaction { 214 | pub fn tx_type(&self) -> Option { 215 | match self { 216 | Self::AccessList(_) => Some(AccessListTx::TYPE), 217 | Self::DynamicFee(_) => Some(DynamicFeeTx::TYPE), 218 | Self::Legacy(_) => None, 219 | } 220 | } 221 | pub fn hash(&self) -> H256 { 222 | match self { 223 | Self::Legacy(tx) => tx.hash(), 224 | Self::AccessList(tx) => tx.hash(), 225 | Self::DynamicFee(tx) => tx.hash(), 226 | } 227 | } 228 | pub fn nonce(&self) -> u64 { 229 | match self { 230 | Self::Legacy(tx) => tx.nonce, 231 | Self::AccessList(tx) => tx.nonce, 232 | Self::DynamicFee(tx) => tx.nonce, 233 | } 234 | } 235 | pub fn to(&self) -> TxAction { 236 | match self { 237 | Self::Legacy(tx) => tx.to, 238 | Self::AccessList(tx) => tx.to, 239 | Self::DynamicFee(tx) => tx.to, 240 | } 241 | } 242 | pub fn value(&self) -> U256 { 243 | match self { 244 | Self::Legacy(tx) => tx.value, 245 | Self::AccessList(tx) => tx.value, 246 | Self::DynamicFee(tx) => tx.value, 247 | } 248 | } 249 | pub fn gas_price(&self) -> Option { 250 | match self { 251 | Self::Legacy(tx) => Some(tx.gas_price), 252 | Self::AccessList(tx) => Some(tx.gas_price), 253 | Self::DynamicFee(_) => None, 254 | } 255 | } 256 | pub fn chain_id(&self) -> Option { 257 | match self { 258 | Self::Legacy(tx) => tx.v.derive_chain_id(), 259 | Self::AccessList(tx) => Some(tx.chain_id), 260 | Self::DynamicFee(tx) => Some(tx.chain_id), 261 | } 262 | } 263 | pub fn tip(&self) -> Option { 264 | match self { 265 | Self::DynamicFee(tx) => Some(tx.tip), 266 | _ => None, 267 | } 268 | } 269 | pub fn fee_cap(&self) -> Option { 270 | match self { 271 | Self::DynamicFee(tx) => Some(tx.fee_cap), 272 | _ => None, 273 | } 274 | } 275 | pub fn gas(&self) -> u64 { 276 | match self { 277 | Self::Legacy(tx) => tx.gas, 278 | Self::AccessList(tx) => tx.gas, 279 | Self::DynamicFee(tx) => tx.gas, 280 | } 281 | } 282 | pub fn data(&self) -> &Bytes { 283 | match self { 284 | Self::Legacy(tx) => &tx.data, 285 | Self::AccessList(tx) => &tx.data, 286 | Self::DynamicFee(tx) => &tx.data, 287 | } 288 | } 289 | pub fn r(&self) -> U256 { 290 | match self { 291 | Self::Legacy(tx) => tx.r, 292 | Self::AccessList(tx) => tx.r, 293 | Self::DynamicFee(tx) => tx.r, 294 | } 295 | } 296 | pub fn s(&self) -> U256 { 297 | match self { 298 | Self::Legacy(tx) => tx.s, 299 | Self::AccessList(tx) => tx.s, 300 | Self::DynamicFee(tx) => tx.s, 301 | } 302 | } 303 | //TODO 304 | pub fn v(&self) -> U256 { 305 | match self { 306 | Self::Legacy(tx) => tx.v.derive_v(), 307 | Self::AccessList(tx) => tx.v, 308 | Self::DynamicFee(tx) => tx.v, 309 | } 310 | } 311 | 312 | pub fn access_list(&self) -> Option> { 313 | match self { 314 | Self::AccessList(tx) => Some(Cow::Borrowed(&tx.access_list)), 315 | Self::DynamicFee(tx) => Some(Cow::Borrowed(&tx.access_list)), 316 | Self::Legacy(_) => None, 317 | } 318 | } 319 | } 320 | 321 | impl LegacyTx { 322 | /// Computes the (signing) hash of the transaction 323 | pub fn hash(&self) -> H256 { 324 | #[derive(RlpEncodable)] 325 | struct AsHash<'a> { 326 | nonce: u64, 327 | gas_price: &'a U256, 328 | gas: u64, 329 | to: &'a TxAction, 330 | value: &'a U256, 331 | data: &'a Bytes, 332 | } 333 | 334 | #[derive(RlpEncodable)] 335 | struct AsHashWithChainId<'a> { 336 | nonce: u64, 337 | gas_price: &'a U256, 338 | gas: u64, 339 | to: &'a TxAction, 340 | value: &'a U256, 341 | data: &'a Bytes, 342 | chain_id: U256, 343 | _pad1: u8, 344 | _pad2: u8, 345 | } 346 | 347 | let mut buf = BytesMut::new(); 348 | if let Some(chain_id) = self.v.derive_chain_id() { 349 | AsHashWithChainId { 350 | nonce: self.nonce, 351 | gas_price: &self.gas_price, 352 | gas: self.gas, 353 | to: &self.to, 354 | value: &self.value, 355 | data: &self.data, 356 | chain_id, 357 | _pad1: 0, 358 | _pad2: 0, 359 | } 360 | .encode(&mut buf); 361 | } else { 362 | AsHash { 363 | nonce: self.nonce, 364 | gas_price: &self.gas_price, 365 | gas: self.gas, 366 | to: &self.to, 367 | value: &self.value, 368 | data: &self.data, 369 | } 370 | .encode(&mut buf); 371 | } 372 | keccak256(buf).into() 373 | } 374 | } 375 | 376 | impl AccessListTx { 377 | /// Computes the (signing) hash of the transaction 378 | pub fn hash(&self) -> H256 { 379 | #[derive(RlpEncodable)] 380 | struct AsHash<'a> { 381 | chain_id: U256, 382 | nonce: u64, 383 | gas_price: &'a U256, 384 | gas: u64, 385 | to: &'a TxAction, 386 | value: &'a U256, 387 | data: &'a Bytes, 388 | access_list: &'a AccessList, 389 | } 390 | 391 | let mut buf = BytesMut::new(); 392 | buf.put_u8(Self::TYPE); 393 | 394 | AsHash { 395 | chain_id: self.chain_id, 396 | nonce: self.nonce, 397 | gas_price: &self.gas_price, 398 | gas: self.gas, 399 | to: &self.to, 400 | value: &self.value, 401 | data: &self.data, 402 | access_list: &self.access_list, 403 | } 404 | .encode(&mut buf); 405 | 406 | keccak256(buf).into() 407 | } 408 | } 409 | 410 | impl DynamicFeeTx { 411 | /// Computes the (signing) hash of the transaction 412 | pub fn hash(&self) -> H256 { 413 | #[derive(RlpEncodable)] 414 | struct AsHash<'a> { 415 | chain_id: U256, 416 | nonce: u64, 417 | tip: &'a U256, 418 | fee_cap: &'a U256, 419 | gas: u64, 420 | to: &'a TxAction, 421 | value: &'a U256, 422 | data: &'a Bytes, 423 | access_list: &'a AccessList, 424 | } 425 | 426 | let mut buf = BytesMut::new(); 427 | buf.put_u8(Self::TYPE); 428 | 429 | AsHash { 430 | chain_id: self.chain_id, 431 | nonce: self.nonce, 432 | tip: &self.tip, 433 | fee_cap: &self.fee_cap, 434 | gas: self.gas, 435 | to: &self.to, 436 | value: &self.value, 437 | data: &self.data, 438 | access_list: &self.access_list, 439 | } 440 | .encode(&mut buf); 441 | 442 | keccak256(buf).into() 443 | } 444 | } 445 | 446 | pub struct TransactionWithSigner { 447 | pub msg: Transaction, 448 | pub signer: Address, 449 | } 450 | 451 | #[cfg(feature = "ethers-types")] 452 | impl From for ethers::types::Transaction { 453 | fn from(tx: TransactionWithSigner) -> Self { 454 | Self { 455 | hash: tx.msg.hash(), 456 | nonce: tx.msg.nonce().into(), 457 | from: tx.signer, 458 | to: tx.msg.to().into(), 459 | value: tx.msg.value(), 460 | gas_price: tx.msg.gas_price(), 461 | gas: tx.msg.gas().into(), 462 | input: tx.msg.data().clone().into(), 463 | transaction_type: tx.msg.tx_type().map(From::from), 464 | access_list: tx.msg.access_list().map(|al| { 465 | al.iter() 466 | .map(|it| it.clone().into()) 467 | .collect::>() 468 | .into() 469 | }), 470 | chain_id: tx.msg.chain_id(), 471 | max_fee_per_gas: tx.msg.fee_cap(), 472 | max_priority_fee_per_gas: tx.msg.tip(), 473 | v: tx.msg.v().as_u64().into(), 474 | r: tx.msg.r(), 475 | s: tx.msg.s(), 476 | ..Default::default() 477 | } 478 | } 479 | } 480 | 481 | #[cfg(feature = "ethers-types")] 482 | impl From for ethers::types::transaction::eip2930::AccessListItem { 483 | fn from(src: AccessTuple) -> Self { 484 | Self { 485 | address: src.address, 486 | storage_keys: src.slots, 487 | } 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/erigon/tables.rs: -------------------------------------------------------------------------------- 1 | use crate::{dupsort_table, erigon::models::*, table}; 2 | use bytes::Bytes; 3 | use ethereum_types::{Address, H256, U256}; 4 | use roaring::RoaringTreemap; 5 | 6 | // --- Erigon db schema version 6.0.0 --- 7 | 8 | // || indicates concatenation 9 | 10 | // Table name => Key => Value 11 | // key: bytes("LastHeader"). val: hash of current canonical head header. erigon: HeadHeaderKey 12 | table!(LastHeader => LastHeaderKey => H256); 13 | // key: bytes("LastBlock"). val: hash of current canonical head block. erigon: HeadBlockKey 14 | table!(LastBlock => LastBlockKey => H256); 15 | // key: address. val: incarnation of account when it was last deleted 16 | table!(IncarnationMap => Address => Incarnation); 17 | // key: tx_hash. val: blocknum containing the tx. erigon: TxLookup 18 | table!(BlockTransactionLookup => H256 => U256); 19 | // key: header_hash. val: blocknum 20 | table!(HeaderNumber => H256 => BlockNumber); 21 | // key: blocknum||blockhash. val: rlp(header). erigon: Headers 22 | table!(Header => HeaderKey => BlockHeader, seek_key = BlockNumber); 23 | // key: blocknum||blockhash. val: encode(block_body) 24 | table!(BlockBody => HeaderKey => BodyForStorage, seek_key = BlockNumber); 25 | // key: address||incarnation. val: code_hash. erigon: PlainContractCode 26 | table!(PlainCodeHash => PlainCodeKey => H256); 27 | // key: blocknum||blockhash. val: senders list. erigon: Senders 28 | table!(TxSender => HeaderKey => Vec
); 29 | // key: blocknum. val: blockhash. erigon: HeaderCanonical 30 | table!(CanonicalHeader => BlockNumber => H256); 31 | // key: index. val: rlp(tx). transaction. erigon: EthTx 32 | table!(BlockTransaction => TxIndex => Transaction); 33 | // key: index. val: rlp(tx). erigon: NonCanonicalTxs 34 | table!(NonCanonicalTransaction => TxIndex => Transaction); 35 | // key: address||shard_id_u64. val: bitmap of blocks w/ change. erigon: AccountsHistory 36 | table!(AccountHistory => AccountHistKey => RoaringTreemap); 37 | // key: address||slot||shard_id_u64. val: bitmap of blocks w/ change. 38 | table!(StorageHistory => StorageHistKey => RoaringTreemap); 39 | // key: blocknum. val: address||encode(account) 40 | dupsort_table!(AccountChangeSet => BlockNumber => AccountCSVal, subkey = Address); 41 | // key: blocknum||address||incarnation. val: slot||slot_value 42 | dupsort_table!(StorageChangeSet => StorageCSKey => StorageCSVal, subkey = H256); 43 | // key: address. val: encode(account). PlainState table also contains Storage. 44 | table!(PlainState => Address => Account); 45 | // key: address||incarnation. val: slot||slot_value (dupsorted). erigon: PlainState 46 | dupsort_table!( 47 | Storage => StorageKey => (H256, U256), 48 | subkey = H256, 49 | rename = PlainState 50 | ); 51 | 52 | // key: keccak(address). val: encode(account). erigon: HashedAcccounts 53 | table!(HashedAccount => H256 => Account); 54 | //TODO: also dupsorted 55 | // key: keccak(address)||incarnation||keccak(slot). val: slot_value 56 | table!(HashedStorage => HashStorageKey => U256); 57 | // key: code_hash. val: contract code 58 | table!(Code => H256 => Bytecode); 59 | // key: keccak256(address)||incarnation. val: code_hash. erigon: ContractCode 60 | table!(HashedCodeHash => ContractCodeKey => H256); 61 | // key: bytestring. val: bytestring. erigon: DatabaseInfo 62 | table!(DbInfo => Bytes => Bytes); 63 | // key: blocknum||blockhash. val: rlp(total_difficulty big.Int). erigon: HeaderTD 64 | table!(HeadersTotalDifficulty => HeaderKey => TotalDifficulty); 65 | // key: blocknum. val: total_issued 66 | table!(Issuance => BlockNumber => U256); 67 | // key: bytes("burnt")||bloknum. val: total_burnt. erigon: Issuance 68 | table!(Burnt => BurntKey => U256, rename = Issuance); 69 | // key: code_hash. value: contract_TEVM_code. erigon: ContractTEVMCode. Unused. 70 | table!(TEVMCode => H256 => Bytes); 71 | // receipts are only stored for canonical blocks 72 | // key: blocknum. val: cbor(receipt). erigon: Receipts 73 | table!(Receipt => BlockNumber => CborReceipts); 74 | // key: blocknum||log_index_in_tx. val: cbor(log). erigon: Log 75 | table!(TransactionLog => LogsKey => CborLogs); 76 | 77 | type Todo = Bytes; 78 | // erigon: TrieOfAccounts 79 | table!(TrieAccount => Todo => Todo); 80 | // erigon: TrieOfStorage 81 | table!(TrieStorage => Todo => Todo); 82 | table!(LogTopicIndex => Todo => Todo); 83 | table!(LogAddressIndex => Todo => Todo); 84 | // key: blocknum||address. 85 | dupsort_table!(CallTraceSet => Todo => Todo, subkey = Todo); 86 | -------------------------------------------------------------------------------- /src/erigon/utils/consts.rs: -------------------------------------------------------------------------------- 1 | use ethereum_types::{Address, H256}; 2 | 3 | pub const KECCAK_LENGTH: usize = H256::len_bytes(); 4 | pub const ADDRESS_LENGTH: usize = Address::len_bytes(); 5 | pub const U64_LENGTH: usize = std::mem::size_of::(); 6 | pub const BLOOM_BYTE_LENGTH: usize = 256; 7 | 8 | // keccak256("") 9 | pub const EMPTY_HASH: H256 = H256(hex_literal::hex!( 10 | "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" 11 | )); 12 | -------------------------------------------------------------------------------- /src/erigon/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use fastrlp::DecodeError; 3 | use roaring::RoaringTreemap; 4 | use tiny_keccak::{Hasher, Keccak}; 5 | 6 | pub mod consts; 7 | use consts as C; 8 | 9 | // https://github.com/ledgerwatch/erigon/blob/f9d7cb5ca9e8a135a76ddcb6fa4ee526ea383554/ethdb/bitmapdb/dbutils.go#L313 10 | pub fn find_gte(map: RoaringTreemap, n: u64) -> Option { 11 | // rank() returns the number of integers in the map <= n, i.e. the index 12 | // of n if it were in the bitmap. 13 | let rank = map.rank(n.saturating_sub(1)); 14 | map.select(rank) 15 | } 16 | 17 | // From ethers: https://github.com/gakonst/ethers-rs/blob/master/ethers-core/src/utils/hash.rs#L26 18 | pub fn keccak256(bytes: S) -> [u8; 32] 19 | where 20 | S: AsRef<[u8]>, 21 | { 22 | let mut output = [0u8; 32]; 23 | let mut hasher = Keccak::v256(); 24 | hasher.update(bytes.as_ref()); 25 | hasher.finalize(&mut output); 26 | output 27 | } 28 | 29 | /// advances buf past an rlp-encoded u64, returning the u64 left-padded with zeroes 30 | pub fn take_u64_rlp(buf: &mut &[u8]) -> Result { 31 | if buf.is_empty() { 32 | return Err(DecodeError::InputTooShort); 33 | } 34 | let len = buf.get_u8().into(); 35 | 36 | if len > C::U64_LENGTH { 37 | return Err(DecodeError::UnexpectedLength); 38 | } 39 | let val = bytes_to_u64(&buf[..len]); 40 | buf.advance(len); 41 | 42 | Ok(val) 43 | } 44 | // https://github.com/akula-bft/akula/blob/a9aed09b31bb41c89832149bcad7248f7fcd70ca/src/models/account.rs#L47 45 | pub fn bytes_to_u64(buf: &[u8]) -> u64 { 46 | let mut decoded = [0u8; 8]; 47 | for (i, b) in buf.iter().rev().enumerate() { 48 | decoded[i] = *b; 49 | } 50 | u64::from_le_bytes(decoded) 51 | } 52 | -------------------------------------------------------------------------------- /src/kv/mod.rs: -------------------------------------------------------------------------------- 1 | use eyre::Result; 2 | use mdbx::{DatabaseFlags, EnvironmentKind, NoWriteMap, TransactionKind, WriteFlags, RO, RW}; 3 | use std::{borrow::Cow, path::Path}; 4 | 5 | pub mod tables; 6 | pub mod traits; 7 | 8 | use tables::TableHandle; 9 | use traits::{DbFlags, DbName, DupSort, Mode, Table, TableDecode, TableEncode}; 10 | 11 | fn open_env( 12 | path: &Path, 13 | num_tables: usize, 14 | flags: mdbx::EnvironmentFlags, 15 | ) -> Result> { 16 | mdbx::Environment::new() 17 | .set_max_dbs(num_tables) 18 | .set_flags(flags) 19 | .open(path) 20 | .map_err(From::from) 21 | } 22 | 23 | /// A wrapper around [`mdbx::Environment`]. 24 | /// 25 | /// We use this wrapper to make a few alterations on the default behavior: 26 | /// - The mode the environment is opened in becomes part of the type signature. 27 | /// You cannot open a read-write transaction using a `MdbxEnv`, and you 28 | /// cannot get a `MdbxEnv` from a `MdbxEnv`. You can, however, move out 29 | /// of the struct and do whatever you want with the inner `mdbx::Environment`, 30 | /// safe or unsafe. You should only ever open a single mdbx environment from 31 | /// the same process. 32 | /// - The `mdbx::EnvironmentKind` is forced to `NoWriteMap`. MDBX_WRITEMAP 33 | /// mode maps data into memory with write permission. This means stray writes 34 | /// through pointers can silently corrupt the db. It's also slower when 35 | /// db size > RAM, so we ignore it. 36 | #[derive(Debug)] 37 | pub struct MdbxEnv { 38 | pub inner: mdbx::Environment, 39 | _mode: std::marker::PhantomData, 40 | } 41 | impl MdbxEnv { 42 | pub fn inner(&self) -> &mdbx::Environment { 43 | &self.inner 44 | } 45 | } 46 | 47 | impl MdbxEnv { 48 | /// Open an mdbx environment. Note that even when opening an environment in 49 | /// read-only mode, mdbx will still modify the LCK-file, unless the filesystem 50 | /// is read-only. 51 | pub fn open(path: &Path, num_tables: usize, flags: EnvFlags) -> Result { 52 | let mode = if M::is_writeable() { 53 | mdbx::Mode::ReadWrite { 54 | sync_mode: mdbx::SyncMode::Durable, 55 | } 56 | } else { 57 | mdbx::Mode::ReadOnly 58 | }; 59 | Ok(Self { 60 | inner: open_env(path, num_tables, flags.with_mode(mode))?, 61 | _mode: std::marker::PhantomData, 62 | }) 63 | } 64 | 65 | /// Create a read-only mdbx transaction. 66 | pub fn begin_ro(&self) -> Result> { 67 | Ok(MdbxTx::new(self.inner.begin_ro_txn()?)) 68 | } 69 | } 70 | 71 | impl MdbxEnv { 72 | /// Create a read-only mdbx transaction. 73 | pub fn begin(&self) -> Result> { 74 | Ok(MdbxTx::new(self.inner.begin_ro_txn()?)) 75 | } 76 | } 77 | 78 | impl MdbxEnv { 79 | /// Create a read-write mdbx transaction. Blocks if another rw transaction is open. 80 | pub fn begin_rw(&self) -> Result> { 81 | Ok(MdbxTx::new(self.inner.begin_rw_txn()?)) 82 | } 83 | } 84 | 85 | /// Holds all [`mdbx::EnvironmentFlags`] except the `mode` field. 86 | #[derive(Clone, Copy, Debug, Default)] 87 | pub struct EnvFlags { 88 | /// Disable readahead. Improves random read performance when db size > RAM. 89 | /// By default, mdbx will dynamically determine whether to disable readahead. 90 | pub no_rdahead: bool, 91 | /// Attempt to [coalesce](https://en.wikipedia.org/wiki/Coalescing_(computer_science)) while garbage collecting. 92 | pub coalesce: bool, 93 | /// If the environment is already in use by another process with unknown flags, 94 | /// by default an MDBX_INCOMPATIBLE error will be thrown. If `accede` is set, 95 | /// the requested table will instead be opened with the existing flags. 96 | pub accede: bool, 97 | /// By default, mdbx interprets the given path as a directory under which 98 | /// the lock file and storage file will be found or created. If `no_sub_dir` 99 | /// is set, this path is instead interpreted to be the storage file itself. 100 | pub no_sub_dir: bool, 101 | /// Attempt to take an exclusive lock on the environment. If another process 102 | /// is already using the environment, returns MDBX_BUSY. 103 | pub exclusive: bool, 104 | /// If enabled, don't initialize freshly malloc'd pages with zeroes. This can 105 | /// result in persisting garbage data. 106 | pub no_meminit: bool, 107 | /// Replace the default FIFO garbage collection policy with LIFO. 108 | pub liforeclaim: bool, 109 | } 110 | impl EnvFlags { 111 | /// Creates an [`mdbx::EnvironmentFlags`] struct with the requested mode. 112 | pub fn with_mode(self, mode: mdbx::Mode) -> mdbx::EnvironmentFlags { 113 | mdbx::EnvironmentFlags { 114 | mode, 115 | no_sub_dir: self.no_sub_dir, 116 | exclusive: self.exclusive, 117 | accede: self.accede, 118 | no_rdahead: self.no_rdahead, 119 | no_meminit: self.no_meminit, 120 | coalesce: self.coalesce, 121 | liforeclaim: self.liforeclaim, 122 | } 123 | } 124 | } 125 | 126 | /// A wrapper around [`mdbx::Transaction`]. 127 | #[derive(Debug)] 128 | pub struct MdbxTx<'env, K: TransactionKind> { 129 | pub inner: mdbx::Transaction<'env, K, NoWriteMap>, 130 | } 131 | impl<'env, M> MdbxTx<'env, M> 132 | where 133 | M: TransactionKind + Mode, 134 | { 135 | pub fn open_db(&self) -> Result> { 136 | let mut flags = Flags::FLAGS; 137 | // If the transaction is read-write, create the database if it does not exist already. 138 | if M::is_writeable() { 139 | flags |= DatabaseFlags::CREATE; 140 | } 141 | Ok(TableHandle::new( 142 | self.inner.open_db_with_flags(Some(Db::NAME), flags)?, 143 | )) 144 | } 145 | } 146 | 147 | impl<'env, K: TransactionKind> MdbxTx<'env, K> { 148 | pub fn new(inner: mdbx::Transaction<'env, K, NoWriteMap>) -> Self { 149 | Self { inner } 150 | } 151 | 152 | pub fn get<'tx, T, F>( 153 | &'tx self, 154 | db: TableHandle<'tx, T::Name, F>, 155 | key: T::Key, 156 | ) -> Result> 157 | where 158 | T: Table<'tx>, 159 | F: DbFlags, 160 | { 161 | self.inner 162 | .get(db.as_ref(), key.encode().as_ref())? 163 | .map(decode_one::) 164 | .transpose() 165 | } 166 | 167 | pub fn cursor<'tx, T, F>( 168 | &'tx self, 169 | db: TableHandle<'tx, T::Name, F>, 170 | ) -> Result> 171 | where 172 | T: Table<'tx>, 173 | F: DbFlags, 174 | { 175 | Ok(MdbxCursor::new(self.inner.cursor(db.as_ref())?)) 176 | } 177 | } 178 | 179 | impl<'env> MdbxTx<'env, RW> { 180 | pub fn put<'tx, T, F>( 181 | &'tx self, 182 | db: TableHandle<'tx, T::Name, F>, 183 | key: T::Key, 184 | val: T::Value, 185 | ) -> Result<()> 186 | where 187 | T: Table<'tx>, 188 | F: DbFlags, 189 | { 190 | self.inner 191 | .put(db.as_ref(), key.encode(), val.encode(), WriteFlags::UPSERT) 192 | .map_err(From::from) 193 | } 194 | 195 | /// Commit the transaction. The Drop impl for mdbx::Transaction will take care 196 | /// of this, but use this method explicitly if you wish to handle any errors. 197 | pub fn commit(self) -> Result { 198 | self.inner.commit().map_err(From::from) 199 | } 200 | } 201 | 202 | /// A wrapper around [`mdbx::Cursor`]. 203 | #[derive(Debug)] 204 | pub struct MdbxCursor<'tx, K, T> 205 | where 206 | K: TransactionKind, 207 | { 208 | pub inner: mdbx::Cursor<'tx, K>, 209 | _dbi: std::marker::PhantomData, 210 | } 211 | impl<'tx, K, T> MdbxCursor<'tx, K, T> 212 | where 213 | K: TransactionKind, 214 | { 215 | pub fn new(inner: mdbx::Cursor<'tx, K>) -> Self { 216 | Self { 217 | inner, 218 | _dbi: std::marker::PhantomData, 219 | } 220 | } 221 | } 222 | 223 | impl<'tx, K, T> MdbxCursor<'tx, K, T> 224 | where 225 | K: TransactionKind, 226 | T: Table<'tx>, 227 | { 228 | /// Returns the (key, value) pair at the first key >= `key` 229 | pub fn seek(&mut self, key: T::SeekKey) -> Result> 230 | where 231 | T::Key: TableDecode, 232 | { 233 | self.inner 234 | .set_range(key.encode().as_ref())? 235 | .map(decode::) 236 | .transpose() 237 | } 238 | 239 | /// Returns the first key/value pair in the table 240 | pub fn first(&mut self) -> Result> 241 | where 242 | T::Key: TableDecode, 243 | { 244 | self.inner.first()?.map(decode::).transpose() 245 | } 246 | 247 | /// Returns the first value in the table without attempting to decode the returned key. 248 | pub fn first_val(&mut self) -> Result> { 249 | self.inner.first()?.map(decode_val::).transpose() 250 | } 251 | 252 | #[allow(clippy::should_implement_trait)] 253 | pub fn next(&mut self) -> Result> 254 | where 255 | T::Key: TableDecode, 256 | { 257 | self.inner.next()?.map(decode::).transpose() 258 | } 259 | 260 | /// Returns an owned iterator over (key, value) pairs beginning at start_key. 261 | pub fn walk( 262 | mut self, 263 | start_key: T::Key, 264 | ) -> Result>::Key, >::Value)>>> 265 | where 266 | T::Key: TableDecode, 267 | { 268 | let first = self 269 | .inner 270 | .set_range(start_key.encode().as_ref())? 271 | .map(decode::); 272 | 273 | Ok(Walker { cur: self, first }) 274 | } 275 | 276 | /// Returns an iterator over (key, value) pairs beginning at start_key. If the table 277 | /// is dupsorted (contains duplicate items for each key), all of the duplicates 278 | /// at a given key will be returned before moving on to the next key. 279 | pub fn iter( 280 | &mut self, 281 | start_key: T::Key, 282 | ) -> impl Iterator>::Key, >::Value)>> + '_ 283 | where 284 | T::Key: TableDecode, 285 | { 286 | self.inner 287 | .iter_from(start_key.encode().as_ref()) 288 | .map(|res| decode::(res?)) 289 | } 290 | 291 | /// Returns an iterator over values beginning at start_key, without attempting 292 | /// to decode the returned keys (only the values). If the table is dupsorted 293 | /// (contains duplicate items for each key), all of the duplicates at a 294 | /// given key will be returned before moving on to the next key. 295 | pub fn iter_val( 296 | &mut self, 297 | start_key: T::Key, 298 | ) -> impl Iterator>::Value>> + '_ { 299 | self.inner 300 | .iter_from(start_key.encode().as_ref()) 301 | .map(|res| decode_val::(res?)) 302 | } 303 | } 304 | 305 | impl<'tx, K, T> MdbxCursor<'tx, K, T> 306 | where 307 | K: TransactionKind, 308 | T: DupSort<'tx>, 309 | { 310 | /// Finds the given key in the table, then the first duplicate entry at that 311 | /// key with data >= subkey, and returns this value. Note that the value 312 | /// returned includes the subkey prefix, meaning you likely want to decode 313 | /// it into `(subkey, value_at_subkey)`. 314 | /// 315 | /// If you want to find an exact subkey in the dupsort "sub table", you need 316 | /// to check that the returned value begins with your subkey. If it does not, 317 | /// then the cursor seeked past the requested subkey without a match, meaning 318 | /// the table does not contain a value that begins with the provided subkey. 319 | pub fn seek_dup(&mut self, key: T::Key, subkey: T::Subkey) -> Result> { 320 | self.inner 321 | .get_both_range(key.encode().as_ref(), subkey.encode().as_ref())? 322 | .map(decode_one::) 323 | .transpose() 324 | } 325 | 326 | /// Returns the current key and the next duplicate value at that key. Note 327 | /// that the value returned includes the subkey prefix, meaning you likely 328 | /// want to decode it into `(subkey, value_at_subkey)`. 329 | pub fn next_dup(&mut self) -> Result> 330 | where 331 | T::Key: TableDecode, 332 | { 333 | self.inner.next_dup()?.map(decode::).transpose() 334 | } 335 | 336 | /// Returns the next duplicate value at the current key, without attempting 337 | /// to decode the table key. Note that the value returned includes the 338 | /// subkey prefix, meaning you likely want to decode it into 339 | /// `(subkey, value_at_subkey)`. 340 | pub fn next_dup_val(&mut self) -> Result> { 341 | self.inner.next_dup()?.map(decode_val::).transpose() 342 | } 343 | 344 | /// Returns an owned iterator over duplicate values for the given key. Note 345 | /// that the values returned include the subkey prefix, meaning you likely 346 | /// want to decode them into `(subkey, value_at_subkey)`. 347 | pub fn walk_dup( 348 | mut self, 349 | key: T::Key, 350 | subkey: T::Subkey, 351 | ) -> Result>::Value>>> { 352 | let first = self 353 | .inner 354 | .get_both_range(key.encode().as_ref(), subkey.encode().as_ref())? 355 | .map(decode_one::); 356 | 357 | Ok(DupWalker { cur: self, first }) 358 | } 359 | } 360 | 361 | // Helper functions, primarily for type inference. These save us from needing 362 | // to specify the TableObject type we expect from every mdbx function call. 363 | pub fn decode<'tx, T>(kv: (Cow<'tx, [u8]>, Cow<'tx, [u8]>)) -> Result<(T::Key, T::Value)> 364 | where 365 | T: Table<'tx>, 366 | T::Key: TableDecode, 367 | { 368 | Ok((TableDecode::decode(&kv.0)?, TableDecode::decode(&kv.1)?)) 369 | } 370 | // Decodes only the value, ignoring the returned key. 371 | pub fn decode_val<'tx, T>(kv: (Cow<'tx, [u8]>, Cow<'tx, [u8]>)) -> Result 372 | where 373 | T: Table<'tx>, 374 | { 375 | TableDecode::decode(&kv.1) 376 | } 377 | // Decodes a single value. 378 | pub fn decode_one<'tx, T>(val: Cow<'tx, [u8]>) -> Result 379 | where 380 | T: Table<'tx>, 381 | { 382 | TableDecode::decode(&val) 383 | } 384 | 385 | /// An internal struct for turning a cursor to a dupsorted table into an iterator 386 | /// over values in that table. 387 | /// 388 | /// See [Akula](https://github.com/akula-bft/akula/blob/1800ac77b979d410bea5ff3bcd2617cb302d66fe/src/kv/mdbx.rs#L432) 389 | /// for a much more interesting approach using generators. 390 | struct DupWalker<'tx, K, T> 391 | where 392 | K: TransactionKind, 393 | T: Table<'tx>, 394 | { 395 | pub cur: MdbxCursor<'tx, K, T>, 396 | pub first: Option>, 397 | } 398 | 399 | impl<'tx, K, T> std::iter::Iterator for DupWalker<'tx, K, T> 400 | where 401 | K: TransactionKind, 402 | T: DupSort<'tx>, 403 | { 404 | type Item = Result; 405 | fn next(&mut self) -> Option { 406 | let first = self.first.take(); 407 | if first.is_some() { 408 | return first; 409 | } 410 | self.cur.next_dup_val().transpose() 411 | } 412 | } 413 | 414 | /// An internal struct for turning a cursor to a table into an iterator 415 | /// over key/value pairs in that table. 416 | /// 417 | /// See [Akula](https://github.com/akula-bft/akula/blob/1800ac77b979d410bea5ff3bcd2617cb302d66fe/src/kv/mdbx.rs#L319) 418 | /// for a much more interesting approach using generators. 419 | struct Walker<'tx, K, T> 420 | where 421 | K: TransactionKind, 422 | T: Table<'tx>, 423 | { 424 | pub cur: MdbxCursor<'tx, K, T>, 425 | pub first: Option>, 426 | } 427 | 428 | impl<'tx, K, T> std::iter::Iterator for Walker<'tx, K, T> 429 | where 430 | K: TransactionKind, 431 | T: Table<'tx>, 432 | T::Key: TableDecode, 433 | { 434 | type Item = Result<(T::Key, T::Value)>; 435 | fn next(&mut self) -> Option { 436 | let first = self.first.take(); 437 | if first.is_some() { 438 | return first; 439 | } 440 | self.cur.next().transpose() 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/kv/tables.rs: -------------------------------------------------------------------------------- 1 | use arrayvec::ArrayVec; 2 | use derive_more::{Deref, DerefMut}; 3 | use ethereum_types::{Address, H256, U256}; 4 | use eyre::{eyre, Result}; 5 | use mdbx::DatabaseFlags; 6 | use roaring::RoaringTreemap; 7 | use std::{ 8 | convert::AsRef, 9 | fmt::{Debug, Display}, 10 | ops::Deref, 11 | }; 12 | 13 | use crate::kv::traits::*; 14 | 15 | const KECCAK_LENGTH: usize = 32; 16 | const ADDRESS_LENGTH: usize = 20; 17 | 18 | pub struct TableHandle<'tx, Dbi, Flags> { 19 | inner: mdbx::Database<'tx>, 20 | _dbi: std::marker::PhantomData<(Dbi, Flags)>, 21 | } 22 | impl<'tx, Dbi, Flags: DbFlags> TableHandle<'tx, Dbi, Flags> { 23 | pub fn new(inner: mdbx::Database<'tx>) -> Self { 24 | Self { 25 | inner, 26 | _dbi: std::marker::PhantomData, 27 | } 28 | } 29 | pub fn inner(&self) -> &mdbx::Database<'tx> { 30 | &self.inner 31 | } 32 | } 33 | impl<'tx, Dbi, Flags: DbFlags> Deref for TableHandle<'tx, Dbi, Flags> { 34 | type Target = mdbx::Database<'tx>; 35 | fn deref(&self) -> &Self::Target { 36 | self.inner() 37 | } 38 | } 39 | impl<'tx, Dbi, Flags: DbFlags> AsRef> for TableHandle<'tx, Dbi, Flags> { 40 | fn as_ref(&self) -> &mdbx::Database<'tx> { 41 | self.inner() 42 | } 43 | } 44 | 45 | pub struct NoFlags; 46 | impl DbFlags for NoFlags { 47 | const FLAGS: DatabaseFlags = DatabaseFlags::empty(); 48 | } 49 | pub struct DupSortFlags; 50 | impl DbFlags for DupSortFlags { 51 | const FLAGS: DatabaseFlags = DatabaseFlags::DUP_SORT; 52 | } 53 | #[macro_export] 54 | macro_rules! table_without_flags { 55 | ($name:ident => $key:ty => $value:ty, seek_key = $seek_key:ty, rename = $rename:ident) => { 56 | #[derive(Debug, Default, Clone, Copy)] 57 | pub struct $name; 58 | 59 | impl<'tx> $crate::kv::traits::Table<'tx> for $name { 60 | type Name = Self; 61 | type Key = $key; 62 | type SeekKey = $seek_key; 63 | type Value = $value; 64 | } 65 | 66 | impl $crate::kv::traits::DbName for $name { 67 | const NAME: &'static str = stringify!($rename); 68 | } 69 | 70 | impl std::fmt::Display for $name { 71 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 72 | write!(f, "{}", stringify!($name)) 73 | } 74 | } 75 | }; 76 | ($name:ident => $key:ty => $value:ty, seek_key = $seek_key:ty) => { 77 | $crate::table_without_flags!($name => $key => $value, seek_key = $seek_key, rename = $name); 78 | }; 79 | ($name:ident => $key:ty => $value:ty, rename = $rename:ident) => { 80 | $crate::table_without_flags!($name => $key => $value, seek_key = $key, rename = $rename); 81 | }; 82 | ($name:ident => $key:ty => $value:ty) => { 83 | $crate::table_without_flags!($name => $key => $value, seek_key = $key, rename = $name); 84 | }; 85 | } 86 | 87 | #[macro_export] 88 | macro_rules! table { 89 | ( 90 | $(#[$meta:meta])* 91 | $name:ident => $($args:tt)* 92 | ) => { 93 | $(#[$meta])* 94 | $crate::table_without_flags!($name => $($args)*); 95 | impl $crate::kv::traits::DefaultFlags for $name { 96 | type Flags = $crate::kv::tables::NoFlags; 97 | } 98 | }; 99 | } 100 | #[macro_export] 101 | macro_rules! dupsort_table { 102 | ($name:ident => $key:ty => $value:ty, subkey = $subkey:ty, rename = $rename:ident) => { 103 | $crate::table_without_flags!($name => $key => $value, rename = $rename); 104 | impl $crate::kv::traits::DefaultFlags for $name { 105 | type Flags = $crate::kv::tables::DupSortFlags; 106 | } 107 | impl crate::kv::traits::DupSort<'_> for $name { 108 | type Subkey = $subkey; 109 | } 110 | }; 111 | ($name:ident => $key:ty => $value:ty, subkey = $subkey:ty) => { 112 | $crate::dupsort_table!($name => $key => $value, subkey = $subkey, rename = $name); 113 | } 114 | } 115 | 116 | // -- Key/Value Encoding/Decoding -- 117 | 118 | impl TableEncode for () { 119 | type Encoded = [u8; 0]; 120 | fn encode(self) -> Self::Encoded { 121 | [] 122 | } 123 | } 124 | 125 | impl TableDecode for () { 126 | fn decode(b: &[u8]) -> Result { 127 | if !b.is_empty() { 128 | return Err(TooLong::<0> { got: b.len() }.into()); 129 | } 130 | Ok(()) 131 | } 132 | } 133 | 134 | impl TableEncode for Vec { 135 | type Encoded = Self; 136 | 137 | fn encode(self) -> Self::Encoded { 138 | self 139 | } 140 | } 141 | 142 | impl TableDecode for Vec { 143 | fn decode(b: &[u8]) -> Result { 144 | Ok(b.to_vec()) 145 | } 146 | } 147 | 148 | #[derive(Clone, Debug, Default, Deref, DerefMut, PartialEq, Eq, PartialOrd, Ord)] 149 | pub struct VariableVec { 150 | pub inner: ArrayVec, 151 | } 152 | 153 | impl FromIterator for VariableVec { 154 | fn from_iter>(iter: T) -> Self { 155 | Self { 156 | inner: ArrayVec::from_iter(iter), 157 | } 158 | } 159 | } 160 | 161 | impl AsRef<[u8]> for VariableVec { 162 | fn as_ref(&self) -> &[u8] { 163 | self.inner.as_ref() 164 | } 165 | } 166 | 167 | impl TableEncode for VariableVec { 168 | type Encoded = Self; 169 | 170 | fn encode(self) -> Self::Encoded { 171 | self 172 | } 173 | } 174 | 175 | impl TableDecode for VariableVec { 176 | fn decode(b: &[u8]) -> Result { 177 | let mut out = Self::default(); 178 | out.try_extend_from_slice(b)?; 179 | Ok(out) 180 | } 181 | } 182 | 183 | impl From> for Vec { 184 | fn from(v: VariableVec) -> Self { 185 | v.to_vec() 186 | } 187 | } 188 | 189 | #[derive(Clone, Debug)] 190 | pub struct InvalidLength { 191 | pub got: usize, 192 | } 193 | 194 | impl Display for InvalidLength { 195 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 196 | write!(f, "Invalid length: {} != {}", EXPECTED, self.got) 197 | } 198 | } 199 | 200 | impl std::error::Error for InvalidLength {} 201 | 202 | #[derive(Clone, Debug)] 203 | pub struct TooShort { 204 | pub got: usize, 205 | } 206 | 207 | impl Display for TooShort { 208 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 209 | write!(f, "Too short: {} < {}", self.got, MINIMUM) 210 | } 211 | } 212 | 213 | impl std::error::Error for TooShort {} 214 | 215 | #[derive(Clone, Debug)] 216 | pub struct TooLong { 217 | pub got: usize, 218 | } 219 | impl Display for TooLong { 220 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 221 | write!(f, "Too long: {} > {}", self.got, MAXIMUM) 222 | } 223 | } 224 | 225 | impl std::error::Error for TooLong {} 226 | 227 | impl TableEncode for H256 { 228 | type Encoded = [u8; KECCAK_LENGTH]; 229 | fn encode(self) -> Self::Encoded { 230 | self.0 231 | } 232 | } 233 | 234 | impl TableDecode for H256 { 235 | fn decode(b: &[u8]) -> Result { 236 | match b.len() { 237 | KECCAK_LENGTH => Ok(H256::from_slice(&*b)), 238 | _ => Err(eyre!("bad")), 239 | } 240 | } 241 | } 242 | 243 | impl TableEncode for U256 { 244 | type Encoded = VariableVec; 245 | fn encode(self) -> Self::Encoded { 246 | let mut buf = [0; 32]; 247 | self.to_big_endian(&mut buf); 248 | buf.into_iter().skip_while(|&v| v == 0).collect() 249 | } 250 | } 251 | 252 | impl TableDecode for U256 { 253 | fn decode(b: &[u8]) -> Result { 254 | if b.len() > KECCAK_LENGTH { 255 | return Err(TooLong:: { got: b.len() }.into()); 256 | } 257 | let mut v = [0; 32]; 258 | v[KECCAK_LENGTH - b.len()..].copy_from_slice(b); 259 | Ok(Self::from_big_endian(&v)) 260 | } 261 | } 262 | 263 | impl TableEncode for Address { 264 | type Encoded = [u8; ADDRESS_LENGTH]; 265 | 266 | fn encode(self) -> Self::Encoded { 267 | self.0 268 | } 269 | } 270 | 271 | impl TableDecode for Address { 272 | fn decode(b: &[u8]) -> Result { 273 | match b.len() { 274 | ADDRESS_LENGTH => Ok(Address::from_slice(&*b)), 275 | other => Err(InvalidLength:: { got: other }.into()), 276 | } 277 | } 278 | } 279 | 280 | impl TableEncode for (H256, U256) { 281 | type Encoded = VariableVec<{ KECCAK_LENGTH + KECCAK_LENGTH }>; 282 | 283 | fn encode(self) -> Self::Encoded { 284 | let mut out = Self::Encoded::default(); 285 | out.try_extend_from_slice(&self.0.encode()).unwrap(); 286 | out.try_extend_from_slice(&self.1.encode()).unwrap(); 287 | out 288 | } 289 | } 290 | 291 | impl TableDecode for (H256, U256) { 292 | fn decode(b: &[u8]) -> Result { 293 | if b.len() > KECCAK_LENGTH + KECCAK_LENGTH { 294 | return Err(TooLong::<{ KECCAK_LENGTH + KECCAK_LENGTH }> { got: b.len() }.into()); 295 | } 296 | 297 | if b.len() < KECCAK_LENGTH { 298 | return Err(TooShort::<{ KECCAK_LENGTH }> { got: b.len() }.into()); 299 | } 300 | 301 | let (location, value) = b.split_at(KECCAK_LENGTH); 302 | 303 | Ok((H256::decode(location)?, U256::decode(value)?)) 304 | } 305 | } 306 | 307 | impl TableEncode for (A, B) 308 | where 309 | A: TableObject, 310 | B: TableObject, 311 | { 312 | type Encoded = VariableVec<256>; 313 | 314 | fn encode(self) -> Self::Encoded { 315 | let mut v = Self::Encoded::default(); 316 | v.try_extend_from_slice(&self.0.encode()).unwrap(); 317 | v.try_extend_from_slice(&self.1.encode()).unwrap(); 318 | v 319 | } 320 | } 321 | 322 | impl TableDecode for (A, B) 323 | where 324 | A: TableObject, 325 | B: TableObject, 326 | { 327 | fn decode(v: &[u8]) -> Result { 328 | if v.len() != A_LEN + B_LEN { 329 | eyre::bail!("Invalid len: {} != {} + {}", v.len(), A_LEN, B_LEN); 330 | } 331 | Ok(( 332 | A::decode(&v[..A_LEN]).unwrap(), 333 | B::decode(&v[A_LEN..]).unwrap(), 334 | )) 335 | } 336 | } 337 | 338 | impl TableEncode for RoaringTreemap { 339 | type Encoded = Vec; 340 | fn encode(self) -> Self::Encoded { 341 | let mut buf = Vec::with_capacity(self.serialized_size()); 342 | self.serialize_into(&mut buf).unwrap(); 343 | buf 344 | } 345 | } 346 | impl TableDecode for RoaringTreemap { 347 | fn decode(b: &[u8]) -> Result { 348 | Ok(RoaringTreemap::deserialize_from(b)?) 349 | } 350 | } 351 | 352 | impl TableEncode for bytes::Bytes { 353 | type Encoded = Self; 354 | 355 | fn encode(self) -> Self::Encoded { 356 | self 357 | } 358 | } 359 | 360 | impl TableDecode for bytes::Bytes { 361 | fn decode(b: &[u8]) -> Result { 362 | Ok(b.to_vec().into()) 363 | } 364 | } 365 | 366 | impl TableEncode for u32 { 367 | type Encoded = [u8; 4]; 368 | 369 | fn encode(self) -> Self::Encoded { 370 | self.to_be_bytes() 371 | } 372 | } 373 | 374 | impl TableDecode for u32 { 375 | fn decode(b: &[u8]) -> Result { 376 | match b.len() { 377 | 4 => Ok(u32::from_be_bytes(*arrayref::array_ref!(&*b, 0, 4))), 378 | other => Err(InvalidLength::<4> { got: other }.into()), 379 | } 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /src/kv/traits.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | pub trait TableEncode: Send + Sync + Sized { 4 | type Encoded: AsRef<[u8]> + Send + Sync; 5 | fn encode(self) -> Self::Encoded; 6 | } 7 | 8 | pub trait TableDecode: Send + Sync + Sized { 9 | fn decode(b: &[u8]) -> eyre::Result; 10 | } 11 | 12 | pub trait TableObject: TableEncode + TableDecode {} 13 | 14 | impl TableObject for T where T: TableEncode + TableDecode {} 15 | 16 | pub trait Table<'tx>: Send + Sync + Debug + 'static { 17 | type Name: DbName; 18 | type Key: TableEncode; 19 | type Value: TableObject; 20 | type SeekKey: TableEncode; 21 | } 22 | 23 | pub trait DupSort<'tx>: Table<'tx> { 24 | type Subkey: TableObject; 25 | } 26 | 27 | pub trait DbName { 28 | const NAME: &'static str; 29 | } 30 | 31 | pub trait DbFlags { 32 | const FLAGS: mdbx::DatabaseFlags; 33 | } 34 | pub trait DefaultFlags { 35 | type Flags: DbFlags; 36 | } 37 | 38 | pub trait Mode: mdbx::TransactionKind { 39 | fn is_writeable() -> bool; 40 | } 41 | impl<'env> Mode for mdbx::RO { 42 | fn is_writeable() -> bool { 43 | false 44 | } 45 | } 46 | impl<'env> Mode for mdbx::RW { 47 | fn is_writeable() -> bool { 48 | true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![doc = include_str!("../doc/mdbx.md")] 3 | pub mod erigon; 4 | pub mod kv; 5 | pub use erigon::*; 6 | 7 | #[cfg(test)] 8 | mod tests { 9 | use super::*; 10 | use crate::{erigon::Erigon, kv::MdbxEnv}; 11 | use ethereum_types::*; 12 | use once_cell::sync::Lazy; 13 | use std::{path::Path, sync::Arc}; 14 | 15 | struct TempMdbxEnv { 16 | pub inner: MdbxEnv, 17 | _leak: tempfile::TempDir, 18 | } 19 | 20 | static ENV: Lazy>> = Lazy::new(|| { 21 | let dir = tempfile::tempdir().unwrap(); 22 | let inner = erigon::env_open(dir.path()).expect("failed to open mem db"); 23 | Arc::new(TempMdbxEnv { inner, _leak: dir }) 24 | }); 25 | 26 | #[test] 27 | fn test_mem_db() -> eyre::Result<()> { 28 | let env = ENV.clone(); 29 | let db = Erigon::begin_rw(&env.inner)?; 30 | let hash = H256::from_low_u64_be(u64::MAX); 31 | db.write_head_header_hash(hash)?; 32 | let res = db.read_head_header_hash()?.unwrap(); 33 | assert_eq!(res, hash); 34 | Ok(()) 35 | } 36 | 37 | #[test] 38 | fn test_live() -> eyre::Result<()> { 39 | let path = Path::new(env!("ERIGON_CHAINDATA")); 40 | let env = env_open(path)?; 41 | let db = Erigon::begin(&env)?; 42 | 43 | let _dst: Address = "0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B".parse()?; 44 | let contract: Address = "0x0d4c6c6605a729a379216c93e919711a081beba2".parse()?; 45 | let _res = db.read_account_hist(contract, 3)?; 46 | 47 | let slot = H256::from_low_u64_be(1); 48 | let res = db.read_storage_hist(contract, 1, slot, 0)?; 49 | let current = db.read_storage(contract, 2, slot)?; 50 | dbg!(res); 51 | dbg!(current); 52 | for read in db.walk_storage(contract, 1, None)? { 53 | let (key, val) = read?; 54 | dbg!(key, val); 55 | } 56 | 57 | let hash = db.read_head_header_hash()?.unwrap(); 58 | let num = db.read_header_number(hash)?.unwrap(); 59 | 60 | let td = db.read_total_difficulty((num, hash))?.unwrap(); 61 | dbg!(td); 62 | 63 | // let burnt = db.read::(1.into())?.unwrap(); 64 | // dbg!(burnt); 65 | Ok(()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/txgen.rs: -------------------------------------------------------------------------------- 1 | use ethers::{abi::Abi, prelude::*, signers::LocalWallet, utils::format_ether}; 2 | use eyre::{eyre, Result}; 3 | use std::{fs, path::Path, sync::Arc, time::Duration}; 4 | 5 | /// Temporary script used for seeding test data 6 | 7 | #[cfg(feature = "txgen")] 8 | mod bindings; 9 | use bindings::{factory::*, store::*}; 10 | 11 | const ENDPOINT: &str = "http://localhost:8545"; 12 | const BUILD_DIR: &str = env!("SOLC_BUILD_DIR"); 13 | 14 | macro_rules! factory { 15 | ($contract:literal, $client:stmt) => { 16 | paste::paste! { 17 | make_factory( 18 | $contract, 19 | crate::bindings:: [<$contract>] :: [<$contract:camel:upper _ABI>] .clone(), 20 | $client) 21 | } 22 | }; 23 | } 24 | 25 | #[tokio::main] 26 | async fn main() -> Result<()> { 27 | let provider = Provider::::try_from(ENDPOINT) 28 | .map_err(|e| eyre!("Could not establish provider: {}", e))? 29 | .interval(Duration::from_millis(1)); 30 | let client = std::sync::Arc::new(provider); 31 | let chainid = client.get_chainid().await?.as_u32() as u16; 32 | 33 | // address: 0x67b1d87101671b127f5f8714789C7192f7ad340e 34 | let src = "26e86e45f6fc45ec6e2ecd128cec80fa1d1505e5507dcd2ae58c3130a7a97b48" 35 | .parse::()? 36 | .with_chain_id(chainid); 37 | let signer = Arc::new(SignerMiddleware::new(client.clone(), src)); 38 | let dst: Address = "0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B".parse()?; 39 | 40 | let bal = client.get_balance(signer.address(), None).await?; 41 | dbg!(format_ether(bal)); 42 | 43 | // Send a transfer 44 | let tx = TransactionRequest::new().to(dst).value(100_usize); 45 | signer.send_transaction(tx, None).await?.await?; 46 | 47 | let fac_fac = factory!("factory", signer.clone())?; 48 | let deployed = fac_fac.deploy(())?.send().await?; 49 | //first deployed contract: 0x0d4c6c6605a729a379216c93e919711a081beba2 50 | println!("Factory address: {:?}", deployed.address()); 51 | let fac = Factory::new(deployed.address(), signer.clone()); 52 | fac.deploy(Default::default()).send().await?.await?; 53 | 54 | let store = Store::new(fac.last().call().await?, signer.clone()); 55 | store.kill().send().await?.await?; 56 | 57 | store 58 | .set(U256::from(1), U256::from(234)) 59 | .send() 60 | .await? 61 | .await?; 62 | Ok(()) 63 | } 64 | 65 | pub fn make_factory( 66 | name: &str, 67 | abi: Abi, 68 | client: Arc, 69 | ) -> Result> { 70 | let build_dir = Path::new(BUILD_DIR); 71 | let bin = fs::read_to_string(&build_dir.join(format!("{}.bin", name)))?; 72 | Ok(ContractFactory::new( 73 | abi, 74 | Bytes::from(hex::decode(bin)?), 75 | client, 76 | )) 77 | } 78 | -------------------------------------------------------------------------------- /test/contracts/Store.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.4.24; 3 | 4 | contract Store { 5 | uint256 public slot0; 6 | uint256 public slot1; 7 | uint256 public slot2; 8 | 9 | constructor() { 10 | slot0 = 2; 11 | slot1 = 3; 12 | slot2 = type(uint256).max; 13 | } 14 | 15 | function set(uint256 key, uint256 val) external { 16 | assembly { 17 | sstore(key, val) 18 | } 19 | } 20 | 21 | function kill() external { 22 | selfdestruct(payable(msg.sender)); 23 | } 24 | } 25 | 26 | contract Factory { 27 | Store public last; 28 | event Deploy(address dst); 29 | function deploy(bytes32 salt) external returns (Store) { 30 | last = new Store{salt: salt}(); 31 | return last; 32 | } 33 | } 34 | --------------------------------------------------------------------------------