-
Notifications
You must be signed in to change notification settings - Fork 619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: move nodes counter to the caching storage #6315
Changes from all commits
a71e23b
3b86172
52d5c59
7c587df
df07bb9
c9db0d1
ffab524
6b5327d
c06f983
72c0be1
ad2fc60
0517369
5777028
d168d01
6f3c509
31e0471
3eabc3b
52d7bda
6f4c37d
2c1ae5b
b60fcf0
a66b203
63a700b
0336b32
314c3f3
7aaf621
9017bd4
de97807
118835d
c4744a7
df58c0f
bb6ecba
ab55083
09fd771
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ use std::cmp::Ordering; | |
use std::collections::HashMap; | ||
use std::fmt; | ||
use std::io::{Cursor, Read, Write}; | ||
use std::sync::Arc; | ||
|
||
use borsh::{BorshDeserialize, BorshSerialize}; | ||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; | ||
|
@@ -18,10 +17,8 @@ use crate::trie::insert_delete::NodesStorage; | |
use crate::trie::iterator::TrieIterator; | ||
use crate::trie::nibble_slice::NibbleSlice; | ||
pub use crate::trie::shard_tries::{KeyForStateChanges, ShardTries, WrappedTrieChanges}; | ||
use crate::trie::trie_storage::{ | ||
TouchedNodesCounter, TrieMemoryPartialStorage, TrieRecordingStorage, TrieStorage, | ||
}; | ||
pub(crate) use crate::trie::trie_storage::{TrieCache, TrieCachingStorage}; | ||
use crate::trie::trie_storage::{TrieMemoryPartialStorage, TrieRecordingStorage, TrieStorage}; | ||
use crate::StorageError; | ||
|
||
mod insert_delete; | ||
|
@@ -408,7 +405,6 @@ impl RawTrieNodeWithSize { | |
|
||
pub struct Trie { | ||
pub(crate) storage: Box<dyn TrieStorage>, | ||
pub counter: TouchedNodesCounter, | ||
} | ||
|
||
/// Stores reference count change for some key-value pair in DB. | ||
|
@@ -472,7 +468,7 @@ pub struct ApplyStatePartResult { | |
|
||
impl Trie { | ||
pub fn new(store: Box<dyn TrieStorage>, _shard_uid: ShardUId) -> Self { | ||
Trie { storage: store, counter: TouchedNodesCounter::default() } | ||
Trie { storage: store } | ||
} | ||
|
||
pub fn recording_reads(&self) -> Self { | ||
|
@@ -483,7 +479,7 @@ impl Trie { | |
shard_uid: storage.shard_uid, | ||
recorded: RefCell::new(Default::default()), | ||
}; | ||
Trie { storage: Box::new(storage), counter: TouchedNodesCounter::default() } | ||
Trie { storage: Box::new(storage) } | ||
} | ||
|
||
pub fn empty_root() -> StateRoot { | ||
|
@@ -506,7 +502,6 @@ impl Trie { | |
recorded_storage, | ||
visited_nodes: Default::default(), | ||
}), | ||
counter: TouchedNodesCounter::default(), | ||
} | ||
} | ||
|
||
|
@@ -578,7 +573,6 @@ impl Trie { | |
if *hash == Trie::empty_root() { | ||
Ok(memory.store(TrieNodeWithSize::empty())) | ||
} else { | ||
self.counter.increment(); | ||
let bytes = self.storage.retrieve_raw_bytes(hash)?; | ||
match RawTrieNodeWithSize::decode(&bytes) { | ||
Ok(value) => { | ||
|
@@ -602,7 +596,7 @@ impl Trie { | |
if *hash == Trie::empty_root() { | ||
return Ok(TrieNodeWithSize::empty()); | ||
} | ||
let bytes = self.retrieve_raw_bytes(hash)?; | ||
let bytes = self.storage.retrieve_raw_bytes(hash)?; | ||
match RawTrieNodeWithSize::decode(&bytes) { | ||
Ok(value) => Ok(TrieNodeWithSize::from_raw(value)), | ||
Err(_) => Err(StorageError::StorageInconsistentState(format!( | ||
|
@@ -612,16 +606,11 @@ impl Trie { | |
} | ||
} | ||
|
||
pub(crate) fn retrieve_raw_bytes(&self, hash: &CryptoHash) -> Result<Arc<[u8]>, StorageError> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @matklad - more Rust question: in such case - should we 'keep' this method (and have it simply call self.storage.retrieve_raw_bytes) or should we allow other classes to call XX.storage.retrieve_raw_bytes (as Alex did in this PR)? For me - one advantage of keeping this method - is that we keep better 'layers' - and avoid having other classes 'peek' into Trie) - but I wonder if it has some performance implications There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mm-near there won't be performance implications here. In general, it's a pretty safe assumption in Rust that linguistic abstractions don't introduce overhead -- static nature of the language and unboxed memory layout guarantee that compiler "sees through" any abstractions. Regarding layering, my general observation is that layering is good only when it is enforced: it's better to focus on specific places where layering is important, rather than to try to enforce it recursively throughout the stack. There should be places where there's explicitly no layers. For Rust, the best enforcement is aligning layers with crate boundaries: that's why I am relatively picky about As an aside, I think we can make storage private rather |
||
self.counter.increment(); | ||
self.storage.retrieve_raw_bytes(hash) | ||
} | ||
|
||
pub fn retrieve_root_node(&self, root: &StateRoot) -> Result<StateRootNode, StorageError> { | ||
if *root == Trie::empty_root() { | ||
return Ok(StateRootNode::empty()); | ||
} | ||
let data = self.retrieve_raw_bytes(root)?; | ||
let data = self.storage.retrieve_raw_bytes(root)?; | ||
match RawTrieNodeWithSize::decode(&data) { | ||
Ok(value) => { | ||
let memory_usage = TrieNodeWithSize::from_raw(value).memory_usage; | ||
|
@@ -645,7 +634,7 @@ impl Trie { | |
if hash == Trie::empty_root() { | ||
return Ok(None); | ||
} | ||
let bytes = self.retrieve_raw_bytes(&hash)?; | ||
let bytes = self.storage.retrieve_raw_bytes(&hash)?; | ||
let node = RawTrieNodeWithSize::decode(&bytes).map_err(|_| { | ||
StorageError::StorageInconsistentState("RawTrieNode decode failed".to_string()) | ||
})?; | ||
|
@@ -701,7 +690,7 @@ impl Trie { | |
pub fn get(&self, root: &CryptoHash, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError> { | ||
match self.get_ref(root, key)? { | ||
Some((_length, hash)) => { | ||
self.retrieve_raw_bytes(&hash).map(|bytes| Some(bytes.to_vec())) | ||
self.storage.retrieve_raw_bytes(&hash).map(|bytes| Some(bytes.to_vec())) | ||
} | ||
None => Ok(None), | ||
} | ||
|
@@ -761,6 +750,10 @@ impl Trie { | |
pub fn iter<'a>(&'a self, root: &CryptoHash) -> Result<TrieIterator<'a>, StorageError> { | ||
TrieIterator::new(self, root) | ||
} | ||
|
||
pub fn get_touched_nodes_count(&self) -> u64 { | ||
self.storage.get_touched_nodes_count() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any protocol changes here? In particular, I see there's
self.storage.retrieve_raw_bytes(hash)?;
indelete_value
, and that now would increment the counter.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great question!
From what I see, all logic leading to incrementing counter here happens in
Trie::update
which itself is called after all tx/receipt execution. I feel that some counter increments makes us think that we charge for them somewhere, although we don't do it in reality. So it's better to remove them. But I also will be happy if someone double-check this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to test this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that what exactly we need to test is that gas costs were not affected by this change - because they are part of protocol.
The similar purpose is served by the test group next to
Runtime
implementation. In particular, they check gas costs afterRuntime::apply()
call, which includeTrie::update
call.However, we currently have only one test applying funcall txs -
test_apply_deficit_gas_for_function_call_partial
. So I will add a new test there which will test a funcall which touches contract storage. This should help in the future - if we accidentally change counter, the test must fail.UPD: added a test
test_contract_read_write_cost
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Longarithm I still don't understand why the change in
delete_value
won't cause any change in gas counting. Could you elaborate more on that?One way to test this manually, is maybe run state viewer apply chain on some mainnet blocks to verify that gas counting doesn't change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mzhangmzz
delete_value
remains unchanged here, it is a change inmove_node_to_mutable
.Anyway it won't cause changes in gas counting, because these methods are called only on
Trie::update
, which happens after tx/receipt processing as I said in the second comment. Having the increment I am removing actually didn't make much sense.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
delete_value
will change here, because previously it only callsself.storage.retrieve_raw_bytes(hash)?
without incrementing the counter. After your change, since you moved the counter incrementing logic insidestorage.retrieve_raw_bytes
,delete_value
will increase the counter.I'm not super familiar with the gas counting logic, so I'm not sure why it is called in
Trie::update
won't affect gas counting.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you are right.
I've looked over all
Trie::update
calls. It is called for four purposes:add_values_to_split_states_impl
)finalize_genesis
)TrieUpdate::finalize
The latter function is called for similar purposes - I think it doesn't make much sense to list all them. Also it is called in the end of
Runtime::apply
to apply changes to trie, when all tx/receipts are already executed and burnt/used gas is computed.Moreover, trie counter is used for gas counting only during function call execution, which is triggered only by
process_receipt
, this is another reason whyTrie::update
change doesn't affect it.Sorry I didn't explain it before properly. On my side, it was a fast look over CLion usages, so I thought it didn't worth attention.
Also, I could leave the incrementation logic as is. But it would make the implementation more ugly, because by default
TrieStorage
shouldn't know about counter.