Skip to content

Commit 812379b

Browse files
committed
fix(db): use consensus encoding as BLOB in sqlite schema
1 parent e895611 commit 812379b

5 files changed

Lines changed: 41 additions & 137 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/target
22
/data
3+
/.light_client_data
34
/kyoto
45
.DS_Store
56
.idea/

justfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ signet:
2727
testnet:
2828
cargo run --example testnet --release
2929

30+
delete-data:
31+
rm -rf .light_client_data
32+
3033
all:
3134
cargo fmt
3235
cargo clippy --all-targets

src/db/error.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@ impl From<bitcoin::consensus::encode::Error> for SqlPeerStoreError {
109109
#[cfg(feature = "database")]
110110
#[derive(Debug)]
111111
pub enum SqlHeaderStoreError {
112-
/// A consensus critical data structure is malformed.
112+
/// The headers do not link together.
113113
Corruption,
114-
/// A string could not be deserialized into a known datatype.
115-
StringConversion,
114+
/// Consensus deserialization failed.
115+
Deserialize(bitcoin::consensus::encode::Error),
116116
/// An error occured performing a SQL operation.
117117
SQL(rusqlite::Error),
118118
}
@@ -121,15 +121,12 @@ pub enum SqlHeaderStoreError {
121121
impl core::fmt::Display for SqlHeaderStoreError {
122122
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123123
match self {
124-
SqlHeaderStoreError::StringConversion => {
125-
write!(
126-
f,
127-
"a string could not be deserialized into a known datatype."
128-
)
129-
}
130124
SqlHeaderStoreError::SQL(e) => {
131125
write!(f, "reading or writing from the database failed: {e}")
132126
}
127+
SqlHeaderStoreError::Deserialize(e) => {
128+
write!(f, "consensus decoding failed {e}")
129+
}
133130
SqlHeaderStoreError::Corruption => {
134131
write!(f, "a consensus critical data structure is malformed.")
135132
}
@@ -142,8 +139,8 @@ impl std::error::Error for SqlHeaderStoreError {
142139
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
143140
match self {
144141
SqlHeaderStoreError::Corruption => None,
145-
SqlHeaderStoreError::StringConversion => None,
146142
SqlHeaderStoreError::SQL(error) => Some(error),
143+
SqlHeaderStoreError::Deserialize(error) => Some(error),
147144
}
148145
}
149146
}
@@ -154,3 +151,10 @@ impl From<rusqlite::Error> for SqlHeaderStoreError {
154151
Self::SQL(value)
155152
}
156153
}
154+
155+
#[cfg(feature = "database")]
156+
impl From<bitcoin::consensus::encode::Error> for SqlHeaderStoreError {
157+
fn from(value: bitcoin::consensus::encode::Error) -> Self {
158+
Self::Deserialize(value)
159+
}
160+
}

src/db/sqlite/headers.rs

Lines changed: 22 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ use std::collections::BTreeMap;
22
use std::fs;
33
use std::ops::{Bound, RangeBounds};
44
use std::path::PathBuf;
5-
use std::str::FromStr;
65
use std::sync::Arc;
76

8-
use bitcoin::block::{Header, Version};
9-
use bitcoin::{BlockHash, CompactTarget, Network, TxMerkleNode};
7+
use bitcoin::block::Header;
8+
use bitcoin::{consensus, BlockHash, Network};
109
use rusqlite::{params, params_from_iter, Connection, Result};
1110
use tokio::sync::Mutex;
1211

@@ -28,13 +27,8 @@ const SCHEMA_VERSION: u8 = 0;
2827
// Always execute this query and adjust the schema with migrations
2928
const INITIAL_HEADER_SCHEMA: &str = "CREATE TABLE IF NOT EXISTS headers (
3029
height INTEGER PRIMARY KEY,
31-
block_hash TEXT NOT NULL,
32-
version INTEGER NOT NULL,
33-
prev_hash TEXT NOT NULL,
34-
merkle_root TEXT NOT NULL,
35-
time INTEGER NOT NULL,
36-
bits INTEGER NOT NULL,
37-
nonce INTEGER NOT NULL
30+
block_hash BLOB NOT NULL,
31+
header BLOB NOT NULL
3832
) STRICT";
3933

4034
const LOAD_QUERY_SELECT_PREFIX: &str = "SELECT * FROM headers ";
@@ -127,30 +121,8 @@ impl SqliteHeaderDb {
127121
let mut rows = query.query(params_from_iter(param_list.iter()))?;
128122
while let Some(row) = rows.next()? {
129123
let height: u32 = row.get(0)?;
130-
let hash: String = row.get(1)?;
131-
let version: i32 = row.get(2)?;
132-
let prev_hash: String = row.get(3)?;
133-
let merkle_root: String = row.get(4)?;
134-
let time: u32 = row.get(5)?;
135-
let bits: u32 = row.get(6)?;
136-
let nonce: u32 = row.get(7)?;
137-
138-
let next_header = Header {
139-
version: Version::from_consensus(version),
140-
prev_blockhash: BlockHash::from_str(&prev_hash)
141-
.map_err(|_| SqlHeaderStoreError::StringConversion)?,
142-
merkle_root: TxMerkleNode::from_str(&merkle_root)
143-
.map_err(|_| SqlHeaderStoreError::StringConversion)?,
144-
time,
145-
bits: CompactTarget::from_consensus(bits),
146-
nonce,
147-
};
148-
if BlockHash::from_str(&hash)
149-
.map_err(|_| SqlHeaderStoreError::StringConversion)?
150-
.ne(&next_header.block_hash())
151-
{
152-
return Err(SqlHeaderStoreError::Corruption);
153-
}
124+
let header: [u8; 80] = row.get(2)?;
125+
let next_header: Header = consensus::deserialize(&header)?;
154126
if let Some(header) = headers.values().last() {
155127
if header.block_hash().ne(&next_header.prev_blockhash) {
156128
return Err(SqlHeaderStoreError::Corruption);
@@ -167,55 +139,21 @@ impl SqliteHeaderDb {
167139
match changes {
168140
BlockHeaderChanges::Connected(indexed_header) => {
169141
let header = indexed_header.header;
170-
let hash: String = header.block_hash().to_string();
171-
let version: i32 = header.version.to_consensus();
172-
let prev_hash: String = header.prev_blockhash.as_raw_hash().to_string();
173-
let merkle_root: String = header.merkle_root.to_string();
174-
let time: u32 = header.time;
175-
let bits: u32 = header.bits.to_consensus();
176-
let nonce: u32 = header.nonce;
177-
let stmt = "INSERT OR REPLACE INTO headers (height, block_hash, version, prev_hash, merkle_root, time, bits, nonce) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)";
178-
tx.execute(
179-
stmt,
180-
params![
181-
indexed_header.height,
182-
hash,
183-
version,
184-
prev_hash,
185-
merkle_root,
186-
time,
187-
bits,
188-
nonce
189-
],
190-
)?;
142+
let hash: Vec<u8> = consensus::serialize(&header.block_hash());
143+
let header: Vec<u8> = consensus::serialize(&indexed_header.header);
144+
let stmt = "INSERT OR REPLACE INTO headers (height, block_hash, header) VALUES (?1, ?2, ?3)";
145+
tx.execute(stmt, params![indexed_header.height, hash, header])?;
191146
}
192147
BlockHeaderChanges::Reorganized {
193148
accepted,
194149
reorganized: _,
195150
} => {
196151
for indexed_header in accepted {
197152
let header = indexed_header.header;
198-
let hash: String = header.block_hash().to_string();
199-
let version: i32 = header.version.to_consensus();
200-
let prev_hash: String = header.prev_blockhash.as_raw_hash().to_string();
201-
let merkle_root: String = header.merkle_root.to_string();
202-
let time: u32 = header.time;
203-
let bits: u32 = header.bits.to_consensus();
204-
let nonce: u32 = header.nonce;
205-
let stmt = "INSERT OR REPLACE INTO headers (height, block_hash, version, prev_hash, merkle_root, time, bits, nonce) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)";
206-
tx.execute(
207-
stmt,
208-
params![
209-
indexed_header.height,
210-
hash,
211-
version,
212-
prev_hash,
213-
merkle_root,
214-
time,
215-
bits,
216-
nonce
217-
],
218-
)?;
153+
let hash: Vec<u8> = consensus::serialize(&header.block_hash());
154+
let header: Vec<u8> = consensus::serialize(&indexed_header.header);
155+
let stmt = "INSERT OR REPLACE INTO headers (height, block_hash, header) VALUES (?1, ?2, ?3)";
156+
tx.execute(stmt, params![indexed_header.height, hash, header])?;
219157
}
220158
}
221159
}
@@ -230,20 +168,18 @@ impl SqliteHeaderDb {
230168
) -> Result<Option<u32>, SqlHeaderStoreError> {
231169
let write_lock = self.conn.lock().await;
232170
let stmt = "SELECT height FROM headers WHERE block_hash = ?1";
233-
let row: Option<u32> =
234-
write_lock.query_row(stmt, params![block_hash.to_string()], |row| row.get(0))?;
171+
let hash: Vec<u8> = consensus::serialize(&block_hash);
172+
let row: Option<u32> = write_lock.query_row(stmt, params![hash], |row| row.get(0))?;
235173
Ok(row)
236174
}
237175

238176
async fn hash_at(&mut self, height: u32) -> Result<Option<BlockHash>, SqlHeaderStoreError> {
239177
let write_lock = self.conn.lock().await;
240178
let stmt = "SELECT block_hash FROM headers WHERE height = ?1";
241-
let row: Option<String> = write_lock.query_row(stmt, params![height], |row| row.get(0))?;
179+
let row: Option<[u8; 32]> =
180+
write_lock.query_row(stmt, params![height], |row| row.get(0))?;
242181
match row {
243-
Some(row) => match BlockHash::from_str(&row) {
244-
Ok(hash) => Ok(Some(hash)),
245-
Err(_) => Err(SqlHeaderStoreError::StringConversion),
246-
},
182+
Some(hash) => Ok(Some(consensus::deserialize(&hash)?)),
247183
None => Ok(None),
248184
}
249185
}
@@ -252,49 +188,9 @@ impl SqliteHeaderDb {
252188
let write_lock = self.conn.lock().await;
253189
let stmt = "SELECT * FROM headers WHERE height = ?1";
254190
let query = write_lock.query_row(stmt, params![height], |row| {
255-
let hash: String = row.get(1)?;
256-
let version: i32 = row.get(2)?;
257-
let prev_hash: String = row.get(3)?;
258-
let merkle_root: String = row.get(4)?;
259-
let time: u32 = row.get(5)?;
260-
let bits: u32 = row.get(6)?;
261-
let nonce: u32 = row.get(7)?;
262-
263-
let header = Header {
264-
version: Version::from_consensus(version),
265-
prev_blockhash: BlockHash::from_str(&prev_hash).map_err(|e| {
266-
rusqlite::Error::FromSqlConversionFailure(
267-
0,
268-
rusqlite::types::Type::Blob,
269-
Box::new(e),
270-
)
271-
})?,
272-
merkle_root: TxMerkleNode::from_str(&merkle_root).map_err(|e| {
273-
rusqlite::Error::FromSqlConversionFailure(
274-
0,
275-
rusqlite::types::Type::Blob,
276-
Box::new(e),
277-
)
278-
})?,
279-
time,
280-
bits: CompactTarget::from_consensus(bits),
281-
nonce,
282-
};
283-
284-
if BlockHash::from_str(&hash)
285-
.map_err(|e| {
286-
rusqlite::Error::FromSqlConversionFailure(
287-
0,
288-
rusqlite::types::Type::Blob,
289-
Box::new(e),
290-
)
291-
})?
292-
.ne(&header.block_hash())
293-
{
294-
return Err(rusqlite::Error::InvalidQuery);
295-
}
296-
297-
Ok(header)
191+
let header_slice: [u8; 80] = row.get(2)?;
192+
consensus::deserialize(&header_slice)
193+
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))
298194
});
299195
match query {
300196
Ok(header) => Ok(Some(header)),

src/db/sqlite/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ pub mod headers;
44
pub mod peers;
55

66
pub(crate) const DEFAULT_CWD: &str = ".";
7-
pub(crate) const DATA_DIR: &str = "data";
7+
pub(crate) const DATA_DIR: &str = ".light_client_data";

0 commit comments

Comments
 (0)