DB-based blockchain data cache for light nodes#251
Conversation
f5e88b3 to
702ea9f
Compare
substrate/client/db/src/light.rs
Outdated
| // prune authorities from 'ancient' blocks | ||
| let mut update_best_authorities = best_authorities.is_some(); | ||
| if let Some(ancient_number) = number.checked_sub(AUTHORITIES_ENTRIES_TO_KEEP) { | ||
| if self.cache.authorities_at_cache().prune_entries(&mut transaction, As::sa(ancient_number))?.1 { |
There was a problem hiding this comment.
I think it might be a little better if the return value of prune_entries be destructed and named on the separate line
substrate/client/src/client.rs
Outdated
| self.executor.call(id, "authorities",&[]) | ||
| .and_then(|r| Vec::<AuthorityId>::decode(&mut &r.return_data[..]) | ||
| .ok_or(error::ErrorKind::AuthLenInvalid.into())) | ||
| match self.backend.blockchain().cache() .and_then(|cache| cache.authorities_at(*id)) { |
There was a problem hiding this comment.
unneeded space between .cache() and .and_then
|
As @pepyakin suggested (externally), it would be good to to leave at least 1 entry in cache (the newest one) when pruning. Will do (I hope tonight/tomorrow). |
gnunicorn
left a comment
There was a problem hiding this comment.
Some house-style remarks, some docs and a few logic questions I'd like to have addressed.
substrate/client/db/src/cache.rs
Outdated
| <<Block as BlockT>::Header as HeaderT>::Number: As<u64>, | ||
| { | ||
| /// Create new cache. | ||
| pub fn new(db: Arc<KeyValueDB>, block_index_column: Option<u32>, authorities_column: Option<u32>) -> ClientResult<Self> { |
| fn authorities_at(&self, at: BlockId<Block>) -> Option<Vec<AuthorityId>> { | ||
| let authorities_at = read_id(&*self.db, self.block_index_column, at).and_then(|at| match at { | ||
| Some(at) => self.authorities_at.value_at_key(at), | ||
| None => Ok(None), |
There was a problem hiding this comment.
I am a bit puzzled about this. How is it possible that we'd ever come to None in here?
There was a problem hiding this comment.
My guess - when we're calling authorities_at(BlockId::Hash(unknown_hash)). Or am I wrong? :)
Anyway - it is the cache, I'd prefer to write cache code without any extra expects, since this is an optimization anyway.
| } | ||
|
|
||
| /// Database-backed blockchain cache which holds its entries as a list. | ||
| /// The meta column holds the pointer to the best known cache entry and |
There was a problem hiding this comment.
what is the definition of best in this context?
| Some(block) => { | ||
| let valid_from = db_key_to_number(&block)?; | ||
| read_storage_entry::<Block, T>(&*db, column, valid_from) | ||
| .map(|entry| Some(Entry { |
There was a problem hiding this comment.
this is some deep listing ... can we split this up to use less indentations (by using multiple .and_then or something)?
substrate/client/db/src/cache.rs
Outdated
|
|
||
| /// Commits the new best pending value to the database. Returns Some if best entry must | ||
| /// be updated after transaction is committed. | ||
| pub fn commit_best_entry(&self, transaction: &mut DBTransaction, valid_from: <<Block as BlockT>::Header as HeaderT>::Number, pending_value: Option<T>) -> Option<Entry<<<Block as BlockT>::Header as HeaderT>::Number, T>> { |
substrate/client/src/client.rs
Outdated
| Block: BlockT, | ||
| { | ||
| fn import_block(&self, block: Block, justification: ::bft::Justification<Block::Hash>) { | ||
| fn import_block(&self, block: Block, justification: ::bft::Justification<Block::Hash>, authorities: &[AuthorityId]) { |
substrate/client/src/client.rs
Outdated
| #[test] | ||
| fn client_uses_authorities_from_blockchain_cache() { | ||
| let client = test_client::new(); | ||
| test_client::client::in_mem::cache_authorities_at(client.backend().blockchain(), Default::default(), Some(vec![[1u8; 32].into()])); |
There was a problem hiding this comment.
(and the one following: ) please wrap.
substrate/client/src/in_mem.rs
Outdated
|
|
||
| impl<Block: BlockT> light::blockchain::Storage<Block> for Blockchain<Block> { | ||
| fn import_header(&self, is_new_best: bool, header: Block::Header) -> error::Result<()> { | ||
| fn import_header(&self, is_new_best: bool, header: Block::Header, authorities: Option<Vec<AuthorityId>>) -> error::Result<()> { |
substrate/client/src/in_mem.rs
Outdated
| } | ||
|
|
||
| /// Insert authorities entry into in-memory blockchain cache. Extracted as a separate function to use it in tests. | ||
| pub fn cache_authorities_at<Block: BlockT>(blockchain: &Blockchain<Block>, at: Block::Hash, authorities: Option<Vec<AuthorityId>>) { |
| pub trait Storage<Block: BlockT>: BlockchainHeaderBackend<Block> { | ||
| /// Store new header. | ||
| fn import_header(&self, is_new_best: bool, header: Block::Header) -> ClientResult<()>; | ||
| fn import_header(&self, is_new_best: bool, header: Block::Header, authorities: Option<Vec<AuthorityId>>) -> ClientResult<()>; |
gnunicorn
left a comment
There was a problem hiding this comment.
Thanks, looks good. Just one last clarification - maybe I am reading this wrong?!?
| } | ||
|
|
||
| /// Prune all entries from the beginning up to the block (including entry at the number). Returns | ||
| /// the number of pruned entries. Pruning never deletes the latest entry in the cache. |
There was a problem hiding this comment.
Do you have test for that? Because if I understand L218 correctly, we early quit the function without actually pruning if our loop finds the last entry, no?
|
The meaning of L218 is about returning from the function if first entry of the list is for block later than There are couple of tests for pruning - see Thanks to your comment, I've found an issue with incorrect reporting of number of pruned entries - they have included the last entry even if it was not physically deleted (actually it was deleted and then inserted back into the cache). Fixed it + fixed |
|
conficts... |
* master: (86 commits) Make contract a separate runtime module (#345) Version bump (#450) DB-based blockchain data cache for light nodes (#251) Update libp2p again (#445) Update version on git head change (#444) Fix the public key of bootnode 3 (#441) Update libp2p (#442) Switch to the master branch of libp2p (#427) Export ws port 9944 and add doc (#440) Iterate over overlay to decide which keys to purge (#436) Exit signal gets its own trait (#433) Add docker image (#375) Reset peers.json if the content is not loadable (#405) Limit number of incoming connections (#391) Fix memory leaks in libp2p (#432) Do not queue empty blocks set for import (#431) 5 random fixes (#1) (#435) Chore: fix typo (#434) Prevent building invalid blocks (#430) Better logging for public key mismatch (#429) ...
* Improve code and dependencies Signed-off-by: koushiro <koushiro.cqx@gmail.com> * remove confused alias Signed-off-by: koushiro <koushiro.cqx@gmail.com>
…armer Subspace networking (farmer)
on top of #250
this PR is for this commit only
This PR is about implementing
authorities_at(and in future -code_at) cache as was first mentioned in this comment.So idea is: when we're importing block, we're checking justification, which includes fetching
authorities_at(at the parent' block state) from remote node (on light clients). We're adding this data to theBlockImportOperationand committing it into the list-style db-cache when committing transaction. The cache is organized in the linked list form. Meta column holds the reference to the tail node (most recent entry in our case) + every node keeps the reference to the previous list element. Blocks between two nodes are guaranteed to have the sameauthorities_atvalue (the one that we've passed to theBlockImportOperationbefore). List entries for 'ancient' blocks (> 2048) are got pruned.In the future (when we'll have 'digest signals' about changes),
BlockImportOperationmust be altered to mark thatauthorities_athas/has not changed in the block we're importing.The same cache will probably be used later for the
code_atcache.