From 87e19602ea46cb3b0ed3cdee7b27aad33276705a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 21 Feb 2014 19:18:30 +0000 Subject: [PATCH] VM test framework. --- CMakeLists.txt | 2 +- TODO | 2 - eth/main.cpp | 20 --- libethereum/Common.cpp | 22 +++ libethereum/Common.h | 12 +- libethereum/ExtVMFace.h | 11 +- libethereum/FeeStructure.cpp | 2 +- libethereum/PeerNetwork.cpp | 2 +- libethereum/Transaction.h | 3 + test/dagger.cpp | 8 +- test/main.cpp | 10 +- test/vm.cpp | 315 ++++++++++++++++++++++++++++++++--- 12 files changed, 349 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5382cf59..fc7fbd0aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_AUTOMOC ON) cmake_policy(SET CMP0015 NEW) -set(ETH_VERSION 0.3.4) +set(ETH_VERSION 0.3.5) set(ETH_BUILD_TYPE ${CMAKE_BUILD_TYPE}) # Default HEADLESS to 0. diff --git a/TODO b/TODO index 59cacdca9..1c12ba467 100644 --- a/TODO +++ b/TODO @@ -51,8 +51,6 @@ GUI For PoC3: - Shared contract acceptence tests. -- Use mining state for nonce. -BUG: need to discard transactions if nonce too old, even when not mining. ### Marko diff --git a/eth/main.cpp b/eth/main.cpp index 3359ae6de..d012961cd 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -35,26 +35,6 @@ using namespace eth; #define ADD_QUOTES_HELPER(s) #s #define ADD_QUOTES(s) ADD_QUOTES_HELPER(s) -bytes contents(std::string const& _file) -{ - std::ifstream is(_file, std::ifstream::binary); - if (!is) - return bytes(); - // get length of file: - is.seekg (0, is.end); - streamoff length = is.tellg(); - is.seekg (0, is.beg); - bytes ret(length); - is.read((char*)ret.data(), length); - is.close(); - return ret; -} - -void writeFile(std::string const& _file, bytes const& _data) -{ - ofstream(_file, ios::trunc).write((char const*)_data.data(), _data.size()); -} - bool isTrue(std::string const& _m) { return _m == "on" || _m == "yes" || _m == "true" || _m == "1"; diff --git a/libethereum/Common.cpp b/libethereum/Common.cpp index fd2af14ef..2b08c84fd 100644 --- a/libethereum/Common.cpp +++ b/libethereum/Common.cpp @@ -21,6 +21,7 @@ #include "Common.h" +#include #include #if WIN32 #pragma warning(push) @@ -281,3 +282,24 @@ std::string eth::formatBalance(u256 _b) ret << _b << " wei"; return ret.str(); } + +bytes eth::contents(std::string const& _file) +{ + std::ifstream is(_file, std::ifstream::binary); + if (!is) + return bytes(); + // get length of file: + is.seekg (0, is.end); + streamoff length = is.tellg(); + is.seekg (0, is.beg); + bytes ret(length); + is.read((char*)ret.data(), length); + is.close(); + return ret; +} + +void eth::writeFile(std::string const& _file, bytes const& _data) +{ + ofstream(_file, ios::trunc).write((char const*)_data.data(), _data.size()); +} + diff --git a/libethereum/Common.h b/libethereum/Common.h index c4a0c263f..5087471e6 100644 --- a/libethereum/Common.h +++ b/libethereum/Common.h @@ -90,6 +90,10 @@ std::string asHex(_T const& _data, int _w = 2) return ret.str(); } +/// Converts a (printable) ASCII hex string into the corresponding byte stream. +/// @example fromUserHex("41626261") == asBytes("Abba") +bytes fromUserHex(std::string const& _s); + template class UnitTest {}; template @@ -105,6 +109,7 @@ public: FixedHash(Arith const& _arith) { toBigEndian(_arith, m_data); } explicit FixedHash(bytes const& _b) { memcpy(m_data.data(), _b.data(), std::min(_b.size(), N)); } explicit FixedHash(byte const* _bs, ConstructFromPointerType) { memcpy(m_data.data(), _bs, N); } + explicit FixedHash(std::string const& _user): FixedHash(fromUserHex(_user)) {} operator Arith() const { return fromBigEndian(m_data); } @@ -311,10 +316,6 @@ std::string escaped(std::string const& _s, bool _all = true); /// @example fromHex('A') == 10 && fromHex('f') == 15 && fromHex('5') == 5 int fromHex(char _i); -/// Converts a (printable) ASCII hex string into the corresponding byte stream. -/// @example fromUserHex("41626261") == asBytes("Abba") -bytes fromUserHex(std::string const& _s); - /// Converts a string into the big-endian base-16 stream of integers (NOT ASCII). /// @example toHex("A")[0] == 4 && toHex("A")[1] == 1 bytes toHex(std::string const& _s); @@ -640,4 +641,7 @@ template inline std::ostream& operator<<(std::ostream& _out, template _S& operator<<(_S& _out, std::shared_ptr<_T> const& _p) { if (_p) _out << "@" << (*_p); else _out << "nullptr"; return _out; } +bytes contents(std::string const& _file); +void writeFile(std::string const& _file, bytes const& _data); + } diff --git a/libethereum/ExtVMFace.h b/libethereum/ExtVMFace.h index 808e5915b..ccb49c48b 100644 --- a/libethereum/ExtVMFace.h +++ b/libethereum/ExtVMFace.h @@ -33,6 +33,15 @@ struct Transaction; class ExtVMFace { public: + ExtVMFace() {} + + ExtVMFace(FeeStructure const& _fees, BlockInfo const& _previousBlock, BlockInfo const& _currentBlock, uint _currentNumber): + fees(_fees), + previousBlock(_previousBlock), + currentBlock(_currentBlock), + currentNumber(_currentNumber) + {} + ExtVMFace(Address _myAddress, Address _txSender, u256 _txValue, u256s const& _txData, FeeStructure const& _fees, BlockInfo const& _previousBlock, BlockInfo const& _currentBlock, uint _currentNumber): myAddress(_myAddress), txSender(_txSender), @@ -57,7 +66,7 @@ public: Address myAddress; Address txSender; u256 txValue; - u256s const& txData; + u256s txData; FeeStructure fees; BlockInfo previousBlock; ///< The current block's information. BlockInfo currentBlock; ///< The current block's information. diff --git a/libethereum/FeeStructure.cpp b/libethereum/FeeStructure.cpp index afcd31751..df8afb21b 100644 --- a/libethereum/FeeStructure.cpp +++ b/libethereum/FeeStructure.cpp @@ -26,7 +26,7 @@ using namespace eth; u256 const c_stepFee = 1; u256 const c_dataFee = 20; -u256 const c_memoryFee = 0;//5; memoryFee is 0 for PoC-3 +u256 const c_memoryFee = 5; u256 const c_extroFee = 40; u256 const c_cryptoFee = 20; u256 const c_newContractFee = 100; diff --git a/libethereum/PeerNetwork.cpp b/libethereum/PeerNetwork.cpp index 7bf32f7c6..1f4a8826c 100644 --- a/libethereum/PeerNetwork.cpp +++ b/libethereum/PeerNetwork.cpp @@ -43,7 +43,7 @@ using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -static const int c_protocolVersion = 5; +static const int c_protocolVersion = 6; static const eth::uint c_maxHashes = 32; ///< Maximum number of hashes GetChain will ever send. static const eth::uint c_maxBlocks = 32; ///< Maximum number of blocks Blocks will ever send. BUG: if this gets too big (e.g. 2048) stuff starts going wrong. diff --git a/libethereum/Transaction.h b/libethereum/Transaction.h index d593335cf..aca8a125c 100644 --- a/libethereum/Transaction.h +++ b/libethereum/Transaction.h @@ -41,6 +41,9 @@ struct Transaction Transaction(bytesConstRef _rlp); Transaction(bytes const& _rlp): Transaction(&_rlp) {} + bool operator==(Transaction const& _c) const { return receiveAddress == _c.receiveAddress && value == _c.value && data == _c.data; } + bool operator!=(Transaction const& _c) const { return !operator==(_c); } + u256 nonce; Address receiveAddress; u256 value; diff --git a/test/dagger.cpp b/test/dagger.cpp index 1a31f8a36..cc22baa13 100644 --- a/test/dagger.cpp +++ b/test/dagger.cpp @@ -31,16 +31,16 @@ int daggerTest() // Test dagger { auto s = steady_clock::now(); - cout << hex << Dagger().eval((h256)1, (h256)0); + cout << hex << Dagger().eval((h256)(u256)1, (h256)(u256)0); cout << " " << dec << duration_cast(steady_clock::now() - s).count() << " ms" << endl; - cout << hex << Dagger().eval((h256)1, (h256)1); + cout << hex << Dagger().eval((h256)(u256)1, (h256)(u256)1); cout << " " << dec << duration_cast(steady_clock::now() - s).count() << " ms" << endl; } { auto s = steady_clock::now(); - cout << hex << Dagger().eval((h256)1, (h256)0); + cout << hex << Dagger().eval((h256)(u256)1, (h256)(u256)0); cout << " " << dec << duration_cast(steady_clock::now() - s).count() << " ms" << endl; - cout << hex << Dagger().eval((h256)1, (h256)1); + cout << hex << Dagger().eval((h256)(u256)1, (h256)(u256)1); cout << " " << dec << duration_cast(steady_clock::now() - s).count() << " ms" << endl; } return 0; diff --git a/test/main.cpp b/test/main.cpp index ed0ea76e7..0cf469010 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -42,11 +42,11 @@ int main(int, char**) std::cout << asHex(s.out()) << std::endl; std::cout << sha3(s.out()) << std::endl;*/ - hexPrefixTest(); - rlpTest(); - trieTest(); - daggerTest(); - cryptoTest(); +// hexPrefixTest(); +// rlpTest(); +// trieTest(); +// daggerTest(); +// cryptoTest(); vmTest(); // stateTest(); // peerTest(argc, argv); diff --git a/test/vm.cpp b/test/vm.cpp index e9f872fcd..285a0397a 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -20,11 +20,15 @@ * State test functions. */ +#include +#include "../json_spirit/json_spirit_reader_template.h" +#include "../json_spirit/json_spirit_writer_template.h" #include #include #include #include using namespace std; +using namespace json_spirit; using namespace eth; namespace eth @@ -33,37 +37,236 @@ namespace eth class FakeExtVM: public ExtVMFace { public: - FakeExtVM(Address _myAddress, u256 _myBalance, u256 _myNonce, u256s _myData, Address _txSender, u256 _txValue, u256s const& _txData, FeeStructure const& _fees, BlockInfo const& _previousBlock, BlockInfo const& _currentBlock, uint _currentNumber): - ExtVMFace(_myAddress, _txSender, _txValue, _txData, _fees, _previousBlock, _currentBlock, _currentNumber) - { - reset(_myBalance, _myNonce, _myData); - } + FakeExtVM() + {} + FakeExtVM(FeeStructure const& _fees, BlockInfo const& _previousBlock, BlockInfo const& _currentBlock, uint _currentNumber): + ExtVMFace(Address(), Address(), 0, u256s(), _fees, _previousBlock, _currentBlock, _currentNumber) + {} u256 store(u256 _n) { return get<3>(addresses[myAddress])[_n]; } void setStore(u256 _n, u256 _v) { get<3>(addresses[myAddress])[_n] = _v; } - void mktx(Transaction& _t) { txs.push_back(_t); } + void mktx(Transaction& _t) + { + if (get<0>(addresses[myAddress]) >= _t.value) + { + get<0>(addresses[myAddress]) -= _t.value; + get<1>(addresses[myAddress])++; +// get<0>(addresses[_t.receiveAddress]) += _t.value; + txs.push_back(_t); + } + } u256 balance(Address _a) { return get<0>(addresses[_a]); } void payFee(bigint _fee) { get<0>(addresses[myAddress]) = (u256)(get<0>(addresses[myAddress]) - _fee); } u256 txCount(Address _a) { return get<1>(addresses[_a]); } u256 extro(Address _a, u256 _pos) { return get<3>(addresses[_a])[_pos]; } u256 extroPrice(Address _a) { return get<2>(addresses[_a]); } - void suicide(Address _a) { dead = _a; } + void suicide(Address _a) + { + for (auto const& i: get<3>(addresses[myAddress])) + if (i.second) + get<0>(addresses[_a]) += fees.m_memoryFee; + get<0>(addresses[_a]) += get<0>(addresses[myAddress]); + addresses.erase(myAddress); + } + + void setTransaction(Address _txSender, u256 _txValue, u256s const& _txData) + { + txSender = _txSender; + txValue = _txValue; + txData = _txData; + } + void setContract(Address _myAddress, u256 _myBalance, u256 _myNonce, u256s _myData) + { + myAddress = _myAddress; + set(myAddress, _myBalance, _myNonce, _myData); + } + void set(Address _a, u256 _myBalance, u256 _myNonce, u256s _myData) + { + get<0>(addresses[_a]) = _myBalance; + get<1>(addresses[_a]) = _myNonce; + get<2>(addresses[_a]) = 0; + for (unsigned i = 0; i < _myData.size(); ++i) + get<3>(addresses[_a])[i] = _myData[i]; + } + + mObject exportEnv() + { + mObject ret; + ret["previousHash"] = toString(previousBlock.hash); + ret["previousNonce"] = toString(previousBlock.nonce); + push(ret, "currentDifficulty", currentBlock.difficulty); + push(ret, "currentTimestamp", currentBlock.timestamp); + ret["currentCoinbase"] = toString(currentBlock.coinbaseAddress); + push(ret, "feeMultiplier", fees.multiplier()); + return ret; + } + + void importEnv(mObject& _o) + { + previousBlock.hash = h256(_o["previousHash"].get_str()); + previousBlock.nonce = h256(_o["previousNonce"].get_str()); + currentBlock.difficulty = toInt(_o["currentDifficulty"]); + currentBlock.timestamp = toInt(_o["currentTimestamp"]); + currentBlock.coinbaseAddress = Address(_o["currentCoinbase"].get_str()); + fees.setMultiplier(toInt(_o["feeMultiplier"])); + } + + static u256 toInt(mValue const& _v) + { + switch (_v.type()) + { + case str_type: return u256(_v.get_str()); + case int_type: return (u256)_v.get_uint64(); + case bool_type: return (u256)(uint64_t)_v.get_bool(); + case real_type: return (u256)(uint64_t)_v.get_real(); + default: cwarn << "Bad type for scalar: " << _v.type(); + } + return 0; + } + + static void push(mObject& o, string const& _n, u256 _v) + { + if (_v < (u256)1 << 64) + o[_n] = (uint64_t)_v; + else + o[_n] = toString(_v); + } + + static void push(mArray& a, u256 _v) + { + if (_v < (u256)1 << 64) + a.push_back((uint64_t)_v); + else + a.push_back(toString(_v)); + } + + mObject exportState() + { + mObject ret; + for (auto const& a: addresses) + { + mObject o; + push(o, "balance", get<0>(a.second)); + push(o, "nonce", get<1>(a.second)); + push(o, "extroPrice", get<2>(a.second)); + + mObject store; + string curKey; + u256 li = 0; + mArray curVal; + for (auto const& s: get<3>(a.second)) + { + if (!li || s.first > li + 8) + { + if (li) + store[curKey] = curVal; + li = s.first; + curKey = toString(li); + curVal = mArray(); + } + else + for (; li != s.first; ++li) + curVal.push_back(0); + push(curVal, s.second); + ++li; + } + if (li) + { + store[curKey] = curVal; + o["store"] = store; + } + ret[toString(a.first)] = o; + } + return ret; + } + + void importState(mObject& _o) + { + for (auto const& i: _o) + { + mObject o = i.second.get_obj(); + auto& a = addresses[Address(i.first)]; + get<0>(a) = toInt(o["balance"]); + get<1>(a) = toInt(o["nonce"]); + get<2>(a) = toInt(o["extroPrice"]); + if (o.count("store")) + for (auto const& j: o["store"].get_obj()) + { + u256 adr(j.first); + for (auto const& k: j.second.get_array()) + get<3>(a)[adr++] = toInt(k); + } + if (o.count("code")) + { + u256s d = compileLisp(o["code"].get_str()); + for (unsigned i = 0; i < d.size(); ++i) + get<3>(a)[(u256)i] = d[i]; + } + } + } + + mObject exportExec() + { + mObject ret; + ret["address"] = toString(myAddress); + ret["sender"] = toString(txSender); + push(ret, "value", txValue); + mArray d; + for (auto const& i: txData) + push(d, i); + ret["data"] = d; + return ret; + } + + void importExec(mObject& _o) + { + myAddress = Address(_o["address"].get_str()); + txSender = Address(_o["sender"].get_str()); + txValue = toInt(_o["value"]); + for (auto const& j: _o["data"].get_array()) + txData.push_back(toInt(j)); + } + + mArray exportTxs() + { + mArray ret; + for (Transaction const& tx: txs) + { + mObject o; + o["destination"] = toString(tx.receiveAddress); + push(o, "value", tx.value); + mArray d; + for (auto const& i: tx.data) + push(d, i); + o["data"] = d; + ret.push_back(o); + } + return ret; + } + + void importTxs(mArray& _txs) + { + for (mValue& v: _txs) + { + auto tx = v.get_obj(); + Transaction t; + t.receiveAddress = Address(tx["destination"].get_str()); + t.value = toInt(tx["value"]); + for (auto const& j: tx["data"].get_array()) + t.data.push_back(toInt(j)); + txs.push_back(t); + } + } void reset(u256 _myBalance, u256 _myNonce, u256s _myData) { txs.clear(); addresses.clear(); - get<0>(addresses[myAddress]) = _myBalance; - get<1>(addresses[myAddress]) = _myNonce; - get<2>(addresses[myAddress]) = 0; - for (unsigned i = 0; i < _myData.size(); ++i) - get<3>(addresses[myAddress])[i] = _myData[i]; - dead = Address(); + set(myAddress, _myBalance, _myNonce, _myData); } map>> addresses; Transactions txs; - Address dead; }; template <> class UnitTest<1> @@ -71,6 +274,72 @@ template <> class UnitTest<1> public: int operator()() { + json_spirit::mValue v; + string s = asString(contents("/home/gav/Projects/cpp-ethereum/test/vmtests.json")); + cout << s << endl; + json_spirit::read_string(s, v); + + doTests(v, true); + + cout << json_spirit::write_string(v, true) << endl; + + bool passed = doTests(v, false); + + return passed ? 0 : 1; + } + + bool doTests(json_spirit::mValue& v, bool _fillin) + { + bool passed = true; + for (auto& i: v.get_obj()) + { + cnote << i.first; + mObject& o = i.second.get_obj(); + + VM vm; + FakeExtVM fev; + fev.importEnv(o["env"].get_obj()); + fev.importState(o["pre"].get_obj()); + + if (_fillin) + o["pre"] = mValue(fev.exportState()); + + for (auto i: o["exec"].get_array()) + { + fev.importExec(i.get_obj()); + vm.go(fev); + } + if (_fillin) + { + o["post"] = mValue(fev.exportState()); + o["txs"] = fev.exportTxs(); + } + else + { + FakeExtVM test; + test.importState(o["post"].get_obj()); + test.importTxs(o["txs"].get_array()); + if (test.addresses != fev.addresses) + { + cwarn << "Test failed: state different."; + passed = false; + } + if (test.txs != fev.txs) + { + cwarn << "Test failed: tx list different:"; + cwarn << test.txs; + cwarn << fev.txs; + passed = false; + } + } + } + return passed; + } + + string makeTestCase() + { + json_spirit::mObject o; + VM vm; BlockInfo pb; pb.hash = sha3("previousHash"); @@ -81,15 +350,19 @@ public: cb.coinbaseAddress = toAddress(sha3("coinbase")); FeeStructure fees; fees.setMultiplier(1); - - string code = "(suicide (txsender))"; - - FakeExtVM fev(toAddress(sha3("contract")), ether, 0, compileLisp(code), toAddress(sha3("sender")), ether, u256s(), fees, pb, cb, 0); - + FakeExtVM fev(fees, pb, cb, 0); + fev.setContract(toAddress(sha3("contract")), ether, 0, compileLisp("(suicide (txsender))")); + o["env"] = fev.exportEnv(); + o["pre"] = fev.exportState(); + fev.setTransaction(toAddress(sha3("sender")), ether, u256s()); + mArray execs; + execs.push_back(fev.exportExec()); + o["exec"] = execs; vm.go(fev); - cnote << fev.dead << formatBalance(fev.balance(toAddress(sha3("contract")))); + o["post"] = fev.exportState(); + o["txs"] = fev.exportTxs(); - return 0; + return json_spirit::write_string(json_spirit::mValue(o), true); } };