hash account state on store (#5573)

This commit is contained in:
sakridge
2019-09-20 13:21:12 -07:00
committed by GitHub
parent 5dd3a07a23
commit 19ae556857
14 changed files with 484 additions and 54 deletions

View File

@ -1,9 +1,10 @@
use crate::hash::Hash;
use crate::{clock::Epoch, pubkey::Pubkey};
use std::{cmp, fmt};
/// An Account with data that is stored on chain
#[repr(C)]
#[derive(Serialize, Deserialize, Clone, Default, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct Account {
/// lamports in the account
pub lamports: u64,
@ -15,8 +16,26 @@ pub struct Account {
pub executable: bool,
/// the epoch at which this account will next owe rent
pub rent_epoch: Epoch,
/// Hash of this account's state, skip serializing as to not expose to external api
/// Used for keeping the accounts state hash updated.
#[serde(skip_serializing, skip_deserializing)]
pub hash: Hash,
}
/// skip comparison of account.hash, since it is only meaningful when the account is loaded in a
/// given fork and some tests do not have that.
impl PartialEq for Account {
fn eq(&self, other: &Self) -> bool {
self.lamports == other.lamports
&& self.data == other.data
&& self.owner == other.owner
&& self.executable == other.executable
&& self.rent_epoch == other.rent_epoch
}
}
impl Eq for Account {}
impl fmt::Debug for Account {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let data_len = cmp::min(64, self.data.len());
@ -27,13 +46,14 @@ impl fmt::Debug for Account {
};
write!(
f,
"Account {{ lamports: {} data.len: {} owner: {} executable: {} rent_epoch: {}{} }}",
"Account {{ lamports: {} data.len: {} owner: {} executable: {} rent_epoch: {}{} hash: {} }}",
self.lamports,
self.data.len(),
self.owner,
self.executable,
self.rent_epoch,
data_str,
self.hash,
)
}
}

163
sdk/src/bank_hash.rs Normal file
View File

@ -0,0 +1,163 @@
use crate::hash::Hash;
use bincode::deserialize_from;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaChaRng;
use serde::{Deserialize, Serialize, Serializer};
use std::fmt;
use std::io::Cursor;
// Type for representing a bank accounts state.
// Taken by xor of a sha256 of accounts state for lower 32-bytes, and
// then generating the rest of the bytes with a chacha rng init'ed with that state.
// 440 bytes solves the birthday problem when xor'ing of preventing an attacker of
// finding a value or set of values that could be xor'ed to match the bitpattern
// of an existing state value.
const BANK_HASH_BYTES: usize = 448;
#[derive(Clone, Copy)]
pub struct BankHash([u8; BANK_HASH_BYTES]);
impl fmt::Debug for BankHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BankHash {}", hex::encode(&self.0[..32]))
}
}
impl fmt::Display for BankHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl PartialEq for BankHash {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]
}
}
impl Eq for BankHash {}
impl Default for BankHash {
fn default() -> Self {
BankHash([0u8; BANK_HASH_BYTES])
}
}
impl BankHash {
pub fn from_hash(hash: &Hash) -> Self {
let mut new = BankHash::default();
// default hash should result in all 0s thus nop for xor
if *hash == Hash::default() {
return new;
}
new.0[..32].copy_from_slice(hash.as_ref());
let mut seed = [0u8; 32];
seed.copy_from_slice(hash.as_ref());
let mut generator = ChaChaRng::from_seed(seed);
generator.fill(&mut new.0[32..]);
new
}
pub fn is_default(&self) -> bool {
self.0[0..32] == Hash::default().as_ref()[0..32]
}
pub fn xor(self: &mut BankHash, hash: BankHash) {
for (i, b) in hash.as_ref().iter().enumerate() {
self.0.as_mut()[i] ^= b;
}
}
}
impl Serialize for BankHash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.0[..])
}
}
struct BankHashVisitor;
impl<'a> serde::de::Visitor<'a> for BankHashVisitor {
type Value = BankHash;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("Expecting BankHash")
}
#[allow(clippy::mutex_atomic)]
fn visit_bytes<E>(self, data: &[u8]) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
use serde::de::Error;
let mut new = BankHash::default();
let mut rd = Cursor::new(&data[..]);
for i in 0..BANK_HASH_BYTES {
new.0[i] = deserialize_from(&mut rd).map_err(Error::custom)?;
}
Ok(new)
}
}
impl<'de> Deserialize<'de> for BankHash {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
deserializer.deserialize_bytes(BankHashVisitor)
}
}
impl AsRef<[u8]> for BankHash {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl AsMut<[u8]> for BankHash {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0[..]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::hash;
use bincode::{deserialize, serialize};
use log::*;
#[test]
fn test_bankhash() {
let hash = hash(&[1, 2, 3, 4]);
let bank_hash = BankHash::from_hash(&hash);
assert!(!bank_hash.is_default());
let default = BankHash::default();
assert!(default.is_default());
assert!(bank_hash != default);
assert!(bank_hash == bank_hash);
for i in 0..BANK_HASH_BYTES / 32 {
let start = i * 32;
let end = start + 32;
assert!(bank_hash.0[start..end] != [0u8; 32]);
}
}
#[test]
fn test_serialize() {
solana_logger::setup();
let hash = hash(&[1, 2, 3, 4]);
let bank_hash = BankHash::from_hash(&hash);
info!("{}", bank_hash);
let bytes = serialize(&bank_hash).unwrap();
let new: BankHash = deserialize(&bytes).unwrap();
info!("{}", new);
assert_eq!(new, bank_hash);
}
}

View File

@ -28,6 +28,8 @@ pub mod log;
// Modules not usable by on-chain programs
#[cfg(not(feature = "program"))]
pub mod bank_hash;
#[cfg(not(feature = "program"))]
pub mod client;
#[cfg(not(feature = "program"))]
pub mod genesis_block;

View File

@ -1,4 +1,5 @@
use crate::account::Account;
use crate::hash::Hash;
const ID: [u8; 32] = [
5, 135, 132, 191, 20, 139, 164, 40, 47, 176, 18, 87, 72, 136, 169, 241, 83, 160, 125, 173, 247,
@ -15,5 +16,6 @@ pub fn create_loadable_account(name: &str) -> Account {
data: name.as_bytes().to_vec(),
executable: true,
rent_epoch: 0,
hash: Hash::default(),
}
}