Coverage Report

Created: 2024-05-16 12:16

/__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
}