Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 25154f9

Browse files
svyatonikgavofyork
authored andcommitted
Replace old headers with CHT in light clients (#512)
* storage proofs * CHT
1 parent 9647e26 commit 25154f9

File tree

19 files changed

+809
-81
lines changed

19 files changed

+809
-81
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

substrate/client/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ error-chain = "0.12"
88
fnv = "1.0"
99
log = "0.3"
1010
parking_lot = "0.4"
11-
triehash = "0.1"
1211
hex-literal = "0.1"
1312
futures = "0.1.17"
1413
ed25519 = { path = "../ed25519" }
@@ -27,6 +26,7 @@ substrate-telemetry = { path = "../telemetry" }
2726
hashdb = { git = "https://github.com/paritytech/parity-common" }
2827
patricia-trie = { git = "https://github.com/paritytech/parity-common" }
2928
rlp = { git = "https://github.com/paritytech/parity-common" }
29+
triehash = { git = "https://github.com/paritytech/parity-common" }
3030

3131
[dev-dependencies]
3232
substrate-test-client = { path = "../test-client" }

substrate/client/db/src/lib.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ use runtime_primitives::BuildStorage;
6161
use state_machine::backend::Backend as StateBackend;
6262
use executor::RuntimeInfo;
6363
use state_machine::{CodeExecutor, DBValue, ExecutionStrategy};
64-
use utils::{Meta, db_err, meta_keys, number_to_db_key, open_database, read_db, read_id, read_meta};
64+
use utils::{Meta, db_err, meta_keys, number_to_db_key, db_key_to_number, open_database,
65+
read_db, read_id, read_meta};
6566
use state_db::StateDb;
6667
pub use state_db::PruningMode;
6768

@@ -183,6 +184,14 @@ impl<Block: BlockT> client::blockchain::HeaderBackend<Block> for BlockchainDb<Bl
183184
}
184185
}
185186

187+
fn number(&self, hash: Block::Hash) -> Result<Option<<Block::Header as HeaderT>::Number>, client::error::Error> {
188+
read_id::<Block>(&*self.db, columns::BLOCK_INDEX, BlockId::Hash(hash))
189+
.and_then(|key| match key {
190+
Some(key) => Ok(Some(db_key_to_number(&key)?)),
191+
None => Ok(None),
192+
})
193+
}
194+
186195
fn hash(&self, number: <Block::Header as HeaderT>::Number) -> Result<Option<Block::Hash>, client::error::Error> {
187196
read_db::<Block>(&*self.db, columns::BLOCK_INDEX, columns::HEADER, BlockId::Number(number)).map(|x|
188197
x.map(|raw| HashFor::<Block>::hash(&raw[..])).map(Into::into)
@@ -397,13 +406,13 @@ impl<Block> client::backend::Backend<Block, KeccakHasher, RlpCodec> for Backend<
397406
}
398407
};
399408
if let Some(finalizing_hash) = finalizing_hash {
400-
trace!("Finalizing block #{} ({:?})", number_u64 - self.finalization_window, finalizing_hash);
409+
trace!(target: "db", "Finalizing block #{} ({:?})", number_u64 - self.finalization_window, finalizing_hash);
401410
let commit = self.storage.state_db.finalize_block(&finalizing_hash);
402411
apply_state_commit(&mut transaction, commit);
403412
}
404413
}
405414

406-
debug!("DB Commit {:?} ({}), best = {}", hash, number, pending_block.is_best);
415+
debug!(target: "db", "DB Commit {:?} ({}), best = {}", hash, number, pending_block.is_best);
407416
self.storage.db.write(transaction).map_err(db_err)?;
408417
self.blockchain.update_meta(hash, number, pending_block.is_best);
409418
}

substrate/client/db/src/light.rs

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,29 @@ use kvdb::{KeyValueDB, DBTransaction};
2323

2424
use client::blockchain::{BlockStatus, Cache as BlockchainCache,
2525
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo};
26+
use client::cht;
2627
use client::error::{ErrorKind as ClientErrorKind, Result as ClientResult};
2728
use client::light::blockchain::Storage as LightBlockchainStorage;
2829
use codec::{Decode, Encode};
29-
use primitives::AuthorityId;
30+
use primitives::{AuthorityId, H256, KeccakHasher};
3031
use runtime_primitives::generic::BlockId;
31-
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash, HashFor, Zero, As};
32+
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash, HashFor,
33+
Zero, One, As, NumberFor};
3234
use cache::DbCache;
33-
use utils::{meta_keys, Meta, db_err, number_to_db_key, open_database, read_db, read_id, read_meta};
35+
use utils::{meta_keys, Meta, db_err, number_to_db_key, db_key_to_number, open_database,
36+
read_db, read_id, read_meta};
3437
use DatabaseSettings;
3538

3639
pub(crate) mod columns {
3740
pub const META: Option<u32> = ::utils::COLUMN_META;
3841
pub const BLOCK_INDEX: Option<u32> = Some(1);
3942
pub const HEADER: Option<u32> = Some(2);
4043
pub const AUTHORITIES: Option<u32> = Some(3);
44+
pub const CHT: Option<u32> = Some(4);
4145
}
4246

4347
/// Keep authorities for last 'AUTHORITIES_ENTRIES_TO_KEEP' blocks.
44-
pub(crate) const AUTHORITIES_ENTRIES_TO_KEEP: u64 = 2048;
48+
pub(crate) const AUTHORITIES_ENTRIES_TO_KEEP: u64 = cht::SIZE;
4549

4650
/// Light blockchain storage. Stores most recent headers + CHTs for older headers.
4751
pub struct LightStorage<Block: BlockT> {
@@ -146,6 +150,14 @@ impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
146150
}
147151
}
148152

153+
fn number(&self, hash: Block::Hash) -> ClientResult<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
154+
read_id::<Block>(&*self.db, columns::BLOCK_INDEX, BlockId::Hash(hash))
155+
.and_then(|key| match key {
156+
Some(key) => Ok(Some(db_key_to_number(&key)?)),
157+
None => Ok(None),
158+
})
159+
}
160+
149161
fn hash(&self, number: <<Block as BlockT>::Header as HeaderT>::Number) -> ClientResult<Option<Block::Hash>> {
150162
read_db::<Block>(&*self.db, columns::BLOCK_INDEX, columns::HEADER, BlockId::Number(number)).map(|x|
151163
x.map(|raw| HashFor::<Block>::hash(&raw[..])).map(Into::into)
@@ -156,6 +168,7 @@ impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
156168
impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
157169
where
158170
Block: BlockT,
171+
Block::Hash: From<H256>,
159172
{
160173
fn import_header(&self, is_new_best: bool, header: Block::Header, authorities: Option<Vec<AuthorityId>>) -> ClientResult<()> {
161174
let mut transaction = DBTransaction::new();
@@ -187,6 +200,27 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
187200
None
188201
};
189202

203+
// build new CHT if required
204+
if let Some(new_cht_number) = cht::is_build_required(cht::SIZE, *header.number()) {
205+
let new_cht_start: NumberFor<Block> = cht::start_number(cht::SIZE, new_cht_number);
206+
let new_cht_root: Option<Block::Hash> = cht::compute_root::<Block::Header, KeccakHasher, _>(
207+
cht::SIZE, new_cht_number, (new_cht_start.as_()..)
208+
.map(|num| self.hash(As::sa(num)).unwrap_or_default()));
209+
210+
if let Some(new_cht_root) = new_cht_root {
211+
transaction.put(columns::CHT, &number_to_db_key(new_cht_start), new_cht_root.as_ref());
212+
213+
let mut prune_block = new_cht_start;
214+
let new_cht_end = cht::end_number(cht::SIZE, new_cht_number);
215+
trace!(target: "db", "Replacing blocks [{}..{}] with CHT#{}", new_cht_start, new_cht_end, new_cht_number);
216+
217+
while prune_block <= new_cht_end {
218+
transaction.delete(columns::HEADER, &number_to_db_key(prune_block));
219+
prune_block += <<Block as BlockT>::Header as HeaderT>::Number::one();
220+
}
221+
}
222+
}
223+
190224
debug!("Light DB Commit {:?} ({})", hash, number);
191225
self.db.write(transaction).map_err(db_err)?;
192226
self.update_meta(hash, number, is_new_best);
@@ -197,13 +231,24 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
197231
Ok(())
198232
}
199233

234+
fn cht_root(&self, cht_size: u64, block: <<Block as BlockT>::Header as HeaderT>::Number) -> ClientResult<Block::Hash> {
235+
let no_cht_for_block = || ClientErrorKind::Backend(format!("CHT for block {} not exists", block)).into();
236+
237+
let cht_number = cht::block_to_cht_number(cht_size, block).ok_or_else(no_cht_for_block)?;
238+
let cht_start = cht::start_number(cht_size, cht_number);
239+
self.db.get(columns::CHT, &number_to_db_key(cht_start)).map_err(db_err)?
240+
.ok_or_else(no_cht_for_block)
241+
.and_then(|hash| Block::Hash::decode(&mut &*hash).ok_or_else(no_cht_for_block))
242+
}
243+
200244
fn cache(&self) -> Option<&BlockchainCache<Block>> {
201245
Some(&self.cache)
202246
}
203247
}
204248

205249
#[cfg(test)]
206250
pub(crate) mod tests {
251+
use client::cht;
207252
use runtime_primitives::testing::{H256 as Hash, Header, Block as RawBlock};
208253
use super::*;
209254

@@ -289,4 +334,59 @@ pub(crate) mod tests {
289334
assert_eq!(db.db.iter(columns::HEADER).count(), 2);
290335
assert_eq!(db.db.iter(columns::BLOCK_INDEX).count(), 2);
291336
}
337+
338+
#[test]
339+
fn ancient_headers_are_replaced_with_cht() {
340+
let db = LightStorage::new_test();
341+
342+
// insert genesis block header (never pruned)
343+
let mut prev_hash = insert_block(&db, &Default::default(), 0, None);
344+
345+
// insert SIZE blocks && ensure that nothing is pruned
346+
for number in 0..cht::SIZE {
347+
prev_hash = insert_block(&db, &prev_hash, 1 + number, None);
348+
}
349+
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE) as usize);
350+
assert_eq!(db.db.iter(columns::CHT).count(), 0);
351+
352+
// insert next SIZE blocks && ensure that nothing is pruned
353+
for number in 0..cht::SIZE {
354+
prev_hash = insert_block(&db, &prev_hash, 1 + cht::SIZE + number, None);
355+
}
356+
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + cht::SIZE) as usize);
357+
assert_eq!(db.db.iter(columns::CHT).count(), 0);
358+
359+
// insert block #{2 * cht::SIZE + 1} && check that new CHT is created + headers of this CHT are pruned
360+
insert_block(&db, &prev_hash, 1 + cht::SIZE + cht::SIZE, None);
361+
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + 1) as usize);
362+
assert_eq!(db.db.iter(columns::CHT).count(), 1);
363+
assert!((0..cht::SIZE).all(|i| db.db.get(columns::HEADER, &number_to_db_key(1 + i)).unwrap().is_none()));
364+
}
365+
366+
#[test]
367+
fn get_cht_fails_for_genesis_block() {
368+
assert!(LightStorage::<Block>::new_test().cht_root(cht::SIZE, 0).is_err());
369+
}
370+
371+
#[test]
372+
fn get_cht_fails_for_non_existant_cht() {
373+
assert!(LightStorage::<Block>::new_test().cht_root(cht::SIZE, (cht::SIZE / 2) as u64).is_err());
374+
}
375+
376+
#[test]
377+
fn get_cht_works() {
378+
let db = LightStorage::new_test();
379+
380+
// insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created
381+
let mut prev_hash = Default::default();
382+
for i in 0..1 + cht::SIZE + cht::SIZE + 1 {
383+
prev_hash = insert_block(&db, &prev_hash, i as u64, None);
384+
}
385+
386+
let cht_root_1 = db.cht_root(cht::SIZE, cht::start_number(cht::SIZE, 0)).unwrap();
387+
let cht_root_2 = db.cht_root(cht::SIZE, (cht::start_number(cht::SIZE, 0) + cht::SIZE / 2) as u64).unwrap();
388+
let cht_root_3 = db.cht_root(cht::SIZE, cht::end_number(cht::SIZE, 0)).unwrap();
389+
assert_eq!(cht_root_1, cht_root_2);
390+
assert_eq!(cht_root_2, cht_root_3);
391+
}
292392
}

substrate/client/src/blockchain.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pub trait HeaderBackend<Block: BlockT>: Send + Sync {
3131
fn info(&self) -> Result<Info<Block>>;
3232
/// Get block status.
3333
fn status(&self, id: BlockId<Block>) -> Result<BlockStatus>;
34+
/// Get block number by hash. Returns `None` if the header is not in the chain.
35+
fn number(&self, hash: Block::Hash) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>>;
3436
/// Get block hash by number. Returns `None` if the header is not in the chain.
3537
fn hash(&self, number: NumberFor<Block>) -> Result<Option<Block::Hash>>;
3638

0 commit comments

Comments
 (0)