Browse Source

VM test framework.

cl-refactor
Gav Wood 11 years ago
parent
commit
87e19602ea
  1. 2
      CMakeLists.txt
  2. 2
      TODO
  3. 20
      eth/main.cpp
  4. 22
      libethereum/Common.cpp
  5. 12
      libethereum/Common.h
  6. 11
      libethereum/ExtVMFace.h
  7. 2
      libethereum/FeeStructure.cpp
  8. 2
      libethereum/PeerNetwork.cpp
  9. 3
      libethereum/Transaction.h
  10. 8
      test/dagger.cpp
  11. 10
      test/main.cpp
  12. 315
      test/vm.cpp

2
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.

2
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

20
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";

22
libethereum/Common.cpp

@ -21,6 +21,7 @@
#include "Common.h"
#include <fstream>
#include <random>
#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());
}

12
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 <unsigned T> class UnitTest {};
template <unsigned N>
@ -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<uint>(_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<Arith>(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 <class T, class U> inline std::ostream& operator<<(std::ostream& _out,
template <class _S, class _T> _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);
}

11
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.

2
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;

2
libethereum/PeerNetwork.cpp

@ -43,7 +43,7 @@ using namespace eth;
#define clogS(X) eth::LogOutputStream<X, true>(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.

3
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;

8
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<milliseconds>(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<milliseconds>(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<milliseconds>(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<milliseconds>(steady_clock::now() - s).count() << " ms" << endl;
}
return 0;

10
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);

315
test/vm.cpp

@ -20,11 +20,15 @@
* State test functions.
*/
#include <fstream>
#include "../json_spirit/json_spirit_reader_template.h"
#include "../json_spirit/json_spirit_writer_template.h"
#include <ExtVMFace.h>
#include <Transaction.h>
#include <VM.h>
#include <Instruction.h>
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<Address, tuple<u256, u256, u256, map<u256, u256>>> 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);
}
};

Loading…
Cancel
Save