/__w/smoldot/smoldot/repo/lib/src/database/full_sqlite/open.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | //! Database opening code. |
19 | | //! |
20 | | //! Contains everything related to the opening and initialization of the database. |
21 | | |
22 | | use super::{CorruptedError, InternalError, SqliteFullDatabase}; |
23 | | |
24 | | use std::path::Path; |
25 | | |
26 | | /// Opens the database using the given [`Config`]. |
27 | | /// |
28 | | /// Note that this doesn't return a [`SqliteFullDatabase`], but rather a [`DatabaseOpen`]. |
29 | 1.04k | pub fn open(config: Config) -> Result<DatabaseOpen, InternalError> { |
30 | 1.04k | let flags = rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | |
31 | 1.04k | rusqlite::OpenFlags::SQLITE_OPEN_CREATE | |
32 | 1.04k | // The "no mutex" option opens SQLite in "multi-threaded" mode, meaning that it can safely |
33 | 1.04k | // be used from multiple threads as long as we don't access the connection from multiple |
34 | 1.04k | // threads *at the same time*. Since we put the connection behind a `Mutex`, and that the |
35 | 1.04k | // underlying library implements `!Sync` for `Connection` as a safety measure anyway, it |
36 | 1.04k | // is safe to enable this option. |
37 | 1.04k | // See https://www.sqlite.org/threadsafe.html |
38 | 1.04k | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX; |
39 | | |
40 | 1.04k | let database = match config.ty { |
41 | 0 | ConfigTy::Disk { path, .. } => rusqlite::Connection::open_with_flags(path, flags), |
42 | 1.04k | ConfigTy::Memory => rusqlite::Connection::open_in_memory_with_flags(flags), |
43 | | } |
44 | 1.04k | .map_err(InternalError)?0 ; |
45 | | |
46 | | // The underlying SQLite wrapper maintains a cache of prepared statements. We set it to a |
47 | | // value superior to the number of different queries we make. |
48 | 1.04k | database.set_prepared_statement_cache_capacity(64); |
49 | 1.04k | |
50 | 1.04k | // Configure the database connection. |
51 | 1.04k | database |
52 | 1.04k | .execute_batch( |
53 | 1.04k | r#" |
54 | 1.04k | -- See https://sqlite.org/pragma.html and https://www.sqlite.org/wal.html |
55 | 1.04k | PRAGMA journal_mode = WAL; |
56 | 1.04k | PRAGMA synchronous = NORMAL; |
57 | 1.04k | PRAGMA locking_mode = EXCLUSIVE; |
58 | 1.04k | PRAGMA encoding = 'UTF-8'; |
59 | 1.04k | PRAGMA trusted_schema = false; |
60 | 1.04k | PRAGMA foreign_keys = ON; |
61 | 1.04k | "#, |
62 | 1.04k | ) |
63 | 1.04k | .map_err(InternalError)?0 ; |
64 | | |
65 | | // `PRAGMA` queries can't be parametrized, and thus we have to use `format!`. |
66 | 1.04k | database |
67 | 1.04k | .execute( |
68 | 1.04k | &format!( |
69 | 1.04k | "PRAGMA cache_size = {}", |
70 | 1.04k | 0i64.saturating_sub_unsigned( |
71 | 1.04k | u64::try_from((config.cache_size.saturating_sub(1) / 1024).saturating_add(1)) |
72 | 1.04k | .unwrap_or(u64::MAX), |
73 | 1.04k | ) |
74 | 1.04k | ), |
75 | 1.04k | (), |
76 | 1.04k | ) |
77 | 1.04k | .map_err(InternalError)?0 ; |
78 | | |
79 | | // `PRAGMA` queries can't be parametrized, and thus we have to use `format!`. |
80 | | if let ConfigTy::Disk { |
81 | 0 | memory_map_size, .. |
82 | 1.04k | } = config.ty |
83 | | { |
84 | 0 | database |
85 | 0 | .execute_batch(&format!("PRAGMA mmap_size = {}", memory_map_size)) |
86 | 0 | .map_err(InternalError)?; |
87 | 1.04k | } |
88 | | |
89 | | // Each SQLite database contains a "user version" whose value can be used by the API user |
90 | | // (that's us!) however they want. Its value defaults to 0 for new database. We use it to |
91 | | // store the schema version. |
92 | 1.04k | let user_version = database |
93 | 1.04k | .prepare_cached("PRAGMA user_version") |
94 | 1.04k | .map_err(InternalError)?0 |
95 | 1.04k | .query_row((), |row| row.get::<_, i64>(0)) _RNCNvNtNtNtCsN16ciHI6Qf_7smoldot8database11full_sqlite4open4open0B9_ Line | Count | Source | 95 | 1.02k | .query_row((), |row| row.get::<_, i64>(0)) |
_RNCNvNtNtNtCseuYC0Zibziv_7smoldot8database11full_sqlite4open4open0B9_ Line | Count | Source | 95 | 21 | .query_row((), |row| row.get::<_, i64>(0)) |
|
96 | 1.04k | .map_err(InternalError)?0 ; |
97 | | |
98 | | // Migrations. |
99 | 1.04k | if user_version <= 0 { |
100 | 1.04k | database |
101 | 1.04k | .execute_batch( |
102 | 1.04k | r#" |
103 | 1.04k | -- `auto_vacuum` can switched between `NONE` and non-`NONE` on newly-created database. |
104 | 1.04k | PRAGMA auto_vacuum = INCREMENTAL; |
105 | 1.04k | |
106 | 1.04k | /* |
107 | 1.04k | Contains all the "global" values in the database. |
108 | 1.04k | A value must be present either in `value_blob` or `value_number` depending on the type of data. |
109 | 1.04k | |
110 | 1.04k | Keys in that table: |
111 | 1.04k | |
112 | 1.04k | - `best` (blob): Hash of the best block. |
113 | 1.04k | |
114 | 1.04k | - `finalized` (number): Height of the finalized block, as a 64bits big endian number. |
115 | 1.04k | |
116 | 1.04k | */ |
117 | 1.04k | CREATE TABLE meta( |
118 | 1.04k | key STRING NOT NULL PRIMARY KEY, |
119 | 1.04k | value_blob BLOB, |
120 | 1.04k | value_number INTEGER, |
121 | 1.04k | -- Either `value_blob` or `value_number` must be NULL but not both. |
122 | 1.04k | CHECK((value_blob IS NULL OR value_number IS NULL) AND (value_blob IS NOT NULL OR value_number IS NOT NULL)) |
123 | 1.04k | ); |
124 | 1.04k | |
125 | 1.04k | /* |
126 | 1.04k | List of all trie nodes of all blocks whose trie is stored in the database. |
127 | 1.04k | */ |
128 | 1.04k | CREATE TABLE trie_node( |
129 | 1.04k | hash BLOB NOT NULL PRIMARY KEY, |
130 | 1.04k | partial_key BLOB NOT NULL -- Each byte is a nibble, in other words all bytes are <16 |
131 | 1.04k | ); |
132 | 1.04k | |
133 | 1.04k | /* |
134 | 1.04k | Storage associated to a trie node. |
135 | 1.04k | For each entry in `trie_node` there exists either 0 or 1 entry in `trie_node_storage` indicating |
136 | 1.04k | the storage value associated to this node. |
137 | 1.04k | */ |
138 | 1.04k | CREATE TABLE trie_node_storage( |
139 | 1.04k | node_hash BLOB NOT NULL PRIMARY KEY, |
140 | 1.04k | value BLOB, |
141 | 1.04k | trie_root_ref BLOB, |
142 | 1.04k | trie_entry_version INTEGER NOT NULL, |
143 | 1.04k | FOREIGN KEY (node_hash) REFERENCES trie_node(hash) ON UPDATE CASCADE ON DELETE CASCADE |
144 | 1.04k | CHECK((value IS NULL) != (trie_root_ref IS NULL)) |
145 | 1.04k | ); |
146 | 1.04k | CREATE INDEX trie_node_storage_by_trie_root_ref ON trie_node_storage(trie_root_ref); |
147 | 1.04k | |
148 | 1.04k | /* |
149 | 1.04k | Parent-child relationship between trie nodes. |
150 | 1.04k | */ |
151 | 1.04k | CREATE TABLE trie_node_child( |
152 | 1.04k | hash BLOB NOT NULL, |
153 | 1.04k | child_num BLOB NOT NULL, -- Always contains one single byte. We use `BLOB` instead of `INTEGER` because SQLite stupidly doesn't provide any way of converting between integers and blobs |
154 | 1.04k | child_hash BLOB NOT NULL, |
155 | 1.04k | PRIMARY KEY (hash, child_num), |
156 | 1.04k | FOREIGN KEY (hash) REFERENCES trie_node(hash) ON UPDATE CASCADE ON DELETE CASCADE |
157 | 1.04k | CHECK(LENGTH(child_num) == 1 AND HEX(child_num) < '10') |
158 | 1.04k | ); |
159 | 1.04k | CREATE INDEX trie_node_child_by_hash ON trie_node_child(hash); |
160 | 1.04k | CREATE INDEX trie_node_child_by_child_hash ON trie_node_child(child_hash); |
161 | 1.04k | |
162 | 1.04k | /* |
163 | 1.04k | List of all known blocks, indexed by their hash or number. |
164 | 1.04k | */ |
165 | 1.04k | CREATE TABLE blocks( |
166 | 1.04k | hash BLOB NOT NULL PRIMARY KEY, |
167 | 1.04k | parent_hash BLOB, -- NULL only for the genesis block |
168 | 1.04k | state_trie_root_hash BLOB, -- NULL if and only if the trie is empty or if the trie storage has been pruned from the database |
169 | 1.04k | number INTEGER NOT NULL, |
170 | 1.04k | header BLOB NOT NULL, |
171 | 1.04k | justification BLOB, |
172 | 1.04k | is_best_chain BOOLEAN NOT NULL, |
173 | 1.04k | UNIQUE(number, hash) |
174 | 1.04k | ); |
175 | 1.04k | CREATE INDEX blocks_by_number ON blocks(number); |
176 | 1.04k | CREATE INDEX blocks_by_parent ON blocks(parent_hash); |
177 | 1.04k | CREATE INDEX blocks_by_state_trie_root_hash ON blocks(state_trie_root_hash); |
178 | 1.04k | CREATE INDEX blocks_by_best ON blocks(number, is_best_chain); |
179 | 1.04k | |
180 | 1.04k | /* |
181 | 1.04k | Each block has a body made from 0+ extrinsics (in practice, there's always at least one extrinsic, |
182 | 1.04k | but the database supports 0). This table contains these extrinsics. |
183 | 1.04k | The `idx` field contains the index between `0` and `num_extrinsics - 1`. The values in `idx` must |
184 | 1.04k | be contiguous for each block. |
185 | 1.04k | */ |
186 | 1.04k | CREATE TABLE blocks_body( |
187 | 1.04k | hash BLOB NOT NULL, |
188 | 1.04k | idx INTEGER NOT NULL, |
189 | 1.04k | extrinsic BLOB NOT NULL, |
190 | 1.04k | UNIQUE(hash, idx), |
191 | 1.04k | CHECK(length(hash) == 32), |
192 | 1.04k | FOREIGN KEY (hash) REFERENCES blocks(hash) ON UPDATE CASCADE ON DELETE CASCADE |
193 | 1.04k | ); |
194 | 1.04k | CREATE INDEX blocks_body_by_block ON blocks_body(hash); |
195 | 1.04k | |
196 | 1.04k | PRAGMA user_version = 1; |
197 | 1.04k | |
198 | 1.04k | "#, |
199 | 1.04k | ) |
200 | 1.04k | .map_err(InternalError)?0 |
201 | 0 | } |
202 | | |
203 | 1.04k | let is_empty = database |
204 | 1.04k | .prepare_cached("SELECT COUNT(*) FROM meta WHERE key = ?") |
205 | 1.04k | .map_err(InternalError)?0 |
206 | 1.04k | .query_row(("best",), |row| row.get::<_, i64>(0)) _RNCNvNtNtNtCsN16ciHI6Qf_7smoldot8database11full_sqlite4open4opens_0B9_ Line | Count | Source | 206 | 1.02k | .query_row(("best",), |row| row.get::<_, i64>(0)) |
_RNCNvNtNtNtCseuYC0Zibziv_7smoldot8database11full_sqlite4open4opens_0B9_ Line | Count | Source | 206 | 21 | .query_row(("best",), |row| row.get::<_, i64>(0)) |
|
207 | 1.04k | .map_err(InternalError)?0 |
208 | | == 0; |
209 | | |
210 | 1.04k | Ok(if !is_empty { |
211 | 0 | DatabaseOpen::Open(SqliteFullDatabase { |
212 | 0 | database: parking_lot::Mutex::new(database), |
213 | 0 | block_number_bytes: config.block_number_bytes, // TODO: consider storing this value in the DB and check it when opening |
214 | 0 | }) |
215 | | } else { |
216 | 1.04k | DatabaseOpen::Empty(DatabaseEmpty { |
217 | 1.04k | database, |
218 | 1.04k | block_number_bytes: config.block_number_bytes, |
219 | 1.04k | }) |
220 | | }) |
221 | 1.04k | } _RNvNtNtNtCsN16ciHI6Qf_7smoldot8database11full_sqlite4open4open Line | Count | Source | 29 | 1.02k | pub fn open(config: Config) -> Result<DatabaseOpen, InternalError> { | 30 | 1.02k | let flags = rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | | 31 | 1.02k | rusqlite::OpenFlags::SQLITE_OPEN_CREATE | | 32 | 1.02k | // The "no mutex" option opens SQLite in "multi-threaded" mode, meaning that it can safely | 33 | 1.02k | // be used from multiple threads as long as we don't access the connection from multiple | 34 | 1.02k | // threads *at the same time*. Since we put the connection behind a `Mutex`, and that the | 35 | 1.02k | // underlying library implements `!Sync` for `Connection` as a safety measure anyway, it | 36 | 1.02k | // is safe to enable this option. | 37 | 1.02k | // See https://www.sqlite.org/threadsafe.html | 38 | 1.02k | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX; | 39 | | | 40 | 1.02k | let database = match config.ty { | 41 | 0 | ConfigTy::Disk { path, .. } => rusqlite::Connection::open_with_flags(path, flags), | 42 | 1.02k | ConfigTy::Memory => rusqlite::Connection::open_in_memory_with_flags(flags), | 43 | | } | 44 | 1.02k | .map_err(InternalError)?0 ; | 45 | | | 46 | | // The underlying SQLite wrapper maintains a cache of prepared statements. We set it to a | 47 | | // value superior to the number of different queries we make. | 48 | 1.02k | database.set_prepared_statement_cache_capacity(64); | 49 | 1.02k | | 50 | 1.02k | // Configure the database connection. | 51 | 1.02k | database | 52 | 1.02k | .execute_batch( | 53 | 1.02k | r#" | 54 | 1.02k | -- See https://sqlite.org/pragma.html and https://www.sqlite.org/wal.html | 55 | 1.02k | PRAGMA journal_mode = WAL; | 56 | 1.02k | PRAGMA synchronous = NORMAL; | 57 | 1.02k | PRAGMA locking_mode = EXCLUSIVE; | 58 | 1.02k | PRAGMA encoding = 'UTF-8'; | 59 | 1.02k | PRAGMA trusted_schema = false; | 60 | 1.02k | PRAGMA foreign_keys = ON; | 61 | 1.02k | "#, | 62 | 1.02k | ) | 63 | 1.02k | .map_err(InternalError)?0 ; | 64 | | | 65 | | // `PRAGMA` queries can't be parametrized, and thus we have to use `format!`. | 66 | 1.02k | database | 67 | 1.02k | .execute( | 68 | 1.02k | &format!( | 69 | 1.02k | "PRAGMA cache_size = {}", | 70 | 1.02k | 0i64.saturating_sub_unsigned( | 71 | 1.02k | u64::try_from((config.cache_size.saturating_sub(1) / 1024).saturating_add(1)) | 72 | 1.02k | .unwrap_or(u64::MAX), | 73 | 1.02k | ) | 74 | 1.02k | ), | 75 | 1.02k | (), | 76 | 1.02k | ) | 77 | 1.02k | .map_err(InternalError)?0 ; | 78 | | | 79 | | // `PRAGMA` queries can't be parametrized, and thus we have to use `format!`. | 80 | | if let ConfigTy::Disk { | 81 | 0 | memory_map_size, .. | 82 | 1.02k | } = config.ty | 83 | | { | 84 | 0 | database | 85 | 0 | .execute_batch(&format!("PRAGMA mmap_size = {}", memory_map_size)) | 86 | 0 | .map_err(InternalError)?; | 87 | 1.02k | } | 88 | | | 89 | | // Each SQLite database contains a "user version" whose value can be used by the API user | 90 | | // (that's us!) however they want. Its value defaults to 0 for new database. We use it to | 91 | | // store the schema version. | 92 | 1.02k | let user_version = database | 93 | 1.02k | .prepare_cached("PRAGMA user_version") | 94 | 1.02k | .map_err(InternalError)?0 | 95 | 1.02k | .query_row((), |row| row.get::<_, i64>(0)) | 96 | 1.02k | .map_err(InternalError)?0 ; | 97 | | | 98 | | // Migrations. | 99 | 1.02k | if user_version <= 0 { | 100 | 1.02k | database | 101 | 1.02k | .execute_batch( | 102 | 1.02k | r#" | 103 | 1.02k | -- `auto_vacuum` can switched between `NONE` and non-`NONE` on newly-created database. | 104 | 1.02k | PRAGMA auto_vacuum = INCREMENTAL; | 105 | 1.02k | | 106 | 1.02k | /* | 107 | 1.02k | Contains all the "global" values in the database. | 108 | 1.02k | A value must be present either in `value_blob` or `value_number` depending on the type of data. | 109 | 1.02k | | 110 | 1.02k | Keys in that table: | 111 | 1.02k | | 112 | 1.02k | - `best` (blob): Hash of the best block. | 113 | 1.02k | | 114 | 1.02k | - `finalized` (number): Height of the finalized block, as a 64bits big endian number. | 115 | 1.02k | | 116 | 1.02k | */ | 117 | 1.02k | CREATE TABLE meta( | 118 | 1.02k | key STRING NOT NULL PRIMARY KEY, | 119 | 1.02k | value_blob BLOB, | 120 | 1.02k | value_number INTEGER, | 121 | 1.02k | -- Either `value_blob` or `value_number` must be NULL but not both. | 122 | 1.02k | CHECK((value_blob IS NULL OR value_number IS NULL) AND (value_blob IS NOT NULL OR value_number IS NOT NULL)) | 123 | 1.02k | ); | 124 | 1.02k | | 125 | 1.02k | /* | 126 | 1.02k | List of all trie nodes of all blocks whose trie is stored in the database. | 127 | 1.02k | */ | 128 | 1.02k | CREATE TABLE trie_node( | 129 | 1.02k | hash BLOB NOT NULL PRIMARY KEY, | 130 | 1.02k | partial_key BLOB NOT NULL -- Each byte is a nibble, in other words all bytes are <16 | 131 | 1.02k | ); | 132 | 1.02k | | 133 | 1.02k | /* | 134 | 1.02k | Storage associated to a trie node. | 135 | 1.02k | For each entry in `trie_node` there exists either 0 or 1 entry in `trie_node_storage` indicating | 136 | 1.02k | the storage value associated to this node. | 137 | 1.02k | */ | 138 | 1.02k | CREATE TABLE trie_node_storage( | 139 | 1.02k | node_hash BLOB NOT NULL PRIMARY KEY, | 140 | 1.02k | value BLOB, | 141 | 1.02k | trie_root_ref BLOB, | 142 | 1.02k | trie_entry_version INTEGER NOT NULL, | 143 | 1.02k | FOREIGN KEY (node_hash) REFERENCES trie_node(hash) ON UPDATE CASCADE ON DELETE CASCADE | 144 | 1.02k | CHECK((value IS NULL) != (trie_root_ref IS NULL)) | 145 | 1.02k | ); | 146 | 1.02k | CREATE INDEX trie_node_storage_by_trie_root_ref ON trie_node_storage(trie_root_ref); | 147 | 1.02k | | 148 | 1.02k | /* | 149 | 1.02k | Parent-child relationship between trie nodes. | 150 | 1.02k | */ | 151 | 1.02k | CREATE TABLE trie_node_child( | 152 | 1.02k | hash BLOB NOT NULL, | 153 | 1.02k | child_num BLOB NOT NULL, -- Always contains one single byte. We use `BLOB` instead of `INTEGER` because SQLite stupidly doesn't provide any way of converting between integers and blobs | 154 | 1.02k | child_hash BLOB NOT NULL, | 155 | 1.02k | PRIMARY KEY (hash, child_num), | 156 | 1.02k | FOREIGN KEY (hash) REFERENCES trie_node(hash) ON UPDATE CASCADE ON DELETE CASCADE | 157 | 1.02k | CHECK(LENGTH(child_num) == 1 AND HEX(child_num) < '10') | 158 | 1.02k | ); | 159 | 1.02k | CREATE INDEX trie_node_child_by_hash ON trie_node_child(hash); | 160 | 1.02k | CREATE INDEX trie_node_child_by_child_hash ON trie_node_child(child_hash); | 161 | 1.02k | | 162 | 1.02k | /* | 163 | 1.02k | List of all known blocks, indexed by their hash or number. | 164 | 1.02k | */ | 165 | 1.02k | CREATE TABLE blocks( | 166 | 1.02k | hash BLOB NOT NULL PRIMARY KEY, | 167 | 1.02k | parent_hash BLOB, -- NULL only for the genesis block | 168 | 1.02k | state_trie_root_hash BLOB, -- NULL if and only if the trie is empty or if the trie storage has been pruned from the database | 169 | 1.02k | number INTEGER NOT NULL, | 170 | 1.02k | header BLOB NOT NULL, | 171 | 1.02k | justification BLOB, | 172 | 1.02k | is_best_chain BOOLEAN NOT NULL, | 173 | 1.02k | UNIQUE(number, hash) | 174 | 1.02k | ); | 175 | 1.02k | CREATE INDEX blocks_by_number ON blocks(number); | 176 | 1.02k | CREATE INDEX blocks_by_parent ON blocks(parent_hash); | 177 | 1.02k | CREATE INDEX blocks_by_state_trie_root_hash ON blocks(state_trie_root_hash); | 178 | 1.02k | CREATE INDEX blocks_by_best ON blocks(number, is_best_chain); | 179 | 1.02k | | 180 | 1.02k | /* | 181 | 1.02k | Each block has a body made from 0+ extrinsics (in practice, there's always at least one extrinsic, | 182 | 1.02k | but the database supports 0). This table contains these extrinsics. | 183 | 1.02k | The `idx` field contains the index between `0` and `num_extrinsics - 1`. The values in `idx` must | 184 | 1.02k | be contiguous for each block. | 185 | 1.02k | */ | 186 | 1.02k | CREATE TABLE blocks_body( | 187 | 1.02k | hash BLOB NOT NULL, | 188 | 1.02k | idx INTEGER NOT NULL, | 189 | 1.02k | extrinsic BLOB NOT NULL, | 190 | 1.02k | UNIQUE(hash, idx), | 191 | 1.02k | CHECK(length(hash) == 32), | 192 | 1.02k | FOREIGN KEY (hash) REFERENCES blocks(hash) ON UPDATE CASCADE ON DELETE CASCADE | 193 | 1.02k | ); | 194 | 1.02k | CREATE INDEX blocks_body_by_block ON blocks_body(hash); | 195 | 1.02k | | 196 | 1.02k | PRAGMA user_version = 1; | 197 | 1.02k | | 198 | 1.02k | "#, | 199 | 1.02k | ) | 200 | 1.02k | .map_err(InternalError)?0 | 201 | 0 | } | 202 | | | 203 | 1.02k | let is_empty = database | 204 | 1.02k | .prepare_cached("SELECT COUNT(*) FROM meta WHERE key = ?") | 205 | 1.02k | .map_err(InternalError)?0 | 206 | 1.02k | .query_row(("best",), |row| row.get::<_, i64>(0)) | 207 | 1.02k | .map_err(InternalError)?0 | 208 | | == 0; | 209 | | | 210 | 1.02k | Ok(if !is_empty { | 211 | 0 | DatabaseOpen::Open(SqliteFullDatabase { | 212 | 0 | database: parking_lot::Mutex::new(database), | 213 | 0 | block_number_bytes: config.block_number_bytes, // TODO: consider storing this value in the DB and check it when opening | 214 | 0 | }) | 215 | | } else { | 216 | 1.02k | DatabaseOpen::Empty(DatabaseEmpty { | 217 | 1.02k | database, | 218 | 1.02k | block_number_bytes: config.block_number_bytes, | 219 | 1.02k | }) | 220 | | }) | 221 | 1.02k | } |
_RNvNtNtNtCseuYC0Zibziv_7smoldot8database11full_sqlite4open4open Line | Count | Source | 29 | 21 | pub fn open(config: Config) -> Result<DatabaseOpen, InternalError> { | 30 | 21 | let flags = rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | | 31 | 21 | rusqlite::OpenFlags::SQLITE_OPEN_CREATE | | 32 | 21 | // The "no mutex" option opens SQLite in "multi-threaded" mode, meaning that it can safely | 33 | 21 | // be used from multiple threads as long as we don't access the connection from multiple | 34 | 21 | // threads *at the same time*. Since we put the connection behind a `Mutex`, and that the | 35 | 21 | // underlying library implements `!Sync` for `Connection` as a safety measure anyway, it | 36 | 21 | // is safe to enable this option. | 37 | 21 | // See https://www.sqlite.org/threadsafe.html | 38 | 21 | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX; | 39 | | | 40 | 21 | let database = match config.ty { | 41 | 0 | ConfigTy::Disk { path, .. } => rusqlite::Connection::open_with_flags(path, flags), | 42 | 21 | ConfigTy::Memory => rusqlite::Connection::open_in_memory_with_flags(flags), | 43 | | } | 44 | 21 | .map_err(InternalError)?0 ; | 45 | | | 46 | | // The underlying SQLite wrapper maintains a cache of prepared statements. We set it to a | 47 | | // value superior to the number of different queries we make. | 48 | 21 | database.set_prepared_statement_cache_capacity(64); | 49 | 21 | | 50 | 21 | // Configure the database connection. | 51 | 21 | database | 52 | 21 | .execute_batch( | 53 | 21 | r#" | 54 | 21 | -- See https://sqlite.org/pragma.html and https://www.sqlite.org/wal.html | 55 | 21 | PRAGMA journal_mode = WAL; | 56 | 21 | PRAGMA synchronous = NORMAL; | 57 | 21 | PRAGMA locking_mode = EXCLUSIVE; | 58 | 21 | PRAGMA encoding = 'UTF-8'; | 59 | 21 | PRAGMA trusted_schema = false; | 60 | 21 | PRAGMA foreign_keys = ON; | 61 | 21 | "#, | 62 | 21 | ) | 63 | 21 | .map_err(InternalError)?0 ; | 64 | | | 65 | | // `PRAGMA` queries can't be parametrized, and thus we have to use `format!`. | 66 | 21 | database | 67 | 21 | .execute( | 68 | 21 | &format!( | 69 | 21 | "PRAGMA cache_size = {}", | 70 | 21 | 0i64.saturating_sub_unsigned( | 71 | 21 | u64::try_from((config.cache_size.saturating_sub(1) / 1024).saturating_add(1)) | 72 | 21 | .unwrap_or(u64::MAX), | 73 | 21 | ) | 74 | 21 | ), | 75 | 21 | (), | 76 | 21 | ) | 77 | 21 | .map_err(InternalError)?0 ; | 78 | | | 79 | | // `PRAGMA` queries can't be parametrized, and thus we have to use `format!`. | 80 | | if let ConfigTy::Disk { | 81 | 0 | memory_map_size, .. | 82 | 21 | } = config.ty | 83 | | { | 84 | 0 | database | 85 | 0 | .execute_batch(&format!("PRAGMA mmap_size = {}", memory_map_size)) | 86 | 0 | .map_err(InternalError)?; | 87 | 21 | } | 88 | | | 89 | | // Each SQLite database contains a "user version" whose value can be used by the API user | 90 | | // (that's us!) however they want. Its value defaults to 0 for new database. We use it to | 91 | | // store the schema version. | 92 | 21 | let user_version = database | 93 | 21 | .prepare_cached("PRAGMA user_version") | 94 | 21 | .map_err(InternalError)?0 | 95 | 21 | .query_row((), |row| row.get::<_, i64>(0)) | 96 | 21 | .map_err(InternalError)?0 ; | 97 | | | 98 | | // Migrations. | 99 | 21 | if user_version <= 0 { | 100 | 21 | database | 101 | 21 | .execute_batch( | 102 | 21 | r#" | 103 | 21 | -- `auto_vacuum` can switched between `NONE` and non-`NONE` on newly-created database. | 104 | 21 | PRAGMA auto_vacuum = INCREMENTAL; | 105 | 21 | | 106 | 21 | /* | 107 | 21 | Contains all the "global" values in the database. | 108 | 21 | A value must be present either in `value_blob` or `value_number` depending on the type of data. | 109 | 21 | | 110 | 21 | Keys in that table: | 111 | 21 | | 112 | 21 | - `best` (blob): Hash of the best block. | 113 | 21 | | 114 | 21 | - `finalized` (number): Height of the finalized block, as a 64bits big endian number. | 115 | 21 | | 116 | 21 | */ | 117 | 21 | CREATE TABLE meta( | 118 | 21 | key STRING NOT NULL PRIMARY KEY, | 119 | 21 | value_blob BLOB, | 120 | 21 | value_number INTEGER, | 121 | 21 | -- Either `value_blob` or `value_number` must be NULL but not both. | 122 | 21 | CHECK((value_blob IS NULL OR value_number IS NULL) AND (value_blob IS NOT NULL OR value_number IS NOT NULL)) | 123 | 21 | ); | 124 | 21 | | 125 | 21 | /* | 126 | 21 | List of all trie nodes of all blocks whose trie is stored in the database. | 127 | 21 | */ | 128 | 21 | CREATE TABLE trie_node( | 129 | 21 | hash BLOB NOT NULL PRIMARY KEY, | 130 | 21 | partial_key BLOB NOT NULL -- Each byte is a nibble, in other words all bytes are <16 | 131 | 21 | ); | 132 | 21 | | 133 | 21 | /* | 134 | 21 | Storage associated to a trie node. | 135 | 21 | For each entry in `trie_node` there exists either 0 or 1 entry in `trie_node_storage` indicating | 136 | 21 | the storage value associated to this node. | 137 | 21 | */ | 138 | 21 | CREATE TABLE trie_node_storage( | 139 | 21 | node_hash BLOB NOT NULL PRIMARY KEY, | 140 | 21 | value BLOB, | 141 | 21 | trie_root_ref BLOB, | 142 | 21 | trie_entry_version INTEGER NOT NULL, | 143 | 21 | FOREIGN KEY (node_hash) REFERENCES trie_node(hash) ON UPDATE CASCADE ON DELETE CASCADE | 144 | 21 | CHECK((value IS NULL) != (trie_root_ref IS NULL)) | 145 | 21 | ); | 146 | 21 | CREATE INDEX trie_node_storage_by_trie_root_ref ON trie_node_storage(trie_root_ref); | 147 | 21 | | 148 | 21 | /* | 149 | 21 | Parent-child relationship between trie nodes. | 150 | 21 | */ | 151 | 21 | CREATE TABLE trie_node_child( | 152 | 21 | hash BLOB NOT NULL, | 153 | 21 | child_num BLOB NOT NULL, -- Always contains one single byte. We use `BLOB` instead of `INTEGER` because SQLite stupidly doesn't provide any way of converting between integers and blobs | 154 | 21 | child_hash BLOB NOT NULL, | 155 | 21 | PRIMARY KEY (hash, child_num), | 156 | 21 | FOREIGN KEY (hash) REFERENCES trie_node(hash) ON UPDATE CASCADE ON DELETE CASCADE | 157 | 21 | CHECK(LENGTH(child_num) == 1 AND HEX(child_num) < '10') | 158 | 21 | ); | 159 | 21 | CREATE INDEX trie_node_child_by_hash ON trie_node_child(hash); | 160 | 21 | CREATE INDEX trie_node_child_by_child_hash ON trie_node_child(child_hash); | 161 | 21 | | 162 | 21 | /* | 163 | 21 | List of all known blocks, indexed by their hash or number. | 164 | 21 | */ | 165 | 21 | CREATE TABLE blocks( | 166 | 21 | hash BLOB NOT NULL PRIMARY KEY, | 167 | 21 | parent_hash BLOB, -- NULL only for the genesis block | 168 | 21 | state_trie_root_hash BLOB, -- NULL if and only if the trie is empty or if the trie storage has been pruned from the database | 169 | 21 | number INTEGER NOT NULL, | 170 | 21 | header BLOB NOT NULL, | 171 | 21 | justification BLOB, | 172 | 21 | is_best_chain BOOLEAN NOT NULL, | 173 | 21 | UNIQUE(number, hash) | 174 | 21 | ); | 175 | 21 | CREATE INDEX blocks_by_number ON blocks(number); | 176 | 21 | CREATE INDEX blocks_by_parent ON blocks(parent_hash); | 177 | 21 | CREATE INDEX blocks_by_state_trie_root_hash ON blocks(state_trie_root_hash); | 178 | 21 | CREATE INDEX blocks_by_best ON blocks(number, is_best_chain); | 179 | 21 | | 180 | 21 | /* | 181 | 21 | Each block has a body made from 0+ extrinsics (in practice, there's always at least one extrinsic, | 182 | 21 | but the database supports 0). This table contains these extrinsics. | 183 | 21 | The `idx` field contains the index between `0` and `num_extrinsics - 1`. The values in `idx` must | 184 | 21 | be contiguous for each block. | 185 | 21 | */ | 186 | 21 | CREATE TABLE blocks_body( | 187 | 21 | hash BLOB NOT NULL, | 188 | 21 | idx INTEGER NOT NULL, | 189 | 21 | extrinsic BLOB NOT NULL, | 190 | 21 | UNIQUE(hash, idx), | 191 | 21 | CHECK(length(hash) == 32), | 192 | 21 | FOREIGN KEY (hash) REFERENCES blocks(hash) ON UPDATE CASCADE ON DELETE CASCADE | 193 | 21 | ); | 194 | 21 | CREATE INDEX blocks_body_by_block ON blocks_body(hash); | 195 | 21 | | 196 | 21 | PRAGMA user_version = 1; | 197 | 21 | | 198 | 21 | "#, | 199 | 21 | ) | 200 | 21 | .map_err(InternalError)?0 | 201 | 0 | } | 202 | | | 203 | 21 | let is_empty = database | 204 | 21 | .prepare_cached("SELECT COUNT(*) FROM meta WHERE key = ?") | 205 | 21 | .map_err(InternalError)?0 | 206 | 21 | .query_row(("best",), |row| row.get::<_, i64>(0)) | 207 | 21 | .map_err(InternalError)?0 | 208 | | == 0; | 209 | | | 210 | 21 | Ok(if !is_empty { | 211 | 0 | DatabaseOpen::Open(SqliteFullDatabase { | 212 | 0 | database: parking_lot::Mutex::new(database), | 213 | 0 | block_number_bytes: config.block_number_bytes, // TODO: consider storing this value in the DB and check it when opening | 214 | 0 | }) | 215 | | } else { | 216 | 21 | DatabaseOpen::Empty(DatabaseEmpty { | 217 | 21 | database, | 218 | 21 | block_number_bytes: config.block_number_bytes, | 219 | 21 | }) | 220 | | }) | 221 | 21 | } |
|
222 | | |
223 | | /// Configuration for the database. |
224 | | #[derive(Debug)] |
225 | | pub struct Config<'a> { |
226 | | /// Type of database. |
227 | | pub ty: ConfigTy<'a>, |
228 | | |
229 | | /// Number of bytes used to encode the block number. |
230 | | pub block_number_bytes: usize, |
231 | | |
232 | | /// Maximum allowed size, in bytes, of the SQLite cache. |
233 | | pub cache_size: usize, |
234 | | } |
235 | | |
236 | | /// Type of database. |
237 | | #[derive(Debug)] |
238 | | pub enum ConfigTy<'a> { |
239 | | /// Store the database on disk. |
240 | | Disk { |
241 | | /// Path to the directory containing the database. |
242 | | path: &'a Path, |
243 | | /// Maximum allowed amount of memory, in bytes, that SQLite will reserve to memory-map |
244 | | /// files. |
245 | | memory_map_size: usize, |
246 | | }, |
247 | | /// Store the database in memory. The database is discarded on destruction. |
248 | | Memory, |
249 | | } |
250 | | |
251 | | /// Either existing database or database prototype. |
252 | | pub enum DatabaseOpen { |
253 | | /// A database already existed and has now been opened. |
254 | | Open(SqliteFullDatabase), |
255 | | |
256 | | /// Either a database has just been created, or there existed a database but it is empty. |
257 | | /// |
258 | | /// > **Note**: The situation where a database existed but is empty can happen if you have |
259 | | /// > previously called [`open`] then dropped the [`DatabaseOpen`] object without |
260 | | /// > filling the newly-created database with data. |
261 | | Empty(DatabaseEmpty), |
262 | | } |
263 | | |
264 | | /// An open database. Holds file descriptors. |
265 | | pub struct DatabaseEmpty { |
266 | | /// See the similar field in [`SqliteFullDatabase`]. |
267 | | database: rusqlite::Connection, |
268 | | |
269 | | /// See the similar field in [`SqliteFullDatabase`]. |
270 | | block_number_bytes: usize, |
271 | | } |
272 | | |
273 | | impl DatabaseEmpty { |
274 | | /// Inserts the given finalized block in the database prototype in order to turn it into |
275 | | /// an actual database. |
276 | | // TODO: can a database not be empty? |
277 | 1.04k | pub fn initialize<'a>( |
278 | 1.04k | self, |
279 | 1.04k | finalized_block_header: &[u8], |
280 | 1.04k | finalized_block_body: impl ExactSizeIterator<Item = &'a [u8]>, |
281 | 1.04k | finalized_block_justification: Option<Vec<u8>>, |
282 | 1.04k | ) -> Result<SqliteFullDatabase, CorruptedError> { |
283 | 1.04k | let database = SqliteFullDatabase { |
284 | 1.04k | database: parking_lot::Mutex::new(self.database), |
285 | 1.04k | block_number_bytes: self.block_number_bytes, |
286 | 1.04k | }; |
287 | 1.04k | |
288 | 1.04k | database.reset( |
289 | 1.04k | finalized_block_header, |
290 | 1.04k | finalized_block_body, |
291 | 1.04k | finalized_block_justification, |
292 | 1.04k | )?0 ; |
293 | | |
294 | 1.04k | Ok(database) |
295 | 1.04k | } _RINvMNtNtNtCsN16ciHI6Qf_7smoldot8database11full_sqlite4openNtB3_13DatabaseEmpty10initializeINtNtNtNtCsaYZPK01V26L_4core4iter7sources5empty5EmptyRShEEB9_ Line | Count | Source | 277 | 1.02k | pub fn initialize<'a>( | 278 | 1.02k | self, | 279 | 1.02k | finalized_block_header: &[u8], | 280 | 1.02k | finalized_block_body: impl ExactSizeIterator<Item = &'a [u8]>, | 281 | 1.02k | finalized_block_justification: Option<Vec<u8>>, | 282 | 1.02k | ) -> Result<SqliteFullDatabase, CorruptedError> { | 283 | 1.02k | let database = SqliteFullDatabase { | 284 | 1.02k | database: parking_lot::Mutex::new(self.database), | 285 | 1.02k | block_number_bytes: self.block_number_bytes, | 286 | 1.02k | }; | 287 | 1.02k | | 288 | 1.02k | database.reset( | 289 | 1.02k | finalized_block_header, | 290 | 1.02k | finalized_block_body, | 291 | 1.02k | finalized_block_justification, | 292 | 1.02k | )?0 ; | 293 | | | 294 | 1.02k | Ok(database) | 295 | 1.02k | } |
Unexecuted instantiation: _RINvMNtNtNtCseuYC0Zibziv_7smoldot8database11full_sqlite4openNtB3_13DatabaseEmpty10initializepEB9_ _RINvMNtNtNtCseuYC0Zibziv_7smoldot8database11full_sqlite4openNtB3_13DatabaseEmpty10initializeINtNtNtNtCsaYZPK01V26L_4core4iter7sources5empty5EmptyRShEECsiLzmwikkc22_14json_rpc_basic Line | Count | Source | 277 | 2 | pub fn initialize<'a>( | 278 | 2 | self, | 279 | 2 | finalized_block_header: &[u8], | 280 | 2 | finalized_block_body: impl ExactSizeIterator<Item = &'a [u8]>, | 281 | 2 | finalized_block_justification: Option<Vec<u8>>, | 282 | 2 | ) -> Result<SqliteFullDatabase, CorruptedError> { | 283 | 2 | let database = SqliteFullDatabase { | 284 | 2 | database: parking_lot::Mutex::new(self.database), | 285 | 2 | block_number_bytes: self.block_number_bytes, | 286 | 2 | }; | 287 | 2 | | 288 | 2 | database.reset( | 289 | 2 | finalized_block_header, | 290 | 2 | finalized_block_body, | 291 | 2 | finalized_block_justification, | 292 | 2 | )?0 ; | 293 | | | 294 | 2 | Ok(database) | 295 | 2 | } |
Unexecuted instantiation: _RINvMNtNtNtCseuYC0Zibziv_7smoldot8database11full_sqlite4openNtB3_13DatabaseEmpty10initializeINtNtNtNtCsaYZPK01V26L_4core4iter7sources5empty5EmptyRShEECscDgN54JpMGG_6author _RINvMNtNtNtCseuYC0Zibziv_7smoldot8database11full_sqlite4openNtB3_13DatabaseEmpty10initializeINtNtNtNtCsaYZPK01V26L_4core4iter7sources5empty5EmptyRShEECsibGXYHQB8Ea_25json_rpc_general_requests Line | Count | Source | 277 | 19 | pub fn initialize<'a>( | 278 | 19 | self, | 279 | 19 | finalized_block_header: &[u8], | 280 | 19 | finalized_block_body: impl ExactSizeIterator<Item = &'a [u8]>, | 281 | 19 | finalized_block_justification: Option<Vec<u8>>, | 282 | 19 | ) -> Result<SqliteFullDatabase, CorruptedError> { | 283 | 19 | let database = SqliteFullDatabase { | 284 | 19 | database: parking_lot::Mutex::new(self.database), | 285 | 19 | block_number_bytes: self.block_number_bytes, | 286 | 19 | }; | 287 | 19 | | 288 | 19 | database.reset( | 289 | 19 | finalized_block_header, | 290 | 19 | finalized_block_body, | 291 | 19 | finalized_block_justification, | 292 | 19 | )?0 ; | 293 | | | 294 | 19 | Ok(database) | 295 | 19 | } |
|
296 | | } |