Merge pull request #853 from ethcore/json_tx_refactor

refactored loading transaction json tests
This commit is contained in:
Nikolay Volf 2016-03-30 03:32:41 +04:00
commit 3e09f99845
9 changed files with 267 additions and 81 deletions

View File

@ -16,75 +16,54 @@
use super::test_common::*; use super::test_common::*;
use evm; use evm;
use ethjson;
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 tests = ethjson::transaction::Test::load(json_data).unwrap();
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(None); for (name, test) in tests.into_iter() {
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.clone()); println!("Transaction: {:?}", ot.borrow()); fail = true }; let mut fail_unless = |cond: bool| if !cond && !fail { failed.push(name.clone()); println!("Transaction failed: {:?}", name); fail = true };
let schedule = match test.find("blocknumber")
.and_then(|j| j.as_string()) let number: Option<u64> = test.block_number.map(Into::into);
.and_then(|s| BlockNumber::from_str(s).ok()) let schedule = match number {
.unwrap_or(0) { x if x < 1_000_000 => &old_schedule, _ => &new_schedule }; None => &old_schedule,
let rlp = Bytes::from_json(&test["rlp"]); Some(x) if x < 1_150_000 => &old_schedule,
let res = UntrustedRlp::new(&rlp).as_val().map_err(From::from).and_then(|t: SignedTransaction| t.validate(schedule, schedule.have_delegate_call)); Some(_) => &new_schedule
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")) {
let rlp: Vec<u8> = test.rlp.into();
let res = UntrustedRlp::new(&rlp)
.as_val()
.map_err(From::from)
.and_then(|t: SignedTransaction| t.validate(schedule, schedule.have_delegate_call));
fail_unless(test.transaction.is_none() == res.is_err());
if let (Some(tx), Some(sender)) = (test.transaction, test.sender) {
let t = res.unwrap(); let t = res.unwrap();
fail_unless(t.sender().unwrap() == address_from_hex(clean(expect_sender))); fail_unless(t.sender().unwrap() == sender.into());
fail_unless(t.data == Bytes::from_json(&tx["data"])); let data: Vec<u8> = tx.data.into();
fail_unless(t.gas == xjson!(&tx["gasLimit"])); fail_unless(t.data == data);
fail_unless(t.gas_price == xjson!(&tx["gasPrice"])); fail_unless(t.gas_price == tx.gas_price.into());
fail_unless(t.nonce == xjson!(&tx["nonce"])); fail_unless(t.nonce == tx.nonce.into());
fail_unless(t.value == xjson!(&tx["value"])); fail_unless(t.value == tx.value.into());
if let Action::Call(ref to) = t.action { let to: Option<_> = tx.to.into();
*ot.borrow_mut() = Some(t.clone()); let to: Option<Address> = to.map(Into::into);
fail_unless(to == &xjson!(&tx["to"])); match t.action {
} else { Action::Call(dest) => fail_unless(Some(dest) == to),
*ot.borrow_mut() = Some(t.clone()); Action::Create => fail_unless(None == to),
fail_unless(Bytes::from_json(&tx["to"]).is_empty());
} }
} }
} }
for f in &failed { for f in &failed {
println!("FAILED: {:?}", f); println!("FAILED: {:?}", f);
} }
failed failed
} }
// Once we have interpolate idents.
/*macro_rules! declare_test {
($test_set_name: ident / $name: ident) => {
#[test]
#[allow(non_snake_case)]
fn $name() {
assert!(do_json_test(include_bytes!(concat!("../res/ethereum/tests/", stringify!($test_set_name), "/", stringify!($name), ".json"))).len() == 0);
}
};
($test_set_name: ident / $prename: ident / $name: ident) => {
#[test]
#[allow(non_snake_case)]
interpolate_idents! { fn [$prename _ $name]()
{
let json = include_bytes!(concat!("../res/ethereum/tests/", stringify!($test_set_name), "/", stringify!($prename), "/", stringify!($name), ".json"));
assert!(do_json_test(json).len() == 0);
}
}
};
}
declare_test!{TransactionTests/ttTransactionTest}
declare_test!{TransactionTests/tt10mbDataField}
declare_test!{TransactionTests/ttWrongRLPTransaction}
declare_test!{TransactionTests/Homestead/ttTransactionTest}
declare_test!{heavy => TransactionTests/Homestead/tt10mbDataField}
declare_test!{TransactionTests/Homestead/ttWrongRLPTransaction}
declare_test!{TransactionTests/RandomTests/tr201506052141PYTHON}*/
declare_test!{TransactionTests_ttTransactionTest, "TransactionTests/ttTransactionTest"} declare_test!{TransactionTests_ttTransactionTest, "TransactionTests/ttTransactionTest"}
declare_test!{heavy => TransactionTests_tt10mbDataField, "TransactionTests/tt10mbDataField"} declare_test!{heavy => TransactionTests_tt10mbDataField, "TransactionTests/tt10mbDataField"}
declare_test!{TransactionTests_ttWrongRLPTransaction, "TransactionTests/ttWrongRLPTransaction"} declare_test!{TransactionTests_ttWrongRLPTransaction, "TransactionTests/ttWrongRLPTransaction"}

View File

@ -97,33 +97,26 @@ impl From<ethjson::state::Transaction> for SignedTransaction {
} }
} }
impl FromJson for SignedTransaction { impl From<ethjson::transaction::Transaction> for SignedTransaction {
#[cfg_attr(feature="dev", allow(single_char_pattern))] fn from(t: ethjson::transaction::Transaction) -> Self {
fn from_json(json: &Json) -> SignedTransaction { let to: Option<_> = t.to.into();
let t = Transaction { SignedTransaction {
nonce: xjson!(&json["nonce"]), unsigned: Transaction {
gas_price: xjson!(&json["gasPrice"]), nonce: t.nonce.into(),
gas: xjson!(&json["gasLimit"]), gas_price: t.gas_price.into(),
action: match Bytes::from_json(&json["to"]) { gas: t.gas_limit.into(),
ref x if x.is_empty() => Action::Create, action: match to {
ref x => Action::Call(Address::from_slice(x)), Some(to) => Action::Call(to.into()),
None => Action::Create
}, },
value: xjson!(&json["value"]), value: t.value.into(),
data: xjson!(&json["data"]), data: t.data.into(),
}; },
match json.find("secretKey") { r: t.r.into(),
Some(&Json::String(ref secret_key)) => t.sign(&h256_from_hex(clean(secret_key))), s: t.s.into(),
_ => SignedTransaction { v: t.v.into(),
unsigned: t, sender: Cell::new(None),
v: match json.find("v") { Some(ref j) => u16::from_json(j) as u8, None => 0 }, hash: Cell::new(None)
r: match json.find("r") { Some(j) => xjson!(j), None => x!(0) },
s: match json.find("s") { Some(j) => xjson!(j), None => x!(0) },
hash: Cell::new(None),
sender: match json.find("sender") {
Some(&Json::String(ref sender)) => Cell::new(Some(address_from_hex(clean(sender)))),
_ => Cell::new(None),
}
}
} }
} }
} }

View File

@ -46,6 +46,10 @@ impl Visitor for BytesVisitor {
let v = match value.len() { let v = match value.len() {
0 => vec![], 0 => vec![],
2 if value.starts_with("0x") => vec![], 2 if value.starts_with("0x") => vec![],
_ if value.starts_with("0x") && value.len() % 2 == 1 => {
let v = "0".to_owned() + &value[2..];
FromHex::from_hex(v.as_ref() as &str).unwrap_or(vec![]),
},
_ if value.starts_with("0x") => FromHex::from_hex(&value[2..]).unwrap_or(vec![]), _ if value.starts_with("0x") => FromHex::from_hex(&value[2..]).unwrap_or(vec![]),
_ => FromHex::from_hex(value).unwrap_or(vec![]), _ => FromHex::from_hex(value).unwrap_or(vec![]),
}; };
@ -64,13 +68,14 @@ mod test {
#[test] #[test]
fn bytes_deserialization() { fn bytes_deserialization() {
let s = r#"["", "0x", "0x12", "1234"]"#; let s = r#"["", "0x", "0x12", "1234", "0x001"]"#;
let deserialized: Vec<Bytes> = serde_json::from_str(s).unwrap(); let deserialized: Vec<Bytes> = serde_json::from_str(s).unwrap();
assert_eq!(deserialized, vec![ assert_eq!(deserialized, vec![
Bytes(vec![]), Bytes(vec![]),
Bytes(vec![]), Bytes(vec![]),
Bytes(vec![0x12]), Bytes(vec![0x12]),
Bytes(vec![0x12, 0x34]) Bytes(vec![0x12, 0x34]),
Bytes(vec![0, 1])
]); ]);
} }

View File

@ -27,3 +27,4 @@ pub mod spec;
pub mod vm; pub mod vm;
pub mod maybe; pub mod maybe;
pub mod state; pub mod state;
pub mod transaction;

View File

@ -0,0 +1,25 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Transaction test deserialization.
mod transaction;
mod txtest;
mod test;
pub use self::transaction::Transaction;
pub use self::txtest::TransactionTest;
pub use self::test::Test;

View File

@ -0,0 +1,43 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! TransactionTest test deserializer.
use std::collections::BTreeMap;
use std::io::Read;
use serde_json;
use serde_json::Error;
use transaction::TransactionTest;
/// TransactionTest test deserializer.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Test(BTreeMap<String, TransactionTest>);
impl IntoIterator for Test {
type Item = <BTreeMap<String, TransactionTest> as IntoIterator>::Item;
type IntoIter = <BTreeMap<String, TransactionTest> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Test {
/// Loads test from json.
pub fn load<R>(reader: R) -> Result<Self, Error> where R: Read {
serde_json::from_reader(reader)
}
}

View File

@ -0,0 +1,70 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Transaction test transaction deserialization.
use uint::Uint;
use bytes::Bytes;
use hash::Address;
use maybe::MaybeEmpty;
/// Transaction test transaction deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Transaction {
/// Transaction data.
pub data: Bytes,
/// Gas limit.
#[serde(rename="gasLimit")]
pub gas_limit: Uint,
/// Gas price.
#[serde(rename="gasPrice")]
pub gas_price: Uint,
/// Nonce.
pub nonce: Uint,
/// To.
pub to: MaybeEmpty<Address>,
/// Value.
pub value: Uint,
/// R.
pub r: Uint,
/// S.
pub s: Uint,
/// V.
pub v: Uint,
}
#[cfg(test)]
mod tests {
use serde_json;
use transaction::Transaction;
#[test]
fn transaction_deserialization() {
let s = r#"{
"data" : "0x",
"gasLimit" : "0xf388",
"gasPrice" : "0x09184e72a000",
"nonce" : "0x00",
"r" : "0x2c",
"s" : "0x04",
"to" : "",
"v" : "0x1b",
"value" : "0x00"
}"#;
let _deserialized: Transaction = serde_json::from_str(s).unwrap();
// TODO: validate all fields
}
}

View File

@ -0,0 +1,64 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Transaction test deserialization.
use uint::Uint;
use bytes::Bytes;
use hash::Address;
use transaction::Transaction;
/// Transaction test deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct TransactionTest {
/// Block number.
#[serde(rename="blocknumber")]
pub block_number: Option<Uint>,
/// Transaction rlp.
pub rlp: Bytes,
/// Transaction sender.
pub sender: Option<Address>,
/// Transaction
pub transaction: Option<Transaction>,
}
#[cfg(test)]
mod tests {
use serde_json;
use transaction::TransactionTest;
#[test]
fn transaction_deserialization() {
let s = r#"{
"blocknumber" : "0",
"rlp" : "0xf83f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870b801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a3664935301",
"sender" : "e115cf6bb5656786569dd273705242ca72d84bc0",
"transaction" : {
"data" : "",
"gasLimit" : "0x5208",
"gasPrice" : "0x01",
"nonce" : "0x00",
"r" : "0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353",
"s" : "0x01",
"to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87",
"v" : "0x1b",
"value" : "0x0b"
}
}"#;
let _deserialized: TransactionTest = serde_json::from_str(s).unwrap();
// TODO: validate all fields
}
}

View File

@ -37,6 +37,12 @@ impl Into<u64> for Uint {
} }
} }
impl Into<u8> for Uint {
fn into(self) -> u8 {
<Uint as Into<u64>>::into(self) as u8
}
}
impl Deserialize for Uint { impl Deserialize for Uint {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer { where D: Deserializer {