Merge branch 'evm' into evm_factory_parametrized
Conflicts: src/executive.rs
This commit is contained in:
commit
f96e598f20
179
src/account.rs
179
src/account.rs
@ -36,6 +36,58 @@ pub struct PodAccount {
|
|||||||
pub storage: BTreeMap<H256, H256>,
|
pub storage: BTreeMap<H256, H256>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PodAccount {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "(bal={}; nonce={}; code={} bytes, #{}; storage={} items)", self.balance, self.nonce, self.code.len(), self.code.sha3(), self.storage.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||||
|
pub struct PodState (BTreeMap<Address, PodAccount>);
|
||||||
|
|
||||||
|
pub fn map_h256_h256_from_json(json: &Json) -> BTreeMap<H256, H256> {
|
||||||
|
json.as_object().unwrap().iter().fold(BTreeMap::new(), |mut m, (key, value)| {
|
||||||
|
m.insert(H256::from(&u256_from_str(key)), H256::from(&u256_from_json(value)));
|
||||||
|
m
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PodState {
|
||||||
|
/// Contruct a new object from the `m`.
|
||||||
|
pub fn from(m: BTreeMap<Address, PodAccount>) -> PodState { PodState(m) }
|
||||||
|
|
||||||
|
/// Translate the JSON object into a hash map of account information ready for insertion into State.
|
||||||
|
pub fn from_json(json: &Json) -> PodState {
|
||||||
|
PodState(json.as_object().unwrap().iter().fold(BTreeMap::new(), |mut state, (address, acc)| {
|
||||||
|
let balance = acc.find("balance").map(&u256_from_json);
|
||||||
|
let nonce = acc.find("nonce").map(&u256_from_json);
|
||||||
|
let storage = acc.find("storage").map(&map_h256_h256_from_json);;
|
||||||
|
let code = acc.find("code").map(&bytes_from_json);
|
||||||
|
if balance.is_some() || nonce.is_some() || storage.is_some() || code.is_some() {
|
||||||
|
state.insert(address_from_hex(address), PodAccount{
|
||||||
|
balance: balance.unwrap_or(U256::zero()),
|
||||||
|
nonce: nonce.unwrap_or(U256::zero()),
|
||||||
|
storage: storage.unwrap_or(BTreeMap::new()),
|
||||||
|
code: code.unwrap_or(Vec::new())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
state
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drain object to get the underlying map.
|
||||||
|
pub fn drain(self) -> BTreeMap<Address, PodAccount> { self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PodState {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
for (add, acc) in &self.0 {
|
||||||
|
try!(writeln!(f, "{} => {}", add, acc));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,PartialEq,Eq)]
|
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||||
pub struct AccountDiff {
|
pub struct AccountDiff {
|
||||||
pub balance: Diff<U256>, // Allowed to be Same
|
pub balance: Diff<U256>, // Allowed to be Same
|
||||||
@ -54,12 +106,47 @@ impl AccountDiff {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format(u: &H256) -> String {
|
||||||
|
if u <= &H256::from(0xffffffff) {
|
||||||
|
format!("{} = 0x{:x}", U256::from(u.as_slice()).low_u32(), U256::from(u.as_slice()).low_u32())
|
||||||
|
} else if u <= &H256::from(u64::max_value()) {
|
||||||
|
format!("{} = 0x{:x}", U256::from(u.as_slice()).low_u64(), U256::from(u.as_slice()).low_u64())
|
||||||
|
// } else if u <= &H256::from("0xffffffffffffffffffffffffffffffffffffffff") {
|
||||||
|
// format!("@{}", Address::from(u))
|
||||||
|
} else {
|
||||||
|
format!("#{}", u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,PartialEq,Eq)]
|
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||||
pub struct StateDiff (BTreeMap<Address, AccountDiff>);
|
pub struct StateDiff (BTreeMap<Address, AccountDiff>);
|
||||||
|
|
||||||
impl fmt::Display for AccountDiff {
|
impl fmt::Display for AccountDiff {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "...\n")
|
match self.nonce {
|
||||||
|
Diff::Born(ref x) => try!(write!(f, " non {}", x)),
|
||||||
|
Diff::Changed(ref pre, ref post) => try!(write!(f, "#{} ({} {} {})", post, pre, if pre > post {"-"} else {"+"}, *max(pre, post) - * min(pre, post))),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
match self.balance {
|
||||||
|
Diff::Born(ref x) => try!(write!(f, " bal {}", x)),
|
||||||
|
Diff::Changed(ref pre, ref post) => try!(write!(f, "${} ({} {} {})", post, pre, if pre > post {"-"} else {"+"}, *max(pre, post) - *min(pre, post))),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
match self.code {
|
||||||
|
Diff::Born(ref x) => try!(write!(f, " code {}", x.pretty())),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
try!(write!(f, "\n"));
|
||||||
|
for (k, dv) in self.storage.iter() {
|
||||||
|
match dv {
|
||||||
|
&Diff::Born(ref v) => try!(write!(f, " + {} => {}\n", format(k), format(v))),
|
||||||
|
&Diff::Changed(ref pre, ref post) => try!(write!(f, " * {} => {} (was {})\n", format(k), format(post), format(pre))),
|
||||||
|
&Diff::Died(_) => try!(write!(f, " X {}\n", format(k))),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +155,7 @@ impl fmt::Display for Existance {
|
|||||||
match self {
|
match self {
|
||||||
&Existance::Born => try!(write!(f, "+++")),
|
&Existance::Born => try!(write!(f, "+++")),
|
||||||
&Existance::Alive => try!(write!(f, "***")),
|
&Existance::Alive => try!(write!(f, "***")),
|
||||||
&Existance::Died => try!(write!(f, "---")),
|
&Existance::Died => try!(write!(f, "XXX")),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -121,21 +208,21 @@ pub fn pod_diff(pre: Option<&PodAccount>, post: Option<&PodAccount>) -> Option<A
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pod_map_diff(pre: &BTreeMap<Address, PodAccount>, post: &BTreeMap<Address, PodAccount>) -> StateDiff {
|
pub fn pod_state_diff(pre: &PodState, post: &PodState) -> StateDiff {
|
||||||
StateDiff(pre.keys().merge(post.keys()).filter_map(|acc| pod_diff(pre.get(acc), post.get(acc)).map(|d|(acc.clone(), d))).collect())
|
StateDiff(pre.0.keys().merge(post.0.keys()).filter_map(|acc| pod_diff(pre.0.get(acc), post.0.get(acc)).map(|d|(acc.clone(), d))).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_diff_create_delete() {
|
fn state_diff_create_delete() {
|
||||||
let a = map![
|
let a = PodState(map![
|
||||||
x!(1) => PodAccount{
|
x!(1) => PodAccount{
|
||||||
balance: x!(69),
|
balance: x!(69),
|
||||||
nonce: x!(0),
|
nonce: x!(0),
|
||||||
code: vec![],
|
code: vec![],
|
||||||
storage: map![]
|
storage: map![]
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
assert_eq!(pod_map_diff(&a, &map![]), StateDiff(map![
|
assert_eq!(pod_state_diff(&a, &PodState(map![])), StateDiff(map![
|
||||||
x!(1) => AccountDiff{
|
x!(1) => AccountDiff{
|
||||||
balance: Diff::Died(x!(69)),
|
balance: Diff::Died(x!(69)),
|
||||||
nonce: Diff::Died(x!(0)),
|
nonce: Diff::Died(x!(0)),
|
||||||
@ -143,7 +230,7 @@ fn state_diff_create_delete() {
|
|||||||
storage: map![],
|
storage: map![],
|
||||||
}
|
}
|
||||||
]));
|
]));
|
||||||
assert_eq!(pod_map_diff(&map![], &a), StateDiff(map![
|
assert_eq!(pod_state_diff(&PodState(map![]), &a), StateDiff(map![
|
||||||
x!(1) => AccountDiff{
|
x!(1) => AccountDiff{
|
||||||
balance: Diff::Born(x!(69)),
|
balance: Diff::Born(x!(69)),
|
||||||
nonce: Diff::Born(x!(0)),
|
nonce: Diff::Born(x!(0)),
|
||||||
@ -155,15 +242,15 @@ fn state_diff_create_delete() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_diff_cretae_delete_with_unchanged() {
|
fn state_diff_cretae_delete_with_unchanged() {
|
||||||
let a = map![
|
let a = PodState(map![
|
||||||
x!(1) => PodAccount{
|
x!(1) => PodAccount{
|
||||||
balance: x!(69),
|
balance: x!(69),
|
||||||
nonce: x!(0),
|
nonce: x!(0),
|
||||||
code: vec![],
|
code: vec![],
|
||||||
storage: map![]
|
storage: map![]
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
let b = map![
|
let b = PodState(map![
|
||||||
x!(1) => PodAccount{
|
x!(1) => PodAccount{
|
||||||
balance: x!(69),
|
balance: x!(69),
|
||||||
nonce: x!(0),
|
nonce: x!(0),
|
||||||
@ -176,8 +263,8 @@ fn state_diff_cretae_delete_with_unchanged() {
|
|||||||
code: vec![],
|
code: vec![],
|
||||||
storage: map![]
|
storage: map![]
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
assert_eq!(pod_map_diff(&a, &b), StateDiff(map![
|
assert_eq!(pod_state_diff(&a, &b), StateDiff(map![
|
||||||
x!(2) => AccountDiff{
|
x!(2) => AccountDiff{
|
||||||
balance: Diff::Born(x!(69)),
|
balance: Diff::Born(x!(69)),
|
||||||
nonce: Diff::Born(x!(0)),
|
nonce: Diff::Born(x!(0)),
|
||||||
@ -185,7 +272,7 @@ fn state_diff_cretae_delete_with_unchanged() {
|
|||||||
storage: map![],
|
storage: map![],
|
||||||
}
|
}
|
||||||
]));
|
]));
|
||||||
assert_eq!(pod_map_diff(&b, &a), StateDiff(map![
|
assert_eq!(pod_state_diff(&b, &a), StateDiff(map![
|
||||||
x!(2) => AccountDiff{
|
x!(2) => AccountDiff{
|
||||||
balance: Diff::Died(x!(69)),
|
balance: Diff::Died(x!(69)),
|
||||||
nonce: Diff::Died(x!(0)),
|
nonce: Diff::Died(x!(0)),
|
||||||
@ -197,7 +284,7 @@ fn state_diff_cretae_delete_with_unchanged() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_diff_change_with_unchanged() {
|
fn state_diff_change_with_unchanged() {
|
||||||
let a = map![
|
let a = PodState(map![
|
||||||
x!(1) => PodAccount{
|
x!(1) => PodAccount{
|
||||||
balance: x!(69),
|
balance: x!(69),
|
||||||
nonce: x!(0),
|
nonce: x!(0),
|
||||||
@ -210,8 +297,8 @@ fn state_diff_change_with_unchanged() {
|
|||||||
code: vec![],
|
code: vec![],
|
||||||
storage: map![]
|
storage: map![]
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
let b = map![
|
let b = PodState(map![
|
||||||
x!(1) => PodAccount{
|
x!(1) => PodAccount{
|
||||||
balance: x!(69),
|
balance: x!(69),
|
||||||
nonce: x!(1),
|
nonce: x!(1),
|
||||||
@ -224,8 +311,8 @@ fn state_diff_change_with_unchanged() {
|
|||||||
code: vec![],
|
code: vec![],
|
||||||
storage: map![]
|
storage: map![]
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
assert_eq!(pod_map_diff(&a, &b), StateDiff(map![
|
assert_eq!(pod_state_diff(&a, &b), StateDiff(map![
|
||||||
x!(1) => AccountDiff{
|
x!(1) => AccountDiff{
|
||||||
balance: Diff::Same,
|
balance: Diff::Same,
|
||||||
nonce: Diff::Changed(x!(0), x!(1)),
|
nonce: Diff::Changed(x!(0), x!(1)),
|
||||||
@ -299,6 +386,12 @@ fn account_diff_storage() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq,Eq,Clone,Copy)]
|
||||||
|
pub enum Filth {
|
||||||
|
Clean,
|
||||||
|
Dirty,
|
||||||
|
}
|
||||||
|
|
||||||
/// Single account in the system.
|
/// Single account in the system.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
@ -308,8 +401,8 @@ pub struct Account {
|
|||||||
nonce: U256,
|
nonce: U256,
|
||||||
// Trie-backed storage.
|
// Trie-backed storage.
|
||||||
storage_root: H256,
|
storage_root: H256,
|
||||||
// Overlay on trie-backed storage.
|
// Overlay on trie-backed storage - tuple is (<clean>, <value>).
|
||||||
storage_overlay: RefCell<HashMap<H256, H256>>,
|
storage_overlay: RefCell<HashMap<H256, (Filth, H256)>>,
|
||||||
// Code hash of the account. If None, means that it's a contract whose code has not yet been set.
|
// Code hash of the account. If None, means that it's a contract whose code has not yet been set.
|
||||||
code_hash: Option<H256>,
|
code_hash: Option<H256>,
|
||||||
// Code cache of the account.
|
// Code cache of the account.
|
||||||
@ -323,7 +416,7 @@ impl PodAccount {
|
|||||||
PodAccount {
|
PodAccount {
|
||||||
balance: acc.balance.clone(),
|
balance: acc.balance.clone(),
|
||||||
nonce: acc.nonce.clone(),
|
nonce: acc.nonce.clone(),
|
||||||
storage: acc.storage_overlay.borrow().iter().fold(BTreeMap::new(), |mut m, (k, v)| {m.insert(k.clone(), v.clone()); m}),
|
storage: acc.storage_overlay.borrow().iter().fold(BTreeMap::new(), |mut m, (k, &(_, ref v))| {m.insert(k.clone(), v.clone()); m}),
|
||||||
code: acc.code_cache.clone()
|
code: acc.code_cache.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -346,7 +439,7 @@ impl Account {
|
|||||||
balance: balance,
|
balance: balance,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
storage_root: SHA3_NULL_RLP,
|
storage_root: SHA3_NULL_RLP,
|
||||||
storage_overlay: RefCell::new(storage),
|
storage_overlay: RefCell::new(storage.into_iter().map(|(k, v)| (k, (Filth::Dirty, v))).collect()),
|
||||||
code_hash: Some(code.sha3()),
|
code_hash: Some(code.sha3()),
|
||||||
code_cache: code
|
code_cache: code
|
||||||
}
|
}
|
||||||
@ -358,7 +451,7 @@ impl Account {
|
|||||||
balance: pod.balance,
|
balance: pod.balance,
|
||||||
nonce: pod.nonce,
|
nonce: pod.nonce,
|
||||||
storage_root: SHA3_NULL_RLP,
|
storage_root: SHA3_NULL_RLP,
|
||||||
storage_overlay: RefCell::new(pod.storage.into_iter().fold(HashMap::new(), |mut m, (k, v)| {m.insert(k, v); m})),
|
storage_overlay: RefCell::new(pod.storage.into_iter().map(|(k, v)| (k, (Filth::Dirty, v))).collect()),
|
||||||
code_hash: Some(pod.code.sha3()),
|
code_hash: Some(pod.code.sha3()),
|
||||||
code_cache: pod.code
|
code_cache: pod.code
|
||||||
}
|
}
|
||||||
@ -418,14 +511,14 @@ impl Account {
|
|||||||
|
|
||||||
/// Set (and cache) the contents of the trie's storage at `key` to `value`.
|
/// Set (and cache) the contents of the trie's storage at `key` to `value`.
|
||||||
pub fn set_storage(&mut self, key: H256, value: H256) {
|
pub fn set_storage(&mut self, key: H256, value: H256) {
|
||||||
self.storage_overlay.borrow_mut().insert(key, value);
|
self.storage_overlay.borrow_mut().insert(key, (Filth::Dirty, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get (and cache) the contents of the trie's storage at `key`.
|
/// Get (and cache) the contents of the trie's storage at `key`.
|
||||||
pub fn storage_at(&self, db: &HashDB, key: &H256) -> H256 {
|
pub fn storage_at(&self, db: &HashDB, key: &H256) -> H256 {
|
||||||
self.storage_overlay.borrow_mut().entry(key.clone()).or_insert_with(||{
|
self.storage_overlay.borrow_mut().entry(key.clone()).or_insert_with(||{
|
||||||
H256::from_slice(TrieDB::new(db, &self.storage_root).get(key.bytes()).unwrap_or(&[0u8;32][..]))
|
(Filth::Clean, H256::from(SecTrieDB::new(db, &self.storage_root).get(key.bytes()).map(|v| -> U256 {decode(v)}).unwrap_or(U256::zero())))
|
||||||
}).clone()
|
}).1.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return the balance associated with this account.
|
/// return the balance associated with this account.
|
||||||
@ -487,11 +580,17 @@ impl Account {
|
|||||||
/// return the storage root associated with this account.
|
/// return the storage root associated with this account.
|
||||||
pub fn base_root(&self) -> &H256 { &self.storage_root }
|
pub fn base_root(&self) -> &H256 { &self.storage_root }
|
||||||
|
|
||||||
/// return the storage root associated with this account or None if it has been altered via the overlay.
|
/// Determine whether there are any un-`commit()`-ed storage-setting operations.
|
||||||
pub fn storage_root(&self) -> Option<&H256> { if self.storage_overlay.borrow().is_empty() {Some(&self.storage_root)} else {None} }
|
pub fn storage_is_clean(&self) -> bool { self.storage_overlay.borrow().iter().find(|&(_, &(f, _))| f == Filth::Dirty).is_none() }
|
||||||
|
|
||||||
/// rturn the storage overlay.
|
/// return the storage root associated with this account or None if it has been altered via the overlay.
|
||||||
pub fn storage_overlay(&self) -> Ref<HashMap<H256, H256>> { self.storage_overlay.borrow() }
|
pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} }
|
||||||
|
|
||||||
|
/// return the storage root associated with this account or None if it has been altered via the overlay.
|
||||||
|
pub fn recent_storage_root(&self) -> &H256 { &self.storage_root }
|
||||||
|
|
||||||
|
/// return the storage overlay.
|
||||||
|
pub fn storage_overlay(&self) -> Ref<HashMap<H256, (Filth, H256)>> { self.storage_overlay.borrow() }
|
||||||
|
|
||||||
/// Increment the nonce of the account by one.
|
/// Increment the nonce of the account by one.
|
||||||
pub fn inc_nonce(&mut self) { self.nonce = self.nonce + U256::from(1u8); }
|
pub fn inc_nonce(&mut self) { self.nonce = self.nonce + U256::from(1u8); }
|
||||||
@ -504,13 +603,15 @@ impl Account {
|
|||||||
|
|
||||||
/// Commit the `storage_overlay` to the backing DB and update `storage_root`.
|
/// Commit the `storage_overlay` to the backing DB and update `storage_root`.
|
||||||
pub fn commit_storage(&mut self, db: &mut HashDB) {
|
pub fn commit_storage(&mut self, db: &mut HashDB) {
|
||||||
let mut t = TrieDBMut::new(db, &mut self.storage_root);
|
let mut t = SecTrieDBMut::new(db, &mut self.storage_root);
|
||||||
for (k, v) in self.storage_overlay.borrow().iter() {
|
for (k, &mut (ref mut f, ref mut v)) in self.storage_overlay.borrow_mut().iter_mut() {
|
||||||
// cast key and value to trait type,
|
if f == &Filth::Dirty {
|
||||||
// so we can call overloaded `to_bytes` method
|
// cast key and value to trait type,
|
||||||
t.insert(k, v);
|
// so we can call overloaded `to_bytes` method
|
||||||
|
t.insert(k, &encode(&U256::from(v.as_slice())));
|
||||||
|
*f = Filth::Clean;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.storage_overlay.borrow_mut().clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -567,7 +668,7 @@ fn storage_at() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let a = Account::from_rlp(&rlp);
|
let a = Account::from_rlp(&rlp);
|
||||||
assert_eq!(a.storage_root().unwrap().hex(), "3541f181d6dad5c504371884684d08c29a8bad04926f8ceddf5e279dbc3cc769");
|
assert_eq!(a.storage_root().unwrap().hex(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2");
|
||||||
assert_eq!(a.storage_at(&mut db, &H256::from(&U256::from(0x00u64))), H256::from(&U256::from(0x1234u64)));
|
assert_eq!(a.storage_at(&mut db, &H256::from(&U256::from(0x00u64))), H256::from(&U256::from(0x1234u64)));
|
||||||
assert_eq!(a.storage_at(&mut db, &H256::from(&U256::from(0x01u64))), H256::new());
|
assert_eq!(a.storage_at(&mut db, &H256::from(&U256::from(0x01u64))), H256::new());
|
||||||
}
|
}
|
||||||
@ -597,7 +698,7 @@ fn commit_storage() {
|
|||||||
a.set_storage(H256::from(&U256::from(0x00u64)), H256::from(&U256::from(0x1234u64)));
|
a.set_storage(H256::from(&U256::from(0x00u64)), H256::from(&U256::from(0x1234u64)));
|
||||||
assert_eq!(a.storage_root(), None);
|
assert_eq!(a.storage_root(), None);
|
||||||
a.commit_storage(&mut db);
|
a.commit_storage(&mut db);
|
||||||
assert_eq!(a.storage_root().unwrap().hex(), "3541f181d6dad5c504371884684d08c29a8bad04926f8ceddf5e279dbc3cc769");
|
assert_eq!(a.storage_root().unwrap().hex(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -30,22 +30,23 @@ impl EnvInfo {
|
|||||||
number: 0,
|
number: 0,
|
||||||
author: Address::new(),
|
author: Address::new(),
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
difficulty: U256::zero(),
|
difficulty: x!(0),
|
||||||
gas_limit: U256::zero(),
|
gas_limit: x!(0),
|
||||||
last_hashes: vec![],
|
last_hashes: vec![],
|
||||||
gas_used: U256::zero()
|
gas_used: x!(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_json(json: &Json) -> EnvInfo {
|
pub fn from_json(json: &Json) -> EnvInfo {
|
||||||
|
let current_number = u64_from_json(&json["currentNumber"]);
|
||||||
EnvInfo {
|
EnvInfo {
|
||||||
number: u64_from_json(&json["currentNumber"]),
|
number: current_number,
|
||||||
author: address_from_json(&json["currentCoinbase"]),
|
author: address_from_json(&json["currentCoinbase"]),
|
||||||
difficulty: u256_from_json(&json["currentDifficulty"]),
|
difficulty: u256_from_json(&json["currentDifficulty"]),
|
||||||
gas_limit: u256_from_json(&json["currentGasLimit"]),
|
gas_limit: u256_from_json(&json["currentGasLimit"]),
|
||||||
timestamp: u64_from_json(&json["currentTimestamp"]),
|
timestamp: u64_from_json(&json["currentTimestamp"]),
|
||||||
last_hashes: vec![h256_from_json(&json["previousHash"])],
|
last_hashes: (1..257).map(|i| format!("{}", current_number - i).as_bytes().sha3()).collect(),
|
||||||
gas_used: U256::zero(),
|
gas_used: x!(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ trait Memory {
|
|||||||
/// Resize (shrink or expand) the memory to specified size (fills 0)
|
/// Resize (shrink or expand) the memory to specified size (fills 0)
|
||||||
fn resize(&mut self, new_size: usize);
|
fn resize(&mut self, new_size: usize);
|
||||||
/// Resize the memory only if its smaller
|
/// Resize the memory only if its smaller
|
||||||
fn expand(&mut self, new_size: U256);
|
fn expand(&mut self, new_size: usize);
|
||||||
/// Write single byte to memory
|
/// Write single byte to memory
|
||||||
fn write_byte(&mut self, offset: U256, value: U256);
|
fn write_byte(&mut self, offset: U256, value: U256);
|
||||||
/// Write a word to memory. Does not resize memory!
|
/// Write a word to memory. Does not resize memory!
|
||||||
@ -141,8 +141,7 @@ impl Memory for Vec<u8> {
|
|||||||
self.resize(new_size, 0);
|
self.resize(new_size, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand(&mut self, new_size: U256) {
|
fn expand(&mut self, size: usize) {
|
||||||
let size = new_size.low_u64() as usize;
|
|
||||||
if size > self.len() {
|
if size > self.len() {
|
||||||
Memory::resize(self, size)
|
Memory::resize(self, size)
|
||||||
}
|
}
|
||||||
@ -203,7 +202,7 @@ impl evm::Evm for Interpreter {
|
|||||||
try!(self.verify_gas(¤t_gas, &gas_cost));
|
try!(self.verify_gas(¤t_gas, &gas_cost));
|
||||||
current_gas = current_gas - gas_cost;
|
current_gas = current_gas - gas_cost;
|
||||||
println!("Gas cost: {} (left: {})", gas_cost, current_gas);
|
println!("Gas cost: {} (left: {})", gas_cost, current_gas);
|
||||||
println!("Executing: {} ", instructions::get_info(instruction).name);
|
println!("Executing: {} ({:x})", instructions::get_info(instruction).name, instruction);
|
||||||
// Execute instruction
|
// Execute instruction
|
||||||
let result = try!(self.exec_instruction(
|
let result = try!(self.exec_instruction(
|
||||||
current_gas, params, ext, instruction, &mut reader, &mut mem, &mut stack
|
current_gas, params, ext, instruction, &mut reader, &mut mem, &mut stack
|
||||||
@ -346,36 +345,37 @@ impl Interpreter {
|
|||||||
Ok(gas)
|
Ok(gas)
|
||||||
},
|
},
|
||||||
InstructionCost::GasMem(gas, mem_size) => {
|
InstructionCost::GasMem(gas, mem_size) => {
|
||||||
let mem_gas = self.mem_gas_cost(schedule, mem.size(), &mem_size);
|
let (mem_gas, new_mem_size) = self.mem_gas_cost(schedule, mem.size(), &mem_size);
|
||||||
// Expand after calculating the cost
|
// Expand after calculating the cost
|
||||||
mem.expand(mem_size);
|
mem.expand(new_mem_size);
|
||||||
Ok(gas + mem_gas)
|
Ok(gas + mem_gas)
|
||||||
},
|
},
|
||||||
InstructionCost::GasMemCopy(gas, mem_size, copy) => {
|
InstructionCost::GasMemCopy(gas, mem_size, copy) => {
|
||||||
let mem_gas = self.mem_gas_cost(schedule, mem.size(), &mem_size);
|
let (mem_gas, new_mem_size) = self.mem_gas_cost(schedule, mem.size(), &mem_size);
|
||||||
let copy_gas = U256::from(schedule.copy_gas) * (add_u256_usize(©, 31) / U256::from(32));
|
let copy_gas = U256::from(schedule.copy_gas) * (add_u256_usize(©, 31) / U256::from(32));
|
||||||
// Expand after calculating the cost
|
// Expand after calculating the cost
|
||||||
mem.expand(mem_size);
|
mem.expand(new_mem_size);
|
||||||
Ok(gas + copy_gas + mem_gas)
|
Ok(gas + copy_gas + mem_gas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &U256) -> U256 {
|
fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &U256) -> (U256, usize) {
|
||||||
let gas_for_mem = |mem_size: usize| {
|
let gas_for_mem = |mem_size: usize| {
|
||||||
let s = mem_size / 32;
|
let s = mem_size / 32;
|
||||||
schedule.memory_gas * s + s * s / schedule.quad_coeff_div
|
schedule.memory_gas * s + s * s / schedule.quad_coeff_div
|
||||||
};
|
};
|
||||||
|
|
||||||
let mem_size_rounded = add_u256_usize(mem_size, 31).low_u64() as usize / 32 * 32;
|
let req_mem_size = mem_size.low_u64() as usize;
|
||||||
let mem_gas = gas_for_mem(mem_size_rounded);
|
let req_mem_size_rounded = (req_mem_size + 31) / 32 * 32;
|
||||||
|
let new_mem_gas = gas_for_mem(req_mem_size_rounded);
|
||||||
let current_mem_gas = gas_for_mem(current_mem_size);
|
let current_mem_gas = gas_for_mem(current_mem_size);
|
||||||
|
|
||||||
U256::from(if mem_gas > current_mem_gas {
|
(if req_mem_size_rounded > current_mem_size {
|
||||||
mem_gas - current_mem_gas
|
U256::from(new_mem_gas - current_mem_gas)
|
||||||
} else {
|
} else {
|
||||||
0
|
U256::zero()
|
||||||
})
|
}, req_mem_size_rounded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -582,9 +582,6 @@ impl Interpreter {
|
|||||||
instructions::EXTCODECOPY => {
|
instructions::EXTCODECOPY => {
|
||||||
let address = u256_to_address(&stack.pop_back());
|
let address = u256_to_address(&stack.pop_back());
|
||||||
let code = ext.extcode(&address);
|
let code = ext.extcode(&address);
|
||||||
for b in &code {
|
|
||||||
println!("Code: {:x}", b);
|
|
||||||
}
|
|
||||||
self.copy_data_to_memory(mem, stack, &code);
|
self.copy_data_to_memory(mem, stack, &code);
|
||||||
},
|
},
|
||||||
instructions::GASPRICE => {
|
instructions::GASPRICE => {
|
||||||
@ -820,6 +817,7 @@ fn address_to_u256(value: Address) -> U256 {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use common::*;
|
use common::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use evm;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_find_jump_destinations() {
|
fn test_find_jump_destinations() {
|
||||||
@ -834,6 +832,22 @@ mod tests {
|
|||||||
assert!(valid_jump_destinations.contains(&66));
|
assert!(valid_jump_destinations.contains(&66));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_calculate_mem_cost() {
|
||||||
|
// given
|
||||||
|
let interpreter = Interpreter;
|
||||||
|
let schedule = evm::Schedule::default();
|
||||||
|
let current_mem_size = 0;
|
||||||
|
let mem_size = U256::from(5);
|
||||||
|
|
||||||
|
// when
|
||||||
|
let (mem_cost, mem_size) = interpreter.mem_gas_cost(&schedule, current_mem_size, &mem_size);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(mem_cost, U256::from(3));
|
||||||
|
assert_eq!(mem_size, 32);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_memory_read_and_write() {
|
fn test_memory_read_and_write() {
|
||||||
// given
|
// given
|
||||||
|
@ -106,6 +106,8 @@ impl<'a> Executive<'a> {
|
|||||||
let sender = try!(t.sender());
|
let sender = try!(t.sender());
|
||||||
let nonce = self.state.nonce(&sender);
|
let nonce = self.state.nonce(&sender);
|
||||||
|
|
||||||
|
// TODO: error on base gas required
|
||||||
|
|
||||||
// validate transaction nonce
|
// validate transaction nonce
|
||||||
if t.nonce != nonce {
|
if t.nonce != nonce {
|
||||||
return Err(From::from(ExecutionError::InvalidNonce { expected: nonce, is: t.nonce }));
|
return Err(From::from(ExecutionError::InvalidNonce { expected: nonce, is: t.nonce }));
|
||||||
@ -137,13 +139,16 @@ impl<'a> Executive<'a> {
|
|||||||
let mut substate = Substate::new();
|
let mut substate = Substate::new();
|
||||||
let backup = self.state.clone();
|
let backup = self.state.clone();
|
||||||
|
|
||||||
|
let schedule = self.engine.schedule(self.info);
|
||||||
|
let init_gas = t.gas - U256::from(t.gas_required(&schedule));
|
||||||
|
|
||||||
let res = match t.action() {
|
let res = match t.action() {
|
||||||
&Action::Create => {
|
&Action::Create => {
|
||||||
let params = ActionParams {
|
let params = ActionParams {
|
||||||
address: contract_address(&sender, &nonce),
|
address: contract_address(&sender, &nonce),
|
||||||
sender: sender.clone(),
|
sender: sender.clone(),
|
||||||
origin: sender.clone(),
|
origin: sender.clone(),
|
||||||
gas: t.gas,
|
gas: init_gas,
|
||||||
gas_price: t.gas_price,
|
gas_price: t.gas_price,
|
||||||
value: t.value,
|
value: t.value,
|
||||||
code: t.data.clone(),
|
code: t.data.clone(),
|
||||||
@ -156,7 +161,7 @@ impl<'a> Executive<'a> {
|
|||||||
address: address.clone(),
|
address: address.clone(),
|
||||||
sender: sender.clone(),
|
sender: sender.clone(),
|
||||||
origin: sender.clone(),
|
origin: sender.clone(),
|
||||||
gas: t.gas,
|
gas: init_gas,
|
||||||
gas_price: t.gas_price,
|
gas_price: t.gas_price,
|
||||||
value: t.value,
|
value: t.value,
|
||||||
code: self.state.code(address).unwrap_or(vec![]),
|
code: self.state.code(address).unwrap_or(vec![]),
|
||||||
@ -421,7 +426,7 @@ impl<'a> Ext for Externalities<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut ex = Executive::from_parent(self.state, self.info, self.engine, self.factory, self.depth);
|
let mut ex = Executive::from_parent(self.state, self.info, self.engine, self.factory, self.depth);
|
||||||
ex.call(¶ms, self.substate, BytesRef::Fixed(output))
|
ex.call(¶ms, self.substate, BytesRef::Fixed(output)).map(|gas_left| gas + gas_left)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extcode(&self, address: &Address) -> Vec<u8> {
|
fn extcode(&self, address: &Address) -> Vec<u8> {
|
||||||
|
@ -2,7 +2,7 @@ use util::*;
|
|||||||
use basic_types::LogBloom;
|
use basic_types::LogBloom;
|
||||||
|
|
||||||
/// A single log's entry.
|
/// A single log's entry.
|
||||||
#[derive(Debug)]
|
#[derive(Debug,PartialEq,Eq)]
|
||||||
pub struct LogEntry {
|
pub struct LogEntry {
|
||||||
pub address: Address,
|
pub address: Address,
|
||||||
pub topics: Vec<H256>,
|
pub topics: Vec<H256>,
|
||||||
@ -19,10 +19,6 @@ impl RlpStandard for LogEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LogEntry {
|
impl LogEntry {
|
||||||
pub fn bloom(&self) -> LogBloom {
|
|
||||||
self.topics.iter().fold(LogBloom::from_bloomed(&self.address.sha3()), |b, t| b.with_bloomed(&t.sha3()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new log entry.
|
/// Create a new log entry.
|
||||||
pub fn new(address: Address, topics: Vec<H256>, data: Bytes) -> LogEntry {
|
pub fn new(address: Address, topics: Vec<H256>, data: Bytes) -> LogEntry {
|
||||||
LogEntry {
|
LogEntry {
|
||||||
@ -32,6 +28,16 @@ impl LogEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert given JSON object to a LogEntry.
|
||||||
|
pub fn from_json(json: &Json) -> LogEntry {
|
||||||
|
// TODO: check bloom.
|
||||||
|
LogEntry {
|
||||||
|
address: address_from_json(&json["address"]),
|
||||||
|
topics: vec_h256_from_json(&json["topics"]),
|
||||||
|
data: bytes_from_json(&json["data"]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns reference to address.
|
/// Returns reference to address.
|
||||||
pub fn address(&self) -> &Address {
|
pub fn address(&self) -> &Address {
|
||||||
&self.address
|
&self.address
|
||||||
@ -46,6 +52,11 @@ impl LogEntry {
|
|||||||
pub fn data(&self) -> &Bytes {
|
pub fn data(&self) -> &Bytes {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates the bloom of this log entry.
|
||||||
|
pub fn bloom(&self) -> LogBloom {
|
||||||
|
self.topics.iter().fold(LogBloom::from_bloomed(&self.address.sha3()), |b, t| b.with_bloomed(&t.sha3()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
10
src/state.rs
10
src/state.rs
@ -183,8 +183,8 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Populate the state from `accounts`.
|
/// Populate the state from `accounts`.
|
||||||
pub fn populate_from(&mut self, accounts: BTreeMap<Address, PodAccount>) {
|
pub fn populate_from(&mut self, accounts: PodState) {
|
||||||
for (add, acc) in accounts.into_iter() {
|
for (add, acc) in accounts.drain().into_iter() {
|
||||||
self.cache.borrow_mut().insert(add, Some(Account::from_pod(acc)));
|
self.cache.borrow_mut().insert(add, Some(Account::from_pod(acc)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,14 +201,14 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Populate a PodAccount map from this state.
|
/// Populate a PodAccount map from this state.
|
||||||
pub fn to_pod_map(&self) -> BTreeMap<Address, PodAccount> {
|
pub fn to_pod(&self) -> PodState {
|
||||||
// TODO: handle database rather than just the cache.
|
// TODO: handle database rather than just the cache.
|
||||||
self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| {
|
PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| {
|
||||||
if let &Some(ref acc) = opt {
|
if let &Some(ref acc) = opt {
|
||||||
m.insert(add.clone(), PodAccount::from_account(acc));
|
m.insert(add.clone(), PodAccount::from_account(acc));
|
||||||
}
|
}
|
||||||
m
|
m
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pull account `a` in our cache from the trie DB and return it.
|
/// Pull account `a` in our cache from the trie DB and return it.
|
||||||
|
@ -1,33 +1,8 @@
|
|||||||
use super::test_common::*;
|
use super::test_common::*;
|
||||||
use state::*;
|
use state::*;
|
||||||
|
use executive::*;
|
||||||
use ethereum;
|
use ethereum;
|
||||||
|
|
||||||
pub fn map_h256_h256_from_json(json: &Json) -> BTreeMap<H256, H256> {
|
|
||||||
json.as_object().unwrap().iter().fold(BTreeMap::new(), |mut m, (key, value)| {
|
|
||||||
m.insert(H256::from(&u256_from_str(key)), H256::from(&u256_from_json(value)));
|
|
||||||
m
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Translate the JSON object into a hash map of account information ready for insertion into State.
|
|
||||||
pub fn pod_map_from_json(json: &Json) -> BTreeMap<Address, PodAccount> {
|
|
||||||
json.as_object().unwrap().iter().fold(BTreeMap::new(), |mut state, (address, acc)| {
|
|
||||||
let balance = acc.find("balance").map(&u256_from_json);
|
|
||||||
let nonce = acc.find("nonce").map(&u256_from_json);
|
|
||||||
let storage = acc.find("storage").map(&map_h256_h256_from_json);;
|
|
||||||
let code = acc.find("code").map(&bytes_from_json);
|
|
||||||
if balance.is_some() || nonce.is_some() || storage.is_some() || code.is_some() {
|
|
||||||
state.insert(address_from_hex(address), PodAccount{
|
|
||||||
balance: balance.unwrap_or(U256::zero()),
|
|
||||||
nonce: nonce.unwrap_or(U256::zero()),
|
|
||||||
storage: storage.unwrap_or(BTreeMap::new()),
|
|
||||||
code: code.unwrap_or(Vec::new())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
||||||
let json = Json::from_str(::std::str::from_utf8(json_data).unwrap()).expect("Json is invalid");
|
let json = Json::from_str(::std::str::from_utf8(json_data).unwrap()).expect("Json is invalid");
|
||||||
let mut failed = Vec::new();
|
let mut failed = Vec::new();
|
||||||
@ -42,29 +17,44 @@ fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
|||||||
let env = EnvInfo::from_json(&test["env"]);
|
let env = EnvInfo::from_json(&test["env"]);
|
||||||
let _out = bytes_from_json(&test["out"]);
|
let _out = bytes_from_json(&test["out"]);
|
||||||
let post_state_root = h256_from_json(&test["postStateRoot"]);
|
let post_state_root = h256_from_json(&test["postStateRoot"]);
|
||||||
let pre = pod_map_from_json(&test["pre"]);
|
let pre = PodState::from_json(&test["pre"]);
|
||||||
let post = pod_map_from_json(&test["post"]);
|
let post = PodState::from_json(&test["post"]);
|
||||||
// TODO: read test["logs"]
|
let logs: Vec<_> = test["logs"].as_array().unwrap().iter().map(&LogEntry::from_json).collect();
|
||||||
|
|
||||||
println!("Transaction: {:?}", t);
|
//println!("Transaction: {:?}", t);
|
||||||
println!("Env: {:?}", env);
|
//println!("Env: {:?}", env);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut s = State::new_temp();
|
||||||
|
s.populate_from(post.clone());
|
||||||
|
s.commit();
|
||||||
|
assert_eq!(&post_state_root, s.root());
|
||||||
|
}
|
||||||
|
|
||||||
let mut s = State::new_temp();
|
let mut s = State::new_temp();
|
||||||
s.populate_from(pre);
|
s.populate_from(pre);
|
||||||
|
let r = s.apply(&env, engine.deref(), &t).unwrap();
|
||||||
|
|
||||||
s.apply(&env, engine.deref(), &t).unwrap();
|
if fail_unless(&r.state_root == &post_state_root) {
|
||||||
let our_post = s.to_pod_map();
|
println!("!!! {}: State mismatch (got: {}, expect: {}):", name, r.state_root, post_state_root);
|
||||||
|
let our_post = s.to_pod();
|
||||||
if fail_unless(s.root() == &post_state_root) {
|
println!("Got:\n{}", our_post);
|
||||||
println!("DIFF:\n{}", pod_map_diff(&post, &our_post));
|
println!("Expect:\n{}", post);
|
||||||
|
println!("Diff ---expect -> +++got:\n{}", pod_state_diff(&post, &our_post));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Compare logs.
|
if fail_unless(logs == r.logs) {
|
||||||
}
|
println!("!!! {}: Logs mismatch:", name);
|
||||||
for f in failed.iter() {
|
println!("Got:\n{:?}", r.logs);
|
||||||
println!("FAILED: {:?}", f);
|
println!("Expect:\n{:?}", logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add extra APIs for output
|
||||||
|
//if fail_unless(out == r.)
|
||||||
}
|
}
|
||||||
|
println!("!!! {:?} tests from failed.", failed.len());
|
||||||
failed
|
failed
|
||||||
}
|
}
|
||||||
|
|
||||||
declare_test!{StateTests_stExample, "StateTests/stExample"}
|
declare_test!{StateTests_stExample, "StateTests/stExample"}
|
||||||
|
declare_test!{StateTests_stLogTests, "StateTests/stLogTests"}
|
Loading…
Reference in New Issue
Block a user