Account diffing.

This commit is contained in:
Gav Wood 2016-01-13 15:54:17 +01:00
parent 28341fef9f
commit 5ba76d767e
2 changed files with 134 additions and 5 deletions

View File

@ -2,7 +2,24 @@ use util::*;
pub const SHA3_EMPTY: H256 = H256( [0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0, 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70] ); pub const SHA3_EMPTY: H256 = H256( [0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0, 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70] );
#[derive(Debug)] #[derive(Debug,Clone,PartialEq,Eq)]
pub struct Diff<T> where T: Eq {
pub pre: T,
pub post_opt: Option<T>,
}
impl<T> Diff<T> where T: Eq {
pub fn new_opt(pre: T, post: T) -> Option<Self> { if pre == post { None } else { Some(Self::new(pre, post)) } }
pub fn one_opt(t: T) -> Option<Self> { Some(Self::one(t)) }
pub fn new(pre: T, post: T) -> Self { Diff { pre: pre, post_opt: Some(post) }}
pub fn one(t: T) -> Self { Diff { pre: t, post_opt: None }}
pub fn pre(&self) -> &T { &self.pre }
pub fn post(&self) -> &T { match self.post_opt { Some(ref x) => x, None => &self.pre } }
}
#[derive(Debug,Clone,PartialEq,Eq)]
/// Genesis account data. Does not have a DB overlay cache. /// Genesis account data. Does not have a DB overlay cache.
pub struct PodAccount { pub struct PodAccount {
// Balance of the account. // Balance of the account.
@ -13,6 +30,116 @@ pub struct PodAccount {
pub storage: BTreeMap<H256, H256>, pub storage: BTreeMap<H256, H256>,
} }
#[derive(Debug,Clone,PartialEq,Eq)]
pub struct PodAccountDiff {
pub exists: Diff<bool>,
pub balance: Option<Diff<U256>>,
pub nonce: Option<Diff<U256>>,
pub code: Option<Diff<Bytes>>,
pub storage: BTreeMap<H256, Diff<H256>>,
}
type StateDiff = BTreeMap<Address, PodAccountDiff>;
pub fn diff(pre: &Option<PodAccount>, post: &Option<PodAccount>) -> Option<PodAccountDiff> {
match (pre, post) {
(&Some(ref x), &None) | (&None, &Some(ref x)) => Some(PodAccountDiff {
exists: Diff::new(pre.is_some(), post.is_some()),
balance: Diff::one_opt(x.balance.clone()),
nonce: Diff::one_opt(x.nonce.clone()),
code: Diff::one_opt(x.code.clone()),
storage: x.storage.iter().fold(BTreeMap::new(), |mut m, (k, v)| {m.insert(k.clone(), Diff::one(v.clone())); m})
}),
(&Some(ref pre), &Some(ref post)) => {
let pre_keys: BTreeSet<_> = pre.storage.keys().collect();
let post_keys: BTreeSet<_> = post.storage.keys().collect();
let storage: Vec<_> = pre_keys.union(&post_keys)
.filter(|k| pre.storage.get(k).unwrap_or(&H256::new()) != post.storage.get(k).unwrap_or(&H256::new()))
.collect();
if pre.balance != post.balance || pre.nonce != post.nonce || pre.code != post.code || storage.len() > 0 {
Some(PodAccountDiff {
exists: Diff::one(true),
balance: Diff::new_opt(pre.balance.clone(), post.balance.clone()),
nonce: Diff::new_opt(pre.nonce.clone(), post.nonce.clone()),
code: Diff::new_opt(pre.code.clone(), post.code.clone()),
storage: storage.into_iter().fold(BTreeMap::new(), |mut m, k| {
let v = Diff::new(pre.storage.get(&k).cloned().unwrap_or(H256::new()), post.storage.get(&k).cloned().unwrap_or(H256::new()));
m.insert((*k).clone(), v);
m
}),
})
} else {
None
}
},
_ => None,
}
}
#[test]
fn account_diff_existence() {
let a = Some(PodAccount{balance: U256::from(69u64), nonce: U256::zero(), code: vec![], storage: BTreeMap::new()});
assert_eq!(diff(&a, &a), None);
assert_eq!(diff(&None, &a), Some(PodAccountDiff{
exists: Diff::new(false, true),
balance: Diff::one_opt(U256::from(69u64)),
nonce: Diff::one_opt(U256::zero()),
code: Diff::one_opt(vec![]),
storage: BTreeMap::new(),
}));
}
#[test]
fn account_diff_basic() {
let a = Some(PodAccount{balance: U256::from(69u64), nonce: U256::zero(), code: vec![], storage: BTreeMap::new()});
let b = Some(PodAccount{balance: U256::from(42u64), nonce: U256::from(1u64), code: vec![], storage: BTreeMap::new()});
assert_eq!(diff(&a, &b), Some(PodAccountDiff {
exists: Diff::one(true),
balance: Diff::new_opt(U256::from(69u64), U256::from(42u64)),
nonce: Diff::new_opt(U256::zero(), U256::from(1u64)),
code: None,
storage: BTreeMap::new(),
}));
}
#[test]
fn account_diff_code() {
let a = Some(PodAccount{balance: U256::zero(), nonce: U256::zero(), code: vec![], storage: BTreeMap::new()});
let b = Some(PodAccount{balance: U256::zero(), nonce: U256::from(1u64), code: vec![0x00u8], storage: BTreeMap::new()});
assert_eq!(diff(&a, &b), Some(PodAccountDiff {
exists: Diff::one(true),
balance: None,
nonce: Diff::new_opt(U256::zero(), U256::from(1u64)),
code: Diff::new_opt(vec![], vec![0x00u8]),
storage: BTreeMap::new(),
}));
}
pub fn h256_from_u8(v: u8) -> H256 {
let mut r = H256::new();
r[31] = v;
r
}
#[test]
fn account_diff_storage() {
let a = Some(PodAccount{balance: U256::zero(), nonce: U256::zero(), code: vec![], storage: vec![(1u8, 1u8), (2, 2), (3, 3), (4, 4), (5, 0), (6, 0), (7, 0)].into_iter().fold(BTreeMap::new(), |mut m, (k, v)|{m.insert(h256_from_u8(k), h256_from_u8(v)); m})});
let b = Some(PodAccount{balance: U256::zero(), nonce: U256::zero(), code: vec![], storage: vec![(1u8, 1u8), (2, 3), (3, 0), (5, 0), (7, 7), (8, 0), (9, 9)].into_iter().fold(BTreeMap::new(), |mut m, (k, v)|{m.insert(h256_from_u8(k), h256_from_u8(v)); m})});
assert_eq!(diff(&a, &b), Some(PodAccountDiff {
exists: Diff::one(true),
balance: None,
nonce: None,
code: None,
storage: vec![
(2u8, Diff::new(h256_from_u8(2), h256_from_u8(3))),
(3, Diff::new(h256_from_u8(3), H256::new())),
(4, Diff::new(h256_from_u8(4), H256::new())),
(7, Diff::new(H256::new(), h256_from_u8(7))),
(9, Diff::new(H256::new(), h256_from_u8(9))),
].into_iter().fold(BTreeMap::new(), |mut m, (k, v)|{m.insert(h256_from_u8(k), v); m})
}));
}
/// Single account in the system. /// Single account in the system.
#[derive(Clone)] #[derive(Clone)]
pub struct Account { pub struct Account {
@ -229,7 +356,7 @@ impl Account {
/// Commit any unsaved code. `code_hash` will always return the hash of the `code_cache` after this. /// Commit any unsaved code. `code_hash` will always return the hash of the `code_cache` after this.
pub fn commit_code(&mut self, db: &mut HashDB) { pub fn commit_code(&mut self, db: &mut HashDB) {
println!("Commiting code of {:?} - {:?}, {:?}", self, self.code_hash.is_none(), self.code_cache.is_empty()); trace!("Commiting code of {:?} - {:?}, {:?}", self, self.code_hash.is_none(), self.code_cache.is_empty());
match (self.code_hash.is_none(), self.code_cache.is_empty()) { match (self.code_hash.is_none(), self.code_cache.is_empty()) {
(true, true) => self.code_hash = Some(SHA3_EMPTY), (true, true) => self.code_hash = Some(SHA3_EMPTY),
(true, false) => { (true, false) => {

View File

@ -55,18 +55,20 @@ fn do_json_test(json_data: &[u8]) -> Vec<String> {
println!("Transaction: {:?}", t); println!("Transaction: {:?}", t);
println!("Env: {:?}", env); println!("Env: {:?}", env);
println!("Out: {:?}", out);
println!("Pre: {:?}", pre); println!("Pre:\n{:?}", pre);
println!("Post: {:?}", post);
let mut s = State::new_temp(); let mut s = State::new_temp();
s.populate_from(pre); s.populate_from(pre);
println!("Our-pre:\n{:?}", s.to_pod_map());
s.apply(&env, engine.deref(), &t).unwrap(); s.apply(&env, engine.deref(), &t).unwrap();
let our_post = s.to_pod_map(); let our_post = s.to_pod_map();
if fail_unless(s.root() == &post_state_root) { if fail_unless(s.root() == &post_state_root) {
println!("EXPECTED:\n{:?}", post); println!("EXPECTED:\n{:?}", post);
println!("GOT:\n{:?}", our_post); println!("GOT:\n{:?}", our_post);
// println!("DIFF:\n{:?}", pod_map_diff(post, our_post));
} }
// TODO: Compare logs. // TODO: Compare logs.