-
Notifications
You must be signed in to change notification settings - Fork 632
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First step towards flat storage: #7327 Continue work here because #7295 was broken. Here we start building it under protocol feature without adding it to nightly. The goal is to support it for localnet with one node which never sends skip approvals, so later we could extend it to more nodes and eventually implement a migration to mainnet. Now, everyone should be able to build neard with this feature, set `max_block_production_delay` to something big and run localnet. I currently couldn't make all tests work with enabled feature, e.g. `test_care_about_shard`, but I open it for review because I don't think it affects the idea. ## Testing * Check that encoding and decoding value reference gives expected results. `ValueRef` can be reused in other places in code * Check that flat state is used in regular trie handler and not used in view trie handler * Check that after block processing, getting value from regular trie and view trie gives the same result
- Loading branch information
1 parent
751ebc8
commit a85db92
Showing
18 changed files
with
316 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use byteorder::{LittleEndian, ReadBytesExt}; | ||
use near_primitives_core::hash::{hash, CryptoHash}; | ||
use std::io::{Cursor, Read}; | ||
|
||
/// State value reference. Used to charge fees for value length before retrieving the value itself. | ||
pub struct ValueRef { | ||
/// Value length in bytes. | ||
pub length: u32, | ||
/// Unique value hash. | ||
pub hash: CryptoHash, | ||
} | ||
|
||
impl ValueRef { | ||
/// Create serialized value reference by the value. | ||
/// Resulting array stores 4 bytes of length and then 32 bytes of hash. | ||
/// TODO (#7327): consider passing hash here to avoid double computation | ||
pub fn create_serialized(value: &[u8]) -> [u8; 36] { | ||
let mut result = [0u8; 36]; | ||
result[0..4].copy_from_slice(&(value.len() as u32).to_le_bytes()); | ||
result[4..36].copy_from_slice(&hash(value).0); | ||
result | ||
} | ||
|
||
/// Decode value reference from the raw byte array. | ||
/// TODO (#7327): use &[u8; 36] and get rid of Cursor; also check that there are no leftover bytes | ||
pub fn decode(bytes: &[u8]) -> Result<Self, std::io::Error> { | ||
let mut cursor = Cursor::new(bytes); | ||
let value_length = cursor.read_u32::<LittleEndian>()?; | ||
let mut arr = [0; 32]; | ||
cursor.read_exact(&mut arr)?; | ||
let value_hash = CryptoHash(arr); | ||
Ok(ValueRef { length: value_length, hash: value_hash }) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::state::ValueRef; | ||
use near_primitives_core::hash::hash; | ||
|
||
#[test] | ||
fn test_encode_decode() { | ||
let value = vec![1, 2, 3]; | ||
let value_ref_ser = ValueRef::create_serialized(&value); | ||
let value_ref = ValueRef::decode(&value_ref_ser).unwrap(); | ||
assert_eq!(value_ref.length, value.len() as u32); | ||
assert_eq!(value_ref.hash, hash(&value)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
//! Contains flat state optimization logic. | ||
//! | ||
//! The state of the contract is a key-value map, `Map<Vec<u8>, Vec<u8>>`. | ||
//! In the database, we store this map as a trie, which allows us to construct succinct proofs that a certain key/value | ||
//! belongs to contract's state. Using a trie has a drawback -- reading a single key/value requires traversing the trie | ||
//! from the root, loading many nodes from the database. | ||
//! To optimize this, we want to use flat state: alongside the trie, we store a mapping from keys to value | ||
//! references so that, if you don't need a proof, you can do a db lookup in just two db accesses - one to get value | ||
//! reference, one to get value itself. | ||
/// TODO (#7327): consider inlining small values, so we could use only one db access. | ||
|
||
#[cfg(feature = "protocol_feature_flat_state")] | ||
use crate::DBCol; | ||
use crate::Store; | ||
use near_primitives::errors::StorageError; | ||
use near_primitives::hash::CryptoHash; | ||
use near_primitives::state::ValueRef; | ||
|
||
/// Struct for getting value references from the flat storage. | ||
/// Used to speed up `get` and `get_ref` trie methods. | ||
/// It should store all trie keys for state on top of chain head, except delayed receipt keys, because they are the | ||
/// same for each shard and they are requested only once during applying chunk. | ||
/// TODO (#7327): implement flat state deltas to support forks | ||
/// TODO (#7327): store on top of final head (or earlier) so updates will only go forward | ||
#[derive(Clone)] | ||
pub struct FlatState { | ||
#[allow(dead_code)] | ||
store: Store, | ||
} | ||
|
||
impl FlatState { | ||
#[cfg(feature = "protocol_feature_flat_state")] | ||
pub fn new(store: Store) -> Self { | ||
Self { store } | ||
} | ||
|
||
#[allow(unused_variables)] | ||
fn get_raw_ref(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError> { | ||
#[cfg(feature = "protocol_feature_flat_state")] | ||
return self | ||
.store | ||
.get(DBCol::FlatState, key) | ||
.map_err(|_| StorageError::StorageInternalError); | ||
#[cfg(not(feature = "protocol_feature_flat_state"))] | ||
unreachable!(); | ||
} | ||
|
||
/// Get value reference using raw trie key and state root. We assume that flat state contains data for this root. | ||
/// To avoid duplication, we don't store values themselves in flat state, they are stored in `DBCol::State`. Also | ||
/// the separation is done so we could charge users for the value length before loading the value. | ||
/// TODO (#7327): support different roots (or block hashes). | ||
pub fn get_ref( | ||
&self, | ||
_root: &CryptoHash, | ||
key: &[u8], | ||
) -> Result<Option<ValueRef>, StorageError> { | ||
match self.get_raw_ref(key)? { | ||
Some(bytes) => { | ||
ValueRef::decode(&bytes).map(Some).map_err(|_| StorageError::StorageInternalError) | ||
} | ||
None => Ok(None), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.