State testing framework. First test is failing.
This commit is contained in:
parent
f46f742ec9
commit
9d2ac7fc37
@ -2,8 +2,31 @@ 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] );
|
||||||
|
|
||||||
/// Single account in the system.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// Genesis account data. Does no thave a DB overlay cache
|
||||||
|
pub struct PodAccount {
|
||||||
|
// Balance of the account.
|
||||||
|
pub balance: U256,
|
||||||
|
// Nonce of the account.
|
||||||
|
pub nonce: U256,
|
||||||
|
pub code: Bytes,
|
||||||
|
pub storage: HashMap<H256, H256>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PodAccount {
|
||||||
|
pub fn rlp(&self) -> Bytes {
|
||||||
|
let mut stream = RlpStream::new_list(4);
|
||||||
|
stream.append(&self.nonce);
|
||||||
|
stream.append(&self.balance);
|
||||||
|
// TODO.
|
||||||
|
stream.append(&SHA3_NULL_RLP);
|
||||||
|
stream.append(&self.code.sha3());
|
||||||
|
stream.out()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Single account in the system.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
// Balance of the account.
|
// Balance of the account.
|
||||||
balance: U256,
|
balance: U256,
|
||||||
@ -32,6 +55,18 @@ impl Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// General constructor.
|
||||||
|
pub fn from_pod(pod: PodAccount) -> Account {
|
||||||
|
Account {
|
||||||
|
balance: pod.balance,
|
||||||
|
nonce: pod.nonce,
|
||||||
|
storage_root: SHA3_NULL_RLP,
|
||||||
|
storage_overlay: RefCell::new(pod.storage),
|
||||||
|
code_hash: Some(pod.code.sha3()),
|
||||||
|
code_cache: pod.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new account with the given balance.
|
/// Create a new account with the given balance.
|
||||||
pub fn new_basic(balance: U256, nonce: U256) -> Account {
|
pub fn new_basic(balance: U256, nonce: U256) -> Account {
|
||||||
Account {
|
Account {
|
||||||
@ -205,6 +240,12 @@ impl Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Account {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", PodAccount{balance: self.balance.clone(), nonce: self.nonce.clone(), storage: self.storage_overlay.borrow().clone(), code: self.code_cache.clone()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ use header::BlockNumber;
|
|||||||
pub type LastHashes = Vec<H256>;
|
pub type LastHashes = Vec<H256>;
|
||||||
|
|
||||||
/// Information concerning the execution environment for a message-call/contract-creation.
|
/// Information concerning the execution environment for a message-call/contract-creation.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct EnvInfo {
|
pub struct EnvInfo {
|
||||||
/// The block number.
|
/// The block number.
|
||||||
pub number: BlockNumber,
|
pub number: BlockNumber,
|
||||||
@ -35,6 +36,18 @@ impl EnvInfo {
|
|||||||
gas_used: U256::zero()
|
gas_used: U256::zero()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_json(json: &Json) -> EnvInfo {
|
||||||
|
EnvInfo {
|
||||||
|
number: u64_from_json(&json["currentNumber"]),
|
||||||
|
author: address_from_json(&json["currentCoinbase"]),
|
||||||
|
difficulty: u256_from_json(&json["currentDifficulty"]),
|
||||||
|
gas_limit: u256_from_json(&json["currentGasLimit"]),
|
||||||
|
timestamp: u64_from_json(&json["currentTimestamp"]),
|
||||||
|
last_hashes: vec![h256_from_json(&json["previousHash"])],
|
||||||
|
gas_used: U256::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO: it should be the other way around.
|
/// TODO: it should be the other way around.
|
||||||
|
15
src/state.rs
15
src/state.rs
@ -5,6 +5,7 @@ use executive::Executive;
|
|||||||
pub type ApplyResult = Result<Receipt, Error>;
|
pub type ApplyResult = Result<Receipt, Error>;
|
||||||
|
|
||||||
/// Representation of the entire state of all accounts in the system.
|
/// Representation of the entire state of all accounts in the system.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
db: OverlayDB,
|
db: OverlayDB,
|
||||||
root: H256,
|
root: H256,
|
||||||
@ -179,9 +180,11 @@ impl State {
|
|||||||
self.root = Self::commit_into(&mut self.db, r, self.cache.borrow_mut().deref_mut());
|
self.root = Self::commit_into(&mut self.db, r, self.cache.borrow_mut().deref_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Populate the state from `accounts`. Just uses `commit_into`.
|
/// Populate the state from `accounts`.
|
||||||
pub fn populate_from(&mut self, _accounts: &mut HashMap<Address, Option<Account>>) {
|
pub fn populate_from(&mut self, accounts: HashMap<Address, PodAccount>) {
|
||||||
unimplemented!();
|
for (add, acc) in accounts.into_iter() {
|
||||||
|
self.cache.borrow_mut().insert(add, Some(Account::from_pod(acc)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -224,9 +227,9 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for State {
|
impl fmt::Debug for State {
|
||||||
fn clone(&self) -> Self {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
State::from_existing(self.db.clone(), self.root.clone(), self.account_start_nonce.clone())
|
write!(f, "{:?}", self.cache.borrow())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,3 +2,4 @@
|
|||||||
mod test_common;
|
mod test_common;
|
||||||
|
|
||||||
mod transaction;
|
mod transaction;
|
||||||
|
mod state;
|
74
src/tests/state.rs
Normal file
74
src/tests/state.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use super::test_common::*;
|
||||||
|
use state::*;
|
||||||
|
use ethereum;
|
||||||
|
|
||||||
|
pub fn hashmap_h256_h256_from_json(json: &Json) -> HashMap<H256, H256> {
|
||||||
|
json.as_object().unwrap().iter().fold(HashMap::new(), |mut m, (key, value)| {
|
||||||
|
m.insert(H256::from(&u256_from_hex(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_account_map_from_json(json: &Json) -> HashMap<Address, PodAccount> {
|
||||||
|
json.as_object().unwrap().iter().fold(HashMap::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: Option<HashMap<H256, H256>> = acc.find("storage").map(&hashmap_h256_h256_from_json);;
|
||||||
|
let code = acc.find("code").map(&bytes_from_json);
|
||||||
|
if balance.is_some() || nonce.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(HashMap::new()),
|
||||||
|
code: code.unwrap_or(Vec::new())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 mut failed = Vec::new();
|
||||||
|
|
||||||
|
let engine = ethereum::new_frontier_test().to_engine().unwrap();
|
||||||
|
|
||||||
|
for (name, test) in json.as_object().unwrap() {
|
||||||
|
let mut fail = false;
|
||||||
|
let mut fail_unless = |cond: bool| if !cond && !fail { failed.push(name.to_string()); fail = true; true } else {false};
|
||||||
|
|
||||||
|
let t = Transaction::from_json(&test["transaction"]);
|
||||||
|
let env = EnvInfo::from_json(&test["env"]);
|
||||||
|
let out = bytes_from_json(&test["out"]);
|
||||||
|
let post_state_root = h256_from_json(&test["postStateRoot"]);
|
||||||
|
let pre = pod_account_map_from_json(&test["pre"]);
|
||||||
|
let post = pod_account_map_from_json(&test["post"]);
|
||||||
|
// TODO: read test["logs"]
|
||||||
|
|
||||||
|
println!("Transaction: {:?}", t);
|
||||||
|
println!("Env: {:?}", env);
|
||||||
|
println!("Out: {:?}", out);
|
||||||
|
println!("Pre: {:?}", pre);
|
||||||
|
println!("Post: {:?}", post);
|
||||||
|
|
||||||
|
let mut s = State::new_temp();
|
||||||
|
s.populate_from(pre);
|
||||||
|
s.apply(&env, engine.deref(), &t).unwrap();
|
||||||
|
|
||||||
|
println!("{:?} ? {:?}", s.root(), post_state_root);
|
||||||
|
|
||||||
|
if fail_unless(s.root() == &post_state_root) {
|
||||||
|
println!("EXPECTED:\n{:?}", post);
|
||||||
|
println!("GOT:\n{:?}", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Compare logs.
|
||||||
|
}
|
||||||
|
for f in failed.iter() {
|
||||||
|
println!("FAILED: {:?}", f);
|
||||||
|
}
|
||||||
|
failed
|
||||||
|
}
|
||||||
|
|
||||||
|
declare_test!{StateTests_stExample, "StateTests/stExample"}
|
@ -1,43 +1,5 @@
|
|||||||
pub use common::*;
|
pub use common::*;
|
||||||
|
|
||||||
pub fn clean(s: &str) -> &str {
|
|
||||||
if s.len() >= 2 && &s[0..2] == "0x" {
|
|
||||||
&s[2..]
|
|
||||||
} else {
|
|
||||||
s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bytes_from_json(json: &Json) -> Bytes {
|
|
||||||
let s = json.as_string().unwrap();
|
|
||||||
if s.len() % 2 == 1 {
|
|
||||||
FromHex::from_hex(&("0".to_string() + &(clean(s).to_string()))[..]).unwrap_or(vec![])
|
|
||||||
} else {
|
|
||||||
FromHex::from_hex(clean(s)).unwrap_or(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn address_from_json(json: &Json) -> Address {
|
|
||||||
let s = json.as_string().unwrap();
|
|
||||||
if s.len() % 2 == 1 {
|
|
||||||
address_from_hex(&("0".to_string() + &(clean(s).to_string()))[..])
|
|
||||||
} else {
|
|
||||||
address_from_hex(clean(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn u256_from_json(json: &Json) -> U256 {
|
|
||||||
let s = json.as_string().unwrap();
|
|
||||||
if s.len() >= 2 && &s[0..2] == "0x" {
|
|
||||||
// hex
|
|
||||||
U256::from_str(&s[2..]).unwrap()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// dec
|
|
||||||
U256::from_dec_str(s).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! declare_test {
|
macro_rules! declare_test {
|
||||||
($id: ident, $name: expr) => {
|
($id: ident, $name: expr) => {
|
||||||
|
@ -6,15 +6,16 @@ fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
|||||||
let mut failed = Vec::new();
|
let mut failed = Vec::new();
|
||||||
let old_schedule = evm::Schedule::new_frontier();
|
let old_schedule = evm::Schedule::new_frontier();
|
||||||
let new_schedule = evm::Schedule::new_homestead();
|
let new_schedule = evm::Schedule::new_homestead();
|
||||||
|
let ot = RefCell::new(Transaction::new());
|
||||||
for (name, test) in json.as_object().unwrap() {
|
for (name, test) in json.as_object().unwrap() {
|
||||||
let mut fail = false;
|
let mut fail = false;
|
||||||
let mut fail_unless = |cond: bool| if !cond && fail { failed.push(name.to_string()); fail = true };
|
let mut fail_unless = |cond: bool| if !cond && !fail { failed.push(name.to_string()); println!("Transaction: {:?}", ot.borrow()); fail = true };
|
||||||
let schedule = match test.find("blocknumber")
|
let schedule = match test.find("blocknumber")
|
||||||
.and_then(|j| j.as_string())
|
.and_then(|j| j.as_string())
|
||||||
.and_then(|s| BlockNumber::from_str(s).ok())
|
.and_then(|s| BlockNumber::from_str(s).ok())
|
||||||
.unwrap_or(0) { x if x < 900000 => &old_schedule, _ => &new_schedule };
|
.unwrap_or(0) { x if x < 900000 => &old_schedule, _ => &new_schedule };
|
||||||
let rlp = bytes_from_json(&test["rlp"]);
|
let rlp = bytes_from_json(&test["rlp"]);
|
||||||
let res = UntrustedRlp::new(&rlp).as_val().map_err(|e| From::from(e)).and_then(|t: Transaction| t.validate(schedule));
|
let res = UntrustedRlp::new(&rlp).as_val().map_err(|e| From::from(e)).and_then(|t: Transaction| t.validate(schedule, schedule.have_delegate_call));
|
||||||
fail_unless(test.find("transaction").is_none() == res.is_err());
|
fail_unless(test.find("transaction").is_none() == res.is_err());
|
||||||
if let (Some(&Json::Object(ref tx)), Some(&Json::String(ref expect_sender))) = (test.find("transaction"), test.find("sender")) {
|
if let (Some(&Json::Object(ref tx)), Some(&Json::String(ref expect_sender))) = (test.find("transaction"), test.find("sender")) {
|
||||||
let t = res.unwrap();
|
let t = res.unwrap();
|
||||||
@ -25,8 +26,10 @@ fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
|||||||
fail_unless(t.nonce == u256_from_json(&tx["nonce"]));
|
fail_unless(t.nonce == u256_from_json(&tx["nonce"]));
|
||||||
fail_unless(t.value == u256_from_json(&tx["value"]));
|
fail_unless(t.value == u256_from_json(&tx["value"]));
|
||||||
if let Action::Call(ref to) = t.action {
|
if let Action::Call(ref to) = t.action {
|
||||||
|
*ot.borrow_mut() = t.clone();
|
||||||
fail_unless(to == &address_from_json(&tx["to"]));
|
fail_unless(to == &address_from_json(&tx["to"]));
|
||||||
} else {
|
} else {
|
||||||
|
*ot.borrow_mut() = t.clone();
|
||||||
fail_unless(bytes_from_json(&tx["to"]).len() == 0);
|
fail_unless(bytes_from_json(&tx["to"]).len() == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use basic_types::*;
|
|||||||
use error::*;
|
use error::*;
|
||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Create,
|
Create,
|
||||||
Call(Address),
|
Call(Address),
|
||||||
@ -10,6 +11,7 @@ pub enum Action {
|
|||||||
|
|
||||||
/// A set of information describing an externally-originating message call
|
/// A set of information describing an externally-originating message call
|
||||||
/// or contract creation operation.
|
/// or contract creation operation.
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
pub nonce: U256,
|
pub nonce: U256,
|
||||||
pub gas_price: U256,
|
pub gas_price: U256,
|
||||||
@ -28,6 +30,21 @@ pub struct Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Transaction {
|
||||||
|
nonce: U256::zero(),
|
||||||
|
gas_price: U256::zero(),
|
||||||
|
gas: U256::zero(),
|
||||||
|
action: Action::Create,
|
||||||
|
value: U256::zero(),
|
||||||
|
data: vec![],
|
||||||
|
v: 0,
|
||||||
|
r: U256::zero(),
|
||||||
|
s: U256::zero(),
|
||||||
|
hash: RefCell::new(None),
|
||||||
|
sender: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Create a new message-call transaction.
|
/// Create a new message-call transaction.
|
||||||
pub fn new_call(to: Address, value: U256, data: Bytes, gas: U256, gas_price: U256, nonce: U256) -> Transaction {
|
pub fn new_call(to: Address, value: U256, data: Bytes, gas: U256, gas_price: U256, nonce: U256) -> Transaction {
|
||||||
Transaction {
|
Transaction {
|
||||||
@ -62,6 +79,32 @@ impl Transaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_json(json: &Json) -> Transaction {
|
||||||
|
let mut r = Transaction {
|
||||||
|
nonce: u256_from_json(&json["nonce"]),
|
||||||
|
gas_price: u256_from_json(&json["gasPrice"]),
|
||||||
|
gas: u256_from_json(&json["gasLimit"]),
|
||||||
|
action: match bytes_from_json(&json["to"]) {
|
||||||
|
ref x if x.len() == 0 => Action::Create,
|
||||||
|
ref x => Action::Call(Address::from_slice(x)),
|
||||||
|
},
|
||||||
|
value: u256_from_json(&json["value"]),
|
||||||
|
data: bytes_from_json(&json["data"]),
|
||||||
|
v: match json.find("v") { Some(ref j) => u8_from_json(j), None => 0 },
|
||||||
|
r: match json.find("r") { Some(ref j) => u256_from_json(j), None => U256::zero() },
|
||||||
|
s: match json.find("s") { Some(ref j) => u256_from_json(j), None => U256::zero() },
|
||||||
|
hash: RefCell::new(None),
|
||||||
|
sender: match json.find("sender") {
|
||||||
|
Some(&Json::String(ref sender)) => RefCell::new(Some(address_from_hex(clean(sender)))),
|
||||||
|
_ => RefCell::new(None),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if let Some(&Json::String(ref secret_key)) = json.find("secretKey") {
|
||||||
|
r.sign(&h256_from_hex(clean(secret_key)));
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the nonce of the transaction.
|
/// Get the nonce of the transaction.
|
||||||
pub fn nonce(&self) -> &U256 { &self.nonce }
|
pub fn nonce(&self) -> &U256 { &self.nonce }
|
||||||
/// Get the gas price of the transaction.
|
/// Get the gas price of the transaction.
|
||||||
@ -146,6 +189,7 @@ impl Transaction {
|
|||||||
|
|
||||||
/// Signs the transaction as coming from `sender`.
|
/// Signs the transaction as coming from `sender`.
|
||||||
pub fn sign(&mut self, secret: &Secret) {
|
pub fn sign(&mut self, secret: &Secret) {
|
||||||
|
// TODO: make always low.
|
||||||
let sig = ec::sign(secret, &self.message_hash());
|
let sig = ec::sign(secret, &self.message_hash());
|
||||||
let (r, s, v) = sig.unwrap().to_rsv();
|
let (r, s, v) = sig.unwrap().to_rsv();
|
||||||
self.r = r;
|
self.r = r;
|
||||||
@ -170,7 +214,10 @@ impl Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Do basic validation, checking for valid signature and minimum gas,
|
/// Do basic validation, checking for valid signature and minimum gas,
|
||||||
pub fn validate(self, schedule: &Schedule) -> Result<Transaction, Error> {
|
pub fn validate(self, schedule: &Schedule, require_low: bool) -> Result<Transaction, Error> {
|
||||||
|
if require_low && !ec::is_low_s(&self.s) {
|
||||||
|
return Err(Error::Util(UtilError::Crypto(CryptoError::InvalidSignature)));
|
||||||
|
}
|
||||||
try!(self.sender());
|
try!(self.sender());
|
||||||
if self.gas < U256::from(self.gas_required(&schedule)) {
|
if self.gas < U256::from(self.gas_required(&schedule)) {
|
||||||
Err(From::from(TransactionError::InvalidGasLimit(OutOfBounds{min: Some(U256::from(self.gas_required(&schedule))), max: None, found: self.gas})))
|
Err(From::from(TransactionError::InvalidGasLimit(OutOfBounds{min: Some(U256::from(self.gas_required(&schedule))), max: None, found: self.gas})))
|
||||||
|
Loading…
Reference in New Issue
Block a user