Browse Source

Merge remote-tracking branch 'upstream/develop' into evmjit-develop

Conflicts:
	test/TestHelper.cpp
cl-refactor
Paweł Bylica 10 years ago
parent
commit
74925a1a8a
  1. 6
      alethzero/Main.ui
  2. 39
      alethzero/MainWin.cpp
  3. 1
      alethzero/MainWin.h
  4. 5
      alethzero/Transact.cpp
  5. 107
      eth/main.cpp
  6. 2
      libdevcore/Common.cpp
  7. 8
      libdevcore/Common.h
  8. 10
      libdevcore/Worker.cpp
  9. 34
      libdevcrypto/MemoryDB.cpp
  10. 11
      libdevcrypto/MemoryDB.h
  11. 57
      libdevcrypto/OverlayDB.cpp
  12. 8
      libdevcrypto/OverlayDB.h
  13. 2
      libdevcrypto/TrieDB.cpp
  14. 36
      libdevcrypto/TrieDB.h
  15. 6
      libethcore/Ethash.cpp
  16. 2
      libethcore/Ethash.h
  17. 81
      libethcore/EthashAux.cpp
  18. 26
      libethcore/EthashAux.h
  19. 9
      libethereum/BlockChain.cpp
  20. 2
      libethereum/Client.cpp
  21. 4
      libethereum/ClientBase.h
  22. 16
      libethereum/State.cpp
  23. 80
      libevmasm/Assembly.cpp
  24. 2
      libevmasm/AssemblyItem.h
  25. 91
      libevmasm/BlockDeduplicator.cpp
  26. 69
      libevmasm/BlockDeduplicator.h
  27. 113
      libevmasm/CommonSubexpressionEliminator.cpp
  28. 22
      libevmasm/CommonSubexpressionEliminator.h
  29. 22
      libevmasm/ControlFlowGraph.cpp
  30. 52
      libevmasm/KnownState.cpp
  31. 12
      libsolidity/Compiler.cpp
  32. 74
      mix/ClientModel.cpp
  33. 17
      mix/ClientModel.h
  34. 62
      mix/qml/QAddressView.qml
  35. 4
      mix/qml/StateDialog.qml
  36. 22
      mix/qml/StateListModel.qml
  37. 29
      mix/qml/StructView.qml
  38. 128
      mix/qml/TransactionDialog.qml
  39. 5
      mix/qml/js/TransactionHelper.js
  40. 21
      test/TestHelper.cpp
  41. 10
      test/libdevcrypto/trie.cpp
  42. 2
      test/libevm/vm.cpp
  43. 150
      test/libp2p/capability.cpp
  44. 59
      test/libsolidity/SolidityCompiler.cpp
  45. 95
      test/libsolidity/SolidityOptimizer.cpp

6
alethzero/Main.ui

@ -151,6 +151,7 @@
</property>
<addaction name="mine"/>
<addaction name="turboMining"/>
<addaction name="prepNextDAG"/>
<addaction name="separator"/>
<addaction name="newTransaction"/>
<addaction name="newAccount"/>
@ -1727,6 +1728,11 @@ font-size: 14pt</string>
<string>In&amp;ject Block</string>
</property>
</action>
<action name="prepNextDAG">
<property name="text">
<string>Prepare Next &amp;DAG</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

39
alethzero/MainWin.cpp

@ -204,7 +204,7 @@ Main::Main(QWidget *parent) :
QSettings s("ethereum", "alethzero");
m_networkConfig = s.value("peers").toByteArray();
bytesConstRef network((byte*)m_networkConfig.data(), m_networkConfig.size());
m_webThree.reset(new WebThreeDirect(string("AlethZero/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir(), WithExisting::Trust, {"eth", "shh"}, p2p::NetworkPreferences(), network));
m_webThree.reset(new WebThreeDirect(string("AlethZero/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir(), WithExisting::Trust, {"eth"/*, "shh"*/}, p2p::NetworkPreferences(), network));
m_httpConnector.reset(new jsonrpc::HttpServer(SensibleHttpPort, "", "", dev::SensibleHttpThreads));
m_server.reset(new OurWebThreeStubServer(*m_httpConnector, *web3(), this));
@ -943,22 +943,30 @@ void Main::on_preview_triggered()
refreshAll();
}
void Main::on_prepNextDAG_triggered()
{
EthashAux::computeFull(ethereum()->blockChain().number() + ETHASH_EPOCH_LENGTH);
}
void Main::refreshMining()
{
pair<uint64_t, unsigned> gp = EthashAux::fullGeneratingProgress();
QString t;
if (gp.first != EthashAux::NotGenerating)
t = QString("DAG for #%1-#%2: %3% complete; ").arg(gp.first).arg(gp.first + ETHASH_EPOCH_LENGTH - 1).arg(gp.second);
MiningProgress p = ethereum()->miningProgress();
ui->mineStatus->setText(ethereum()->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining");
if (!ui->miningView->isVisible())
return;
list<MineInfo> l = ethereum()->miningHistory();
static unsigned lh = 0;
if (p.hashes < lh)
ui->miningView->resetStats();
lh = p.hashes;
ui->miningView->appendStats(l, p);
/* if (p.ms)
for (MineInfo const& i: l)
cnote << i.hashes * 10 << "h/sec, need:" << i.requirement << " best:" << i.best << " best-so-far:" << p.best << " avg-speed:" << (p.hashes * 1000 / p.ms) << "h/sec";
*/
ui->mineStatus->setText(t + (ethereum()->isMining() ? p.hashes > 0 ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Awaiting DAG" : "Not mining"));
if (ethereum()->isMining() && p.hashes > 0)
{
if (!ui->miningView->isVisible())
return;
list<MineInfo> l = ethereum()->miningHistory();
static unsigned lh = 0;
if (p.hashes < lh)
ui->miningView->resetStats();
lh = p.hashes;
ui->miningView->appendStats(l, p);
}
}
void Main::setBeneficiary(Address const& _b)
@ -1878,6 +1886,7 @@ void Main::on_mine_triggered()
{
if (ui->mine->isChecked())
{
// EthashAux::computeFull(ethereum()->blockChain().number());
ethereum()->setAddress(m_beneficiary);
ethereum()->startMining();
}
@ -2027,6 +2036,7 @@ std::string Main::prettyU256(dev::u256 const& _n) const
void Main::on_post_clicked()
{
return;
shh::Message m;
m.setTo(stringToPublic(ui->shhTo->currentText()));
m.setPayload(parseData(ui->shhData->toPlainText().toStdString()));
@ -2051,6 +2061,7 @@ int Main::authenticate(QString _title, QString _text)
void Main::refreshWhispers()
{
return;
ui->whispers->clear();
for (auto const& w: whisper()->all())
{

1
alethzero/MainWin.h

@ -124,6 +124,7 @@ private slots:
// Mining
void on_mine_triggered();
void on_prepNextDAG_triggered();
// View
void on_refresh_triggered();

5
alethzero/Transact.cpp

@ -299,8 +299,9 @@ void Transact::rejigData()
return;
// Determine how much balance we have to play with...
auto s = findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice());
auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock);
//findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice());
auto s = fromAccount();
auto b = ethereum()->balanceAt(s, PendingBlock);
m_allGood = true;
QString htmlInfo;

107
eth/main.cpp

@ -158,6 +158,11 @@ void help()
<< " --port <port> Connect to remote port (default: 30303)." << endl
<< " --network-id <n> Only connect to other hosts with this network id (default:0)." << endl
<< " --upnp <on/off> Use UPnP for NAT (default: on)." << endl
<< endl
<< "Client structured logging:" << endl
<< " --structured-logging Enable structured logging (default output to stdout)." << endl
<< " --structured-logging-format <format> Set the structured logging time format." << endl
<< " --structured-logging-url <URL> Set the structured logging destination (currently only file:// supported)." << endl
#if ETH_JSONRPC || !ETH_TRUE
<< endl
<< "Work farming mode:" << endl
@ -806,8 +811,11 @@ int main(int argc, char** argv)
structuredLoggingFormat = string(argv[++i]);
else if (arg == "--structured-logging")
structuredLogging = true;
else if (arg == "--structured-logging-destination" && i + 1 < argc)
else if (arg == "--structured-logging-url" && i + 1 < argc)
{
structuredLogging = true;
structuredLoggingURL = argv[++i];
}
else if ((arg == "-d" || arg == "--path" || arg == "--db-path") && i + 1 < argc)
dbPath = argv[++i];
else if ((arg == "-D" || arg == "--create-dag") && i + 1 < argc)
@ -1033,6 +1041,28 @@ int main(int argc, char** argv)
if (!clientName.empty())
clientName += "/";
string logbuf;
bool silence = false;
std::string additional;
g_logPost = [&](std::string const& a, char const*){
if (silence)
logbuf += a + "\n";
else
cout << "\r \r" << a << endl << additional << flush;
};
auto getPassword = [&](string const& prompt){
auto s = silence;
silence = true;
cout << endl;
string ret = dev::getPassword(prompt);
silence = s;
return ret;
};
auto getAccountPassword = [&](Address const& a){
return getPassword("Enter password for address " + keyManager.accountDetails()[a].first + " (" + a.abridged() + "; hint:" + keyManager.accountDetails()[a].second + "): ");
};
StructuredLogger::get().initialize(structuredLogging, structuredLoggingFormat, structuredLoggingURL);
VMFactory::setKind(jit ? VMKind::JIT : VMKind::Interpreter);
auto netPrefs = publicIP.empty() ? NetworkPreferences(listenIP ,listenPort, upnp) : NetworkPreferences(publicIP, listenIP ,listenPort, upnp);
@ -1042,13 +1072,38 @@ int main(int argc, char** argv)
clientImplString,
dbPath,
killChain,
nodeMode == NodeMode::Full ? set<string>{"eth", "shh"} : set<string>(),
nodeMode == NodeMode::Full ? set<string>{"eth"/*, "shh"*/} : set<string>(),
netPrefs,
&nodesState);
if (mode == OperationMode::DAGInit)
doInitDAG(web3.ethereum()->blockChain().number() + (initDAG == PendingBlock ? 30000 : 0));
if (keyManager.exists())
while (masterPassword.empty())
{
masterPassword = getPassword("Please enter your MASTER password: ");
if (!keyManager.load(masterPassword))
{
cout << "Password invalid. Try again." << endl;
masterPassword.clear();
}
}
else
{
while (masterPassword.empty())
{
masterPassword = getPassword("Please enter a MASTER password to protect your key store (make it strong!): ");
string confirm = getPassword("Please confirm the password by entering it again: ");
if (masterPassword != confirm)
{
cout << "Passwords were different. Try again." << endl;
masterPassword.clear();
}
}
keyManager.create(masterPassword);
}
auto toNumber = [&](string const& s) -> unsigned {
if (s == "latest")
return web3.ethereum()->number();
@ -1137,53 +1192,13 @@ int main(int argc, char** argv)
if (remoteHost.size())
web3.addNode(p2p::NodeId(), remoteHost + ":" + toString(remotePort));
if (keyManager.exists())
while (masterPassword.empty())
{
masterPassword = getPassword("Please enter your MASTER password: ");
if (!keyManager.load(masterPassword))
{
cout << "Password invalid. Try again." << endl;
masterPassword.clear();
}
}
else
{
while (masterPassword.empty())
{
masterPassword = getPassword("Please enter a MASTER password to protect your key store (make it strong!): ");
string confirm = getPassword("Please confirm the password by entering it again: ");
if (masterPassword != confirm)
{
cout << "Passwords were different. Try again." << endl;
masterPassword.clear();
}
}
keyManager.create(masterPassword);
}
string logbuf;
bool silence = false;
std::string additional;
g_logPost = [&](std::string const& a, char const*) { if (silence) logbuf += a + "\n"; else cout << "\r \r" << a << endl << additional << flush; };
// TODO: give hints &c.
auto getPassword = [&](Address const& a){
auto s = silence;
silence = true;
cout << endl;
string ret = dev::getPassword("Enter password for address " + keyManager.accountDetails()[a].first + " (" + a.abridged() + "; hint:" + keyManager.accountDetails()[a].second + "): ");
silence = s;
return ret;
};
#if ETH_JSONRPC || !ETH_TRUE
shared_ptr<WebThreeStubServer> jsonrpcServer;
unique_ptr<jsonrpc::AbstractServerConnector> jsonrpcConnector;
if (jsonrpc > -1)
{
jsonrpcConnector = unique_ptr<jsonrpc::AbstractServerConnector>(new jsonrpc::HttpServer(jsonrpc, "", "", SensibleHttpThreads));
jsonrpcServer = shared_ptr<WebThreeStubServer>(new WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared<SimpleAccountHolder>([&](){return web3.ethereum();}, getPassword, keyManager), vector<KeyPair>()));
jsonrpcServer = shared_ptr<WebThreeStubServer>(new WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared<SimpleAccountHolder>([&](){return web3.ethereum();}, getAccountPassword, keyManager), vector<KeyPair>()));
jsonrpcServer->StartListening();
}
#endif
@ -1327,7 +1342,7 @@ int main(int argc, char** argv)
if (jsonrpc < 0)
jsonrpc = SensibleHttpPort;
jsonrpcConnector = unique_ptr<jsonrpc::AbstractServerConnector>(new jsonrpc::HttpServer(jsonrpc, "", "", SensibleHttpThreads));
jsonrpcServer = shared_ptr<WebThreeStubServer>(new WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared<SimpleAccountHolder>([&](){return web3.ethereum();}, getPassword, keyManager), vector<KeyPair>()));
jsonrpcServer = shared_ptr<WebThreeStubServer>(new WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared<SimpleAccountHolder>([&](){return web3.ethereum();}, getAccountPassword, keyManager), vector<KeyPair>()));
jsonrpcServer->StartListening();
}
else if (cmd == "jsonstop")
@ -1479,7 +1494,7 @@ int main(int argc, char** argv)
try
{
Address dest = h160(fromHex(hexAddr, WhenError::Throw));
c->submitTransaction(keyManager.secret(signingKey, [&](){ return getPassword(signingKey); }), amount, dest, bytes(), minGas);
c->submitTransaction(keyManager.secret(signingKey, [&](){ return getAccountPassword(signingKey); }), amount, dest, bytes(), minGas);
}
catch (BadHexCharacter& _e)
{
@ -1548,7 +1563,7 @@ int main(int argc, char** argv)
else if (gas < minGas)
cwarn << "Minimum gas amount is" << minGas;
else
c->submitTransaction(keyManager.secret(signingKey, [&](){ return getPassword(signingKey); }), endowment, init, gas, gasPrice);
c->submitTransaction(keyManager.secret(signingKey, [&](){ return getAccountPassword(signingKey); }), endowment, init, gas, gasPrice);
}
else
cwarn << "Require parameters: contract ENDOWMENT GASPRICE GAS CODEHEX";

2
libdevcore/Common.cpp

@ -28,7 +28,7 @@ using namespace dev;
namespace dev
{
char const* Version = "0.9.18";
char const* Version = "0.9.19";
const u256 UndefinedU256 = ~(u256)0;

8
libdevcore/Common.h

@ -199,12 +199,12 @@ private:
#define DEV_TIMED_FUNCTION DEV_TIMED_SCOPE(__PRETTY_FUNCTION__)
#endif
#define DEV_TIMED_IF(S, MS) for (::std::pair<::dev::TimerHelper, bool> __eth_t(::dev::TimerHelper(#S, MS), true); __eth_t.second; __eth_t.second = false)
#define DEV_TIMED_SCOPE_IF(S) ::dev::TimerHelper __eth_t(S, MS)
#define DEV_TIMED_ABOVE(S, MS) for (::std::pair<::dev::TimerHelper, bool> __eth_t(::dev::TimerHelper(#S, MS), true); __eth_t.second; __eth_t.second = false)
#define DEV_TIMED_SCOPE_ABOVE(S) ::dev::TimerHelper __eth_t(S, MS)
#if WIN32
#define DEV_TIMED_FUNCTION_IF(MS) DEV_TIMED_SCOPE_IF(__FUNCSIG__, MS)
#define DEV_TIMED_FUNCTION_ABOVE(MS) DEV_TIMED_SCOPE_ABOVE(__FUNCSIG__, MS)
#else
#define DEV_TIMED_FUNCTION_IF(MS) DEV_TIMED_SCOPE_IF(__PRETTY_FUNCTION__, MS)
#define DEV_TIMED_FUNCTION_ABOVE(MS) DEV_TIMED_SCOPE_ABOVE(__PRETTY_FUNCTION__, MS)
#endif
enum class WithExisting: int

10
libdevcore/Worker.cpp

@ -65,15 +65,15 @@ void Worker::startWorking()
m_state.exchange(ex);
// cnote << "Waiting until not Stopped...";
DEV_TIMED_IF(Worker stopping, 100)
DEV_TIMED_ABOVE(Worker stopping, 100)
while (m_state == WorkerState::Stopped)
this_thread::sleep_for(chrono::milliseconds(20));
}
}));
// cnote << "Spawning" << m_name;
}
DEV_TIMED_IF(Start worker, 100)
while (m_state != WorkerState::Started)
DEV_TIMED_ABOVE(Start worker, 100)
while (m_state == WorkerState::Starting)
this_thread::sleep_for(chrono::microseconds(20));
}
@ -85,7 +85,7 @@ void Worker::stopWorking()
WorkerState ex = WorkerState::Started;
m_state.compare_exchange_strong(ex, WorkerState::Stopping);
DEV_TIMED_IF(Stop worker, 100)
DEV_TIMED_ABOVE(Stop worker, 100)
while (m_state != WorkerState::Stopped)
this_thread::sleep_for(chrono::microseconds(20));
}
@ -99,7 +99,7 @@ void Worker::terminate()
{
m_state.exchange(WorkerState::Killing);
DEV_TIMED_IF(Terminate worker, 100)
DEV_TIMED_ABOVE(Terminate worker, 100)
m_work->join();
m_work.reset();

34
libdevcrypto/MemoryDB.cpp

@ -32,6 +32,7 @@ const char* DBWarn::name() { return "TDB"; }
std::unordered_map<h256, std::string> MemoryDB::get() const
{
ReadGuard l(x_this);
std::unordered_map<h256, std::string> ret;
for (auto const& i: m_main)
if (!m_enforceRefs || i.second.second > 0)
@ -39,21 +40,34 @@ std::unordered_map<h256, std::string> MemoryDB::get() const
return ret;
}
MemoryDB& MemoryDB::operator=(MemoryDB const& _c)
{
if (this == &_c)
return *this;
ReadGuard l(_c.x_this);
WriteGuard l2(x_this);
m_main = _c.m_main;
m_aux = _c.m_aux;
return *this;
}
std::string MemoryDB::lookup(h256 const& _h) const
{
ReadGuard l(x_this);
auto it = m_main.find(_h);
if (it != m_main.end())
{
if (!m_enforceRefs || it->second.second > 0)
return it->second.first;
// else if (m_enforceRefs && m_refCount.count(it->first) && !m_refCount.at(it->first))
// cnote << "Lookup required for value with no refs. Let's hope it's in the DB." << _h;
else
cwarn << "Lookup required for value with refcount == 0. This is probably a critical trie issue" << _h;
}
return std::string();
}
bool MemoryDB::exists(h256 const& _h) const
{
ReadGuard l(x_this);
auto it = m_main.find(_h);
if (it != m_main.end() && (!m_enforceRefs || it->second.second > 0))
return true;
@ -62,6 +76,7 @@ bool MemoryDB::exists(h256 const& _h) const
void MemoryDB::insert(h256 const& _h, bytesConstRef _v)
{
WriteGuard l(x_this);
auto it = m_main.find(_h);
if (it != m_main.end())
{
@ -77,34 +92,34 @@ void MemoryDB::insert(h256 const& _h, bytesConstRef _v)
bool MemoryDB::kill(h256 const& _h)
{
ReadGuard l(x_this);
if (m_main.count(_h))
{
if (m_main[_h].second > 0)
{
m_main[_h].second--;
return true;
}
#if ETH_PARANOIA
else
{
// If we get to this point, then there was probably a node in the level DB which we need to remove and which we have previously
// used as part of the memory-based MemoryDB. Nothing to be worried about *as long as the node exists in the DB*.
dbdebug << "NOKILL-WAS" << _h;
return false;
}
dbdebug << "KILL" << _h << "=>" << m_main[_h].second;
return true;
}
else
{
dbdebug << "NOKILL" << _h;
return false;
}
#else
}
return true;
#endif
}
return false;
}
void MemoryDB::purge()
{
WriteGuard l(x_this);
for (auto it = m_main.begin(); it != m_main.end(); )
if (it->second.second)
++it;
@ -114,6 +129,7 @@ void MemoryDB::purge()
h256Hash MemoryDB::keys() const
{
ReadGuard l(x_this);
h256Hash ret;
for (auto const& i: m_main)
if (i.second.second)

11
libdevcrypto/MemoryDB.h

@ -23,6 +23,7 @@
#include <unordered_map>
#include <libdevcore/Common.h>
#include <libdevcore/Guards.h>
#include <libdevcore/FixedHash.h>
#include <libdevcore/Log.h>
#include <libdevcore/RLP.h>
@ -43,6 +44,9 @@ class MemoryDB
public:
MemoryDB() {}
MemoryDB(MemoryDB const& _c) { operator=(_c); }
MemoryDB& operator=(MemoryDB const& _c);
void clear() { m_main.clear(); } // WARNING !!!! didn't originally clear m_refCount!!!
std::unordered_map<h256, std::string> get() const;
@ -53,13 +57,14 @@ public:
bool kill(h256 const& _h);
void purge();
bytes lookupAux(h256 const& _h) const { try { return m_aux.at(_h).first; } catch (...) { return bytes(); } }
void removeAux(h256 const& _h) { m_aux[_h].second = false; }
void insertAux(h256 const& _h, bytesConstRef _v) { m_aux[_h] = make_pair(_v.toBytes(), true); }
bytes lookupAux(h256 const& _h) const { ReadGuard l(x_this); auto it = m_aux.find(_h); if (it != m_aux.end() && (!m_enforceRefs || it->second.second)) return it->second.first; return bytes(); }
void removeAux(h256 const& _h) { WriteGuard l(x_this); m_aux[_h].second = false; }
void insertAux(h256 const& _h, bytesConstRef _v) { WriteGuard l(x_this); m_aux[_h] = make_pair(_v.toBytes(), true); }
h256Hash keys() const;
protected:
mutable SharedMutex x_this;
std::unordered_map<h256, std::pair<std::string, unsigned>> m_main;
std::unordered_map<h256, std::pair<bytes, bool>> m_aux;

57
libdevcrypto/OverlayDB.cpp

@ -19,6 +19,7 @@
* @date 2014
*/
#include <thread>
#include <leveldb/db.h>
#include <leveldb/write_batch.h>
#include <libdevcore/Common.h>
@ -29,6 +30,8 @@ using namespace dev;
namespace dev
{
h256 const EmptyTrie = sha3(rlp(""));
OverlayDB::~OverlayDB()
{
if (m_db.use_count() == 1 && m_db.get())
@ -41,30 +44,41 @@ void OverlayDB::commit()
{
ldb::WriteBatch batch;
// cnote << "Committing nodes to disk DB:";
for (auto const& i: m_main)
DEV_READ_GUARDED(x_this)
{
// cnote << i.first << "#" << m_main[i.first].second;
if (i.second.second)
batch.Put(ldb::Slice((char const*)i.first.data(), i.first.size), ldb::Slice(i.second.first.data(), i.second.first.size()));
}
for (auto const& i: m_aux)
if (i.second.second)
for (auto const& i: m_main)
{
bytes b = i.first.asBytes();
b.push_back(255); // for aux
batch.Put(bytesConstRef(&b), bytesConstRef(&i.second.first));
if (i.second.second)
batch.Put(ldb::Slice((char const*)i.first.data(), i.first.size), ldb::Slice(i.second.first.data(), i.second.first.size()));
// cnote << i.first << "#" << m_main[i.first].second;
}
m_db->Write(m_writeOptions, &batch);
for (auto const& i: m_aux)
if (i.second.second)
{
bytes b = i.first.asBytes();
b.push_back(255); // for aux
batch.Put(bytesConstRef(&b), bytesConstRef(&i.second.first));
}
}
while (true)
{
ldb::Status o = m_db->Write(m_writeOptions, &batch);
if (o.ok())
break;
cwarn << "Error writing to database. Sleeping a while then retrying. If it keeps saying this, free up some space!";
this_thread::sleep_for(chrono::milliseconds(500));
}
m_aux.clear();
m_main.clear();
}
}
bytes OverlayDB::lookupAux(h256 _h) const
bytes OverlayDB::lookupAux(h256 const& _h) const
{
bytes ret = MemoryDB::lookupAux(_h);
if (!ret.empty())
return ret;
return move(ret);
std::string v;
bytes b = _h.asBytes();
b.push_back(255); // for aux
@ -76,18 +90,19 @@ bytes OverlayDB::lookupAux(h256 _h) const
void OverlayDB::rollback()
{
WriteGuard l(x_this);
m_main.clear();
}
std::string OverlayDB::lookup(h256 _h) const
std::string OverlayDB::lookup(h256 const& _h) const
{
std::string ret = MemoryDB::lookup(_h);
if (ret.empty() && m_db)
m_db->Get(m_readOptions, ldb::Slice((char const*)_h.data(), 32), &ret);
return ret;
return move(ret);
}
bool OverlayDB::exists(h256 _h) const
bool OverlayDB::exists(h256 const& _h) const
{
if (MemoryDB::exists(_h))
return true;
@ -97,16 +112,20 @@ bool OverlayDB::exists(h256 _h) const
return !ret.empty();
}
void OverlayDB::kill(h256 _h)
void OverlayDB::kill(h256 const& _h)
{
#if ETH_PARANOIA
#if ETH_PARANOIA || 1
if (!MemoryDB::kill(_h))
{
std::string ret;
if (m_db)
m_db->Get(m_readOptions, ldb::Slice((char const*)_h.data(), 32), &ret);
if (ret.empty())
// No point node ref decreasing for EmptyTrie since we never bother incrementing it in the first place for
// empty storage tries.
if (ret.empty() && _h != EmptyTrie)
cnote << "Decreasing DB node ref count below zero with no DB node. Probably have a corrupt Trie." << _h;
// TODO: for 1.1: ref-counted triedb.
}
#else
MemoryDB::kill(_h);

8
libdevcrypto/OverlayDB.h

@ -46,11 +46,11 @@ public:
void commit();
void rollback();
std::string lookup(h256 _h) const;
bool exists(h256 _h) const;
void kill(h256 _h);
std::string lookup(h256 const& _h) const;
bool exists(h256 const& _h) const;
void kill(h256 const& _h);
bytes lookupAux(h256 _h) const;
bytes lookupAux(h256 const& _h) const;
private:
using MemoryDB::clear;

2
libdevcrypto/TrieDB.cpp

@ -25,6 +25,6 @@ using namespace std;
using namespace dev;
h256 const dev::c_shaNull = sha3(rlp(""));
h256 const dev::EmptyTrie = c_shaNull;
h256 const dev::EmptyTrie = sha3(rlp(""));
const char* TrieDBChannel::name() { return "-T-"; }

36
libdevcrypto/TrieDB.h

@ -79,7 +79,7 @@ public:
void open(DB* _db) { m_db = _db; }
void open(DB* _db, h256 const& _root, Verification _v = Verification::Normal) { m_db = _db; setRoot(_root, _v); }
void init() { setRoot(insertNode(&RLPNull)); assert(node(m_root).size()); }
void init() { setRoot(forceInsertNode(&RLPNull)); assert(node(m_root).size()); }
void setRoot(h256 const& _root, Verification _v = Verification::Normal)
{
@ -88,11 +88,13 @@ public:
{
if (m_root == c_shaNull && !m_db->exists(m_root))
init();
/*std::cout << "Setting root to " << _root << " (patched to " << m_root << ")" << std::endl;*/
}
/*std::cout << "Setting root to " << _root << " (patched to " << m_root << ")" << std::endl;*/
#if ETH_DEBUG
if (_v == Verification::Normal)
#endif
if (!node(m_root).size())
BOOST_THROW_EXCEPTION(RootNotFound());
}
}
/// True if the trie is uninitialised (i.e. that the DB doesn't contain the root node).
@ -282,11 +284,17 @@ private:
std::string deref(RLP const& _n) const;
std::string node(h256 _h) const { return m_db->lookup(_h); }
void insertNode(h256 _h, bytesConstRef _v) { m_db->insert(_h, _v); }
void killNode(h256 _h) { m_db->kill(_h); }
h256 insertNode(bytesConstRef _v) { auto h = sha3(_v); insertNode(h, _v); return h; }
void killNode(RLP const& _d) { if (_d.data().size() >= 32) killNode(sha3(_d.data())); }
// These are low-level node insertion functions that just go straight through into the DB.
h256 forceInsertNode(bytesConstRef _v) { auto h = sha3(_v); forceInsertNode(h, _v); return h; }
void forceInsertNode(h256 _h, bytesConstRef _v) { m_db->insert(_h, _v); }
void forceKillNode(h256 _h) { m_db->kill(_h); }
// This are semantically-aware node insertion functions that only kills when the node's
// data is < 32 bytes. It can safely be used when pruning the trie but won't work correctly
// for the special case of the root (which is always looked up via a hash). In that case,
// use forceKillNode().
void killNode(RLP const& _d) { if (_d.data().size() >= 32) forceKillNode(sha3(_d.data())); }
h256 m_root;
DB* m_db = nullptr;
@ -743,8 +751,8 @@ template <class DB> void GenericTrieDB<DB>::insert(bytesConstRef _key, bytesCons
// However, we know it's the root node and thus always hashed.
// So, if it's less than 32 (and thus should have been deleted but wasn't) then we delete it here.
if (rv.size() < 32)
killNode(m_root);
m_root = insertNode(&b);
forceKillNode(m_root);
m_root = forceInsertNode(&b);
}
template <class DB> std::string GenericTrieDB<DB>::at(bytesConstRef _key) const
@ -890,8 +898,8 @@ template <class DB> void GenericTrieDB<DB>::remove(bytesConstRef _key)
if (b.size())
{
if (rv.size() < 32)
killNode(m_root);
m_root = insertNode(&b);
forceKillNode(m_root);
m_root = forceInsertNode(&b);
}
}
@ -1081,7 +1089,7 @@ template <class DB> RLPStream& GenericTrieDB<DB>::streamNode(RLPStream& _s, byte
if (_b.size() < 32)
_s.appendRaw(_b);
else
_s.append(insertNode(&_b));
_s.append(forceInsertNode(&_b));
return _s;
}
@ -1122,7 +1130,7 @@ template <class DB> bytes GenericTrieDB<DB>::graft(RLP const& _orig)
// remove second item from the trie after derefrencing it into s & n.
auto lh = _orig[1].toHash<h256>();
s = node(lh);
killNode(lh);
forceKillNode(lh);
n = RLP(s);
}
assert(n.itemCount() == 2);

6
libethcore/Ethash.cpp

@ -76,9 +76,9 @@ Ethash::WorkPackage Ethash::package(BlockInfo const& _bi)
return ret;
}
void Ethash::prep(BlockInfo const& _header)
void Ethash::prep(BlockInfo const& _header, std::function<int(unsigned)> const& _f)
{
EthashAux::full(_header);
EthashAux::full((unsigned)_header.number, _f);
}
bool Ethash::preVerify(BlockInfo const& _header)
@ -310,6 +310,8 @@ void Ethash::GPUMiner::workLoop()
unsigned device = instances() > 1 ? index() : s_deviceId;
if (!EthashAux::computeFull(w.blockNumber))
return;
EthashAux::FullType dag = EthashAux::full(w.blockNumber);
bytesConstRef dagData = dag->data();
m_miner->init(dagData.data(), dagData.size(), 32, s_platformId, device);

2
libethcore/Ethash.h

@ -74,7 +74,7 @@ public:
static std::string name();
static unsigned revision();
static void prep(BlockInfo const& _header);
static void prep(BlockInfo const& _header, std::function<int(unsigned)> const& _f = std::function<int(unsigned)>());
static bool verify(BlockInfo const& _header);
static bool preVerify(BlockInfo const& _header);
static WorkPackage package(BlockInfo const& _header);

81
libethcore/EthashAux.cpp

@ -41,6 +41,8 @@ using namespace chrono;
using namespace dev;
using namespace eth;
const char* DAGChannel::name() { return EthGreen "DAG"; }
EthashAux* dev::eth::EthashAux::s_this = nullptr;
EthashAux::~EthashAux()
@ -76,9 +78,30 @@ h256 EthashAux::seedHash(unsigned _number)
return get()->m_seedHashes[epoch];
}
uint64_t EthashAux::number(h256 const& _seedHash)
{
Guard l(get()->x_epochs);
unsigned epoch = 0;
auto epochIter = get()->m_epochs.find(_seedHash);
if (epochIter == get()->m_epochs.end())
{
// cdebug << "Searching for seedHash " << _seedHash;
for (h256 h; h != _seedHash && epoch < 2048; ++epoch, h = sha3(h), get()->m_epochs[h] = epoch) {}
if (epoch == 2048)
{
std::ostringstream error;
error << "apparent block number for " << _seedHash << " is too high; max is " << (ETHASH_EPOCH_LENGTH * 2048);
throw std::invalid_argument(error.str());
}
}
else
epoch = epochIter->second;
return epoch * ETHASH_EPOCH_LENGTH;
}
void EthashAux::killCache(h256 const& _s)
{
RecursiveGuard l(x_this);
RecursiveGuard l(x_lights);
m_lights.erase(_s);
}
@ -89,7 +112,7 @@ EthashAux::LightType EthashAux::light(BlockInfo const& _header)
EthashAux::LightType EthashAux::light(uint64_t _blockNumber)
{
RecursiveGuard l(get()->x_this);
RecursiveGuard l(get()->x_lights);
h256 seedHash = EthashAux::seedHash(_blockNumber);
LightType ret = get()->m_lights[seedHash];
return ret ? ret : (get()->m_lights[seedHash] = make_shared<LightAllocation>(_blockNumber));
@ -126,32 +149,56 @@ bytesConstRef EthashAux::FullAllocation::data() const
return bytesConstRef((byte const*)ethash_full_dag(full), size());
}
EthashAux::FullType EthashAux::full(BlockInfo const& _header)
static std::function<int(unsigned)> s_dagCallback;
static int dagCallbackShim(unsigned _p)
{
return full((uint64_t) _header.number);
clog(DAGChannel) << "Generating DAG file. Progress: " << toString(_p) << "%";
return s_dagCallback ? s_dagCallback(_p) : 0;
}
struct DAGChannel: public LogChannel { static const char* name(); static const int verbosity = 0; };
const char* DAGChannel::name() { return EthGreen "DAG"; }
static int ethash_callback(unsigned int _progress)
EthashAux::FullType EthashAux::full(uint64_t _blockNumber, function<int(unsigned)> const& _f)
{
clog(DAGChannel) << "Generating DAG file. Progress: " << toString(_progress) << "%";
return 0;
auto l = light(_blockNumber);
h256 seedHash = EthashAux::seedHash(_blockNumber);
FullType ret;
DEV_GUARDED(get()->x_fulls)
if ((ret = get()->m_fulls[seedHash].lock()))
{
get()->m_lastUsedFull = ret;
return ret;
}
s_dagCallback = _f;
ret = make_shared<FullAllocation>(l->light, dagCallbackShim);
DEV_GUARDED(get()->x_fulls)
get()->m_fulls[seedHash] = get()->m_lastUsedFull = ret;
return ret;
}
EthashAux::FullType EthashAux::full(uint64_t _blockNumber)
unsigned EthashAux::computeFull(uint64_t _blockNumber)
{
RecursiveGuard l(get()->x_this);
Guard l(get()->x_fulls);
h256 seedHash = EthashAux::seedHash(_blockNumber);
FullType ret;
if ((ret = get()->m_fulls[seedHash].lock()))
if (FullType ret = get()->m_fulls[seedHash].lock())
{
get()->m_lastUsedFull = ret;
return ret;
return 100;
}
ret = get()->m_lastUsedFull = make_shared<FullAllocation>(light(_blockNumber)->light, ethash_callback);
get()->m_fulls[seedHash] = ret;
return ret;
if (!get()->m_fullGenerator || !get()->m_fullGenerator->joinable())
{
get()->m_fullProgress = 0;
get()->m_generatingFullNumber = _blockNumber / ETHASH_EPOCH_LENGTH * ETHASH_EPOCH_LENGTH;
get()->m_fullGenerator = unique_ptr<thread>(new thread([=](){
get()->full(_blockNumber, [](unsigned p){ get()->m_fullProgress = p; return 0; });
get()->m_fullProgress = 0;
get()->m_generatingFullNumber = NotGenerating;
}));
}
return (get()->m_generatingFullNumber == _blockNumber) ? get()->m_fullProgress : 0;
}
Ethash::Result EthashAux::FullAllocation::compute(h256 const& _headerHash, Nonce const& _nonce) const

26
libethcore/EthashAux.h

@ -19,13 +19,17 @@
* @date 2014
*/
#include <condition_variable>
#include <libethash/ethash.h>
#include <libdevcore/Worker.h>
#include "Ethash.h"
namespace dev
{
namespace eth{
namespace eth
{
struct DAGChannel: public LogChannel { static const char* name(); static const int verbosity = 1; };
class EthashAux
{
@ -58,29 +62,41 @@ public:
using FullType = std::shared_ptr<FullAllocation>;
static h256 seedHash(unsigned _number);
static uint64_t number(h256 const& _seedHash);
static uint64_t cacheSize(BlockInfo const& _header);
static LightType light(BlockInfo const& _header);
static LightType light(uint64_t _blockNumber);
static FullType full(BlockInfo const& _header);
static FullType full(uint64_t _blockNumber);
static const uint64_t NotGenerating = (uint64_t)-1;
/// Kicks off generation of DAG for @a _blocknumber and @returns false or @returns true if ready.
static unsigned computeFull(uint64_t _blockNumber);
/// Information on the generation progress.
static std::pair<uint64_t, unsigned> fullGeneratingProgress() { return std::make_pair(get()->m_generatingFullNumber, get()->m_fullProgress); }
/// Kicks off generation of DAG for @a _blocknumber and blocks until ready; @returns result.
static FullType full(uint64_t _blockNumber, std::function<int(unsigned)> const& _f = std::function<int(unsigned)>());
static Ethash::Result eval(BlockInfo const& _header) { return eval(_header, _header.nonce); }
static Ethash::Result eval(BlockInfo const& _header, Nonce const& _nonce);
static Ethash::Result eval(uint64_t _blockNumber, h256 const& _headerHash, Nonce const& _nonce);
private:
EthashAux() {}
void killCache(h256 const& _s);
static EthashAux* s_this;
RecursiveMutex x_this;
RecursiveMutex x_lights;
std::unordered_map<h256, std::shared_ptr<LightAllocation>> m_lights;
Mutex x_fulls;
std::condition_variable m_fullsChanged;
std::unordered_map<h256, std::weak_ptr<FullAllocation>> m_fulls;
FullType m_lastUsedFull;
std::unique_ptr<std::thread> m_fullGenerator;
uint64_t m_generatingFullNumber = NotGenerating;
unsigned m_fullProgress;
Mutex x_epochs;
std::unordered_map<h256, unsigned> m_epochs;

9
libethereum/BlockChain.cpp

@ -466,7 +466,14 @@ ImportRoute BlockChain::import(bytes const& _block, OverlayDB const& _db, Import
blb.blooms.push_back(s.receipt(i).bloom());
br.receipts.push_back(s.receipt(i));
}
s.cleanup(true);
try {
s.cleanup(true);
}
catch (BadRoot)
{
cwarn << "BadRoot error. Retrying import later.";
BOOST_THROW_EXCEPTION(FutureTime());
}
td = pd.totalDifficulty + tdIncrease;

2
libethereum/Client.cpp

@ -164,7 +164,7 @@ const char* ClientDetail::name() { return EthTeal "⧫" EthCoal " ●"; }
#endif
Client::Client(p2p::Host* _extNet, std::string const& _dbPath, WithExisting _forceAction, u256 _networkId):
Worker("eth"),
Worker("eth", 0),
m_vc(_dbPath),
m_bc(_dbPath, max(m_vc.action(), _forceAction), [](unsigned d, unsigned t){ cerr << "REVISING BLOCKCHAIN: Processed " << d << " of " << t << "...\r"; }),
m_gp(new TrivialGasPricer),

4
libethereum/ClientBase.h

@ -84,11 +84,11 @@ public:
using Interface::submitTransaction;
/// Makes the given call. Nothing is recorded into the state.
virtual ExecutionResult call(Address const& _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, BlockNumber _blockNumber = PendingBlock, FudgeFactor _ff = FudgeFactor::Strict) override;
virtual ExecutionResult call(Address const& _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber, FudgeFactor _ff = FudgeFactor::Strict) override;
using Interface::call;
/// Makes the given create. Nothing is recorded into the state.
virtual ExecutionResult create(Address const& _secret, u256 _value, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, BlockNumber _blockNumber = PendingBlock, FudgeFactor _ff = FudgeFactor::Strict) override;
virtual ExecutionResult create(Address const& _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber, FudgeFactor _ff = FudgeFactor::Strict) override;
using Interface::create;
using Interface::balanceAt;

16
libethereum/State.cpp

@ -515,10 +515,10 @@ pair<TransactionReceipts, bool> State::sync(BlockChain const& _bc, TransactionQu
cnote << i.first << "Dropping old transaction (nonce too low)";
_tq.drop(i.first);
}
else if (got > req + 5)
else if (got > req + 25)
{
// too new
cnote << i.first << "Dropping new transaction (> 5 nonces ahead)";
cnote << i.first << "Dropping new transaction (> 25 nonces ahead)";
_tq.drop(i.first);
}
else
@ -707,11 +707,21 @@ void State::cleanup(bool _fullCommit)
{
if (_fullCommit)
{
paranoia("immediately before database commit", true);
// Commit the new trie to disk.
clog(StateTrace) << "Committing to disk: stateRoot" << m_currentBlock.stateRoot << "=" << rootHash() << "=" << toHex(asBytes(m_db.lookup(rootHash())));
try {
EnforceRefs er(m_db, true);
rootHash();
}
catch (BadRoot const&)
{
clog(StateChat) << "Trie corrupt! :-(";
throw;
}
m_db.commit();
clog(StateTrace) << "Committed: stateRoot" << m_currentBlock.stateRoot << "=" << rootHash() << "=" << toHex(asBytes(m_db.lookup(rootHash())));

80
libevmasm/Assembly.cpp

@ -24,6 +24,7 @@
#include <libdevcore/Log.h>
#include <libevmasm/CommonSubexpressionEliminator.h>
#include <libevmasm/ControlFlowGraph.h>
#include <libevmasm/BlockDeduplicator.h>
#include <json/json.h>
using namespace std;
using namespace dev;
@ -311,54 +312,54 @@ Assembly& Assembly::optimise(bool _enable)
copt << toString(*this);
count = 0;
//@todo CFG interface should be a generator, that returns an item and a pointer to a
// knownstate, which has to replace the current state if it is not null.
// Feed these items to the CSE, but also store them and replace the stored version
// if the items generated by the CSE are shorter. (or even use less gas?)
copt << "Performing control flow analysis...";
copt << "Performing optimisation...";
{
ControlFlowGraph cfg(m_items);
AssemblyItems optItems;
AssemblyItems optimisedItems;
for (BasicBlock const& block: cfg.optimisedBlocks())
copy(m_items.begin() + block.begin, m_items.begin() + block.end,
back_inserter(optItems));
if (optItems.size() < m_items.size())
{
copt << "Old size: " << m_items.size() << ", new size: " << optItems.size();
m_items = move(optItems);
count++;
assertThrow(!!block.startState, OptimizerException, "");
CommonSubexpressionEliminator eliminator(*block.startState);
auto iter = m_items.begin() + block.begin;
auto const end = m_items.begin() + block.end;
while (iter < end)
{
auto orig = iter;
iter = eliminator.feedItems(iter, end);
bool shouldReplace = false;
AssemblyItems optimisedChunk;
try
{
optimisedChunk = eliminator.getOptimizedItems();
shouldReplace = (optimisedChunk.size() < size_t(iter - orig));
}
catch (StackTooDeepException const&)
{
// This might happen if the opcode reconstruction is not as efficient
// as the hand-crafted code.
}
if (shouldReplace)
{
copt << "Old size: " << (iter - orig) << ", new size: " << optimisedChunk.size();
count++;
optimisedItems += optimisedChunk;
}
else
copy(orig, iter, back_inserter(optimisedItems));
}
}
}
copt << "Performing common subexpression elimination...";
for (auto iter = m_items.begin(); iter != m_items.end();)
{
//@todo use only a single state / expression classes instance.
KnownState state(make_shared<ExpressionClasses>());
CommonSubexpressionEliminator eliminator(state);
auto orig = iter;
iter = eliminator.feedItems(iter, m_items.end());
AssemblyItems optItems;
bool shouldReplace = false;
try
if (optimisedItems.size() < m_items.size())
{
optItems = eliminator.getOptimizedItems();
shouldReplace = (optItems.size() < size_t(iter - orig));
}
catch (StackTooDeepException const&)
{
// This might happen if the opcode reconstruction is not as efficient
// as the hand-crafted code.
m_items = move(optimisedItems);
count++;
}
if (shouldReplace)
{
copt << "Old size: " << (iter - orig) << ", new size: " << optItems.size();
// This only modifies PushTags, we have to run again to actually remove code.
BlockDeduplicator dedup(m_items);
if (dedup.deduplicate())
count++;
for (auto moveIter = optItems.begin(); moveIter != optItems.end(); ++orig, ++moveIter)
*orig = move(*moveIter);
iter = m_items.erase(orig, iter);
}
}
}
@ -461,7 +462,8 @@ bytes Assembly::assemble() const
for (auto const& i: tagRef)
{
bytesRef r(ret.data() + i.first, bytesPerTag);
toBigEndian(tagPos[i.second], r);
//@todo in the failure case, we could use the position of the invalid jumpdest
toBigEndian(i.second < tagPos.size() ? tagPos[i.second] : (1 << (8 * bytesPerTag)) - 1, r);
}
if (!m_data.empty())

2
libevmasm/AssemblyItem.h

@ -68,6 +68,8 @@ public:
/// @returns true iff the type and data of the items are equal.
bool operator==(AssemblyItem const& _other) const { return m_type == _other.m_type && m_data == _other.m_data; }
bool operator!=(AssemblyItem const& _other) const { return !operator==(_other); }
/// Less-than operator compatible with operator==.
bool operator<(AssemblyItem const& _other) const { return std::tie(m_type, m_data) < std::tie(_other.m_type, _other.m_data); }
/// @returns an upper bound for the number of bytes required by this item, assuming that
/// the value of a jump tag takes @a _addressLength bytes.

91
libevmasm/BlockDeduplicator.cpp

@ -0,0 +1,91 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file BlockDeduplicator.cpp
* @author Christian <c@ethdev.com>
* @date 2015
* Unifies basic blocks that share content.
*/
#include <libevmasm/BlockDeduplicator.h>
#include <functional>
#include <libevmasm/AssemblyItem.h>
#include <libevmasm/SemanticInformation.h>
using namespace std;
using namespace dev;
using namespace dev::eth;
bool BlockDeduplicator::deduplicate()
{
// Compares indices based on the suffix that starts there, ignoring tags and stopping at
// opcodes that stop the control flow.
function<bool(size_t, size_t)> comparator = [&](size_t _i, size_t _j)
{
if (_i == _j)
return false;
BlockIterator first(m_items.begin() + _i, m_items.end());
BlockIterator second(m_items.begin() + _j, m_items.end());
BlockIterator end(m_items.end(), m_items.end());
if (first != end && (*first).type() == Tag)
++first;
if (second != end && (*second).type() == Tag)
++second;
return std::lexicographical_compare(first, end, second, end);
};
set<size_t, function<bool(size_t, size_t)>> blocksSeen(comparator);
map<u256, u256> tagReplacement;
for (size_t i = 0; i < m_items.size(); ++i)
{
if (m_items.at(i).type() != Tag)
continue;
auto it = blocksSeen.find(i);
if (it == blocksSeen.end())
blocksSeen.insert(i);
else
tagReplacement[m_items.at(i).data()] = m_items.at(*it).data();
}
bool ret = false;
for (AssemblyItem& item: m_items)
if (item.type() == PushTag && tagReplacement.count(item.data()))
{
ret = true;
item.setData(tagReplacement.at(item.data()));
}
return ret;
}
BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++()
{
if (it == end)
return *this;
if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem(eth::Instruction::JUMPI))
it = end;
else
{
++it;
while (it != end && it->type() == Tag)
++it;
}
return *this;
}

69
libevmasm/BlockDeduplicator.h

@ -0,0 +1,69 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file BlockDeduplicator.h
* @author Christian <c@ethdev.com>
* @date 2015
* Unifies basic blocks that share content.
*/
#pragma once
#include <cstddef>
#include <vector>
#include <functional>
namespace dev
{
namespace eth
{
class AssemblyItem;
using AssemblyItems = std::vector<AssemblyItem>;
/**
* Optimizer class to be used to unify blocks that share content.
* Modifies the passed vector in place.
*/
class BlockDeduplicator
{
public:
BlockDeduplicator(AssemblyItems& _items): m_items(_items) {}
/// @returns true if something was changed
bool deduplicate();
private:
/// Iterator that skips tags skips to the end if (all branches of) the control
/// flow does not continue to the next instruction.
struct BlockIterator: std::iterator<std::forward_iterator_tag, AssemblyItem const>
{
public:
BlockIterator(AssemblyItems::const_iterator _it, AssemblyItems::const_iterator _end):
it(_it), end(_end) { }
BlockIterator& operator++();
bool operator==(BlockIterator const& _other) const { return it == _other.it; }
bool operator!=(BlockIterator const& _other) const { return it != _other.it; }
AssemblyItem const& operator*() const { return *it; }
AssemblyItems::const_iterator it;
AssemblyItems::const_iterator end;
};
AssemblyItems& m_items;
};
}
}

113
libevmasm/CommonSubexpressionEliminator.cpp

@ -45,16 +45,22 @@ vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
for (int height = minHeight; height <= m_state.stackHeight(); ++height)
targetStackContents[height] = m_state.stackElement(height, SourceLocation());
// Debug info:
//stream(cout, initialStackContents, targetStackContents);
AssemblyItems items = CSECodeGenerator(m_state.expressionClasses(), m_storeOperations).generateCode(
m_initialState.stackHeight(),
initialStackContents,
targetStackContents
);
if (m_breakingItem)
{
items.push_back(*m_breakingItem);
m_state.feedItem(*m_breakingItem);
}
// cleanup
m_initialState = m_state;
m_breakingItem = nullptr;
m_storeOperations.clear();
return items;
}
@ -113,16 +119,14 @@ AssemblyItems CSECodeGenerator::generateCode(
{
m_stackHeight = _initialStackHeight;
m_stack = _initialStack;
m_targetStack = _targetStackContents;
for (auto const& item: m_stack)
if (!m_classPositions.count(item.second))
m_classPositions[item.second] = item.first;
// @todo: provide information about the positions of copies of class elements
m_classPositions[item.second].insert(item.first);
// generate the dependency graph starting from final storage and memory writes and target stack contents
for (auto const& p: m_storeOperations)
addDependencies(p.second.back().expression);
for (auto const& targetItem: _targetStackContents)
for (auto const& targetItem: m_targetStack)
{
m_finalClasses.insert(targetItem.second);
addDependencies(targetItem.second);
@ -141,13 +145,16 @@ AssemblyItems CSECodeGenerator::generateCode(
generateClassElement(seqAndId.second, true);
// generate the target stack elements
for (auto const& targetItem: _targetStackContents)
for (auto const& targetItem: m_targetStack)
{
int position = generateClassElement(targetItem.second);
assertThrow(position != c_invalidPosition, OptimizerException, "");
if (position == targetItem.first)
if (m_stack.count(targetItem.first) && m_stack.at(targetItem.first) == targetItem.second)
continue; // already there
generateClassElement(targetItem.second);
assertThrow(!m_classPositions[targetItem.second].empty(), OptimizerException, "");
if (m_classPositions[targetItem.second].count(targetItem.first))
continue;
SourceLocation const& location = m_expressionClasses.representative(targetItem.second).item->getLocation();
int position = classElementPosition(targetItem.second);
if (position < targetItem.first)
// it is already at its target, we need another copy
appendDup(position, location);
@ -164,21 +171,24 @@ AssemblyItems CSECodeGenerator::generateCode(
// check validity
int finalHeight = 0;
if (!_targetStackContents.empty())
if (!m_targetStack.empty())
// have target stack, so its height should be the final height
finalHeight = (--_targetStackContents.end())->first;
finalHeight = (--m_targetStack.end())->first;
else if (!_initialStack.empty())
// no target stack, only erase the initial stack
finalHeight = _initialStack.begin()->first - 1;
else
// neither initial no target stack, no change in height
finalHeight = 0;
finalHeight = _initialStackHeight;
assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height.");
return m_generatedItems;
}
void CSECodeGenerator::addDependencies(Id _c)
{
if (m_classPositions.count(_c))
return; // it is already on the stack
if (m_neededBy.count(_c))
return; // we already computed the dependencies for _c
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c);
@ -254,19 +264,23 @@ void CSECodeGenerator::addDependencies(Id _c)
}
}
int CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
{
for (auto it: m_classPositions)
for (auto p: it.second)
if (p > m_stackHeight)
assertThrow(false, OptimizerException, "");
// do some cleanup
removeStackTopIfPossible();
if (m_classPositions.count(_c))
{
assertThrow(
m_classPositions[_c] != c_invalidPosition,
!m_classPositions[_c].empty(),
OptimizerException,
"Element already removed but still needed."
);
return m_classPositions[_c];
return;
}
ExpressionClasses::Expression const& expr = m_expressionClasses.representative(_c);
assertThrow(
@ -339,16 +353,16 @@ int CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
m_generatedItems.back() == AssemblyItem(Instruction::SWAP1))
// this will not append a swap but remove the one that is already there
appendOrRemoveSwap(m_stackHeight - 1, location);
for (auto arg: arguments)
if (canBeRemoved(arg, _c))
m_classPositions[arg] = c_invalidPosition;
for (size_t i = 0; i < arguments.size(); ++i)
{
m_classPositions[m_stack[m_stackHeight - i]].erase(m_stackHeight - i);
m_stack.erase(m_stackHeight - i);
}
appendItem(*expr.item);
if (expr.item->type() != Operation || instructionInfo(expr.item->instruction()).ret == 1)
{
m_stack[m_stackHeight] = _c;
return m_classPositions[_c] = m_stackHeight;
m_classPositions[_c].insert(m_stackHeight);
}
else
{
@ -357,31 +371,39 @@ int CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
OptimizerException,
"Invalid number of return values."
);
return m_classPositions[_c] = c_invalidPosition;
m_classPositions[_c]; // ensure it is created to mark the expression as generated
}
}
int CSECodeGenerator::classElementPosition(Id _id) const
{
assertThrow(
m_classPositions.count(_id) && m_classPositions.at(_id) != c_invalidPosition,
m_classPositions.count(_id) && !m_classPositions.at(_id).empty(),
OptimizerException,
"Element requested but is not present."
);
return m_classPositions.at(_id);
return *max_element(m_classPositions.at(_id).begin(), m_classPositions.at(_id).end());
}
bool CSECodeGenerator::canBeRemoved(Id _element, Id _result)
bool CSECodeGenerator::canBeRemoved(Id _element, Id _result, int _fromPosition)
{
// Returns false if _element is finally needed or is needed by a class that has not been
// computed yet. Note that m_classPositions also includes classes that were deleted in the meantime.
if (m_finalClasses.count(_element))
return false;
// Default for _fromPosition is the canonical position of the element.
if (_fromPosition == c_invalidPosition)
_fromPosition = classElementPosition(_element);
auto range = m_neededBy.equal_range(_element);
for (auto it = range.first; it != range.second; ++it)
if (it->second != _result && !m_classPositions.count(it->second))
return false;
bool haveCopy = m_classPositions.at(_element).size() > 1;
if (m_finalClasses.count(_element))
// It is part of the target stack. It can be removed if it is a copy that is not in the target position.
return haveCopy && (!m_targetStack.count(_fromPosition) || m_targetStack[_fromPosition] != _element);
else if (!haveCopy)
{
// Can be removed unless it is needed by a class that has not been computed yet.
// Note that m_classPositions also includes classes that were deleted in the meantime.
auto range = m_neededBy.equal_range(_element);
for (auto it = range.first; it != range.second; ++it)
if (it->second != _result && !m_classPositions.count(it->second))
return false;
}
return true;
}
@ -391,11 +413,11 @@ bool CSECodeGenerator::removeStackTopIfPossible()
return false;
assertThrow(m_stack.count(m_stackHeight) > 0, OptimizerException, "");
Id top = m_stack[m_stackHeight];
if (!canBeRemoved(top))
if (!canBeRemoved(top, Id(-1), m_stackHeight))
return false;
m_generatedItems.push_back(AssemblyItem(Instruction::POP));
m_classPositions[m_stack[m_stackHeight]].erase(m_stackHeight);
m_stack.erase(m_stackHeight);
m_stackHeight--;
appendItem(AssemblyItem(Instruction::POP));
return true;
}
@ -407,6 +429,7 @@ void CSECodeGenerator::appendDup(int _fromPosition, SourceLocation const& _locat
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(dupInstruction(instructionNum), _location));
m_stack[m_stackHeight] = m_stack[_fromPosition];
m_classPositions[m_stack[m_stackHeight]].insert(m_stackHeight);
}
void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, SourceLocation const& _location)
@ -418,13 +441,15 @@ void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, SourceLocation cons
assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep.");
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(swapInstruction(instructionNum), _location));
// The value of a class can be present in multiple locations on the stack. We only update the
// "canonical" one that is tracked by m_classPositions
if (m_classPositions[m_stack[m_stackHeight]] == m_stackHeight)
m_classPositions[m_stack[m_stackHeight]] = _fromPosition;
if (m_classPositions[m_stack[_fromPosition]] == _fromPosition)
m_classPositions[m_stack[_fromPosition]] = m_stackHeight;
swap(m_stack[m_stackHeight], m_stack[_fromPosition]);
if (m_stack[m_stackHeight] != m_stack[_fromPosition])
{
m_classPositions[m_stack[m_stackHeight]].erase(m_stackHeight);
m_classPositions[m_stack[m_stackHeight]].insert(_fromPosition);
m_classPositions[m_stack[_fromPosition]].erase(_fromPosition);
m_classPositions[m_stack[_fromPosition]].insert(m_stackHeight);
swap(m_stack[m_stackHeight], m_stack[_fromPosition]);
}
if (m_generatedItems.size() >= 2 &&
SemanticInformation::isSwapInstruction(m_generatedItems.back()) &&
*(m_generatedItems.end() - 2) == m_generatedItems.back())

22
libevmasm/CommonSubexpressionEliminator.h

@ -71,13 +71,6 @@ public:
/// @returns the resulting items after optimization.
AssemblyItems getOptimizedItems();
/// Streams debugging information to @a _out.
std::ostream& stream(
std::ostream& _out,
std::map<int, Id> _initialStack = std::map<int, Id>(),
std::map<int, Id> _targetStack = std::map<int, Id>()
) const;
private:
/// Feeds the item into the system for analysis.
void feedItem(AssemblyItem const& _item, bool _copyItem = false);
@ -126,16 +119,15 @@ private:
void addDependencies(Id _c);
/// Produce code that generates the given element if it is not yet present.
/// @returns the stack position of the element or c_invalidPosition if it does not actually
/// generate a value on the stack.
/// @param _allowSequenced indicates that sequence-constrained operations are allowed
int generateClassElement(Id _c, bool _allowSequenced = false);
void generateClassElement(Id _c, bool _allowSequenced = false);
/// @returns the position of the representative of the given id on the stack.
/// @note throws an exception if it is not on the stack.
int classElementPosition(Id _id) const;
/// @returns true if @a _element can be removed - in general or, if given, while computing @a _result.
bool canBeRemoved(Id _element, Id _result = Id(-1));
/// @returns true if the copy of @a _element can be removed from stack position _fromPosition
/// - in general or, if given, while computing @a _result.
bool canBeRemoved(Id _element, Id _result = Id(-1), int _fromPosition = c_invalidPosition);
/// Appends code to remove the topmost stack element if it can be removed.
bool removeStackTopIfPossible();
@ -157,8 +149,8 @@ private:
std::multimap<Id, Id> m_neededBy;
/// Current content of the stack.
std::map<int, Id> m_stack;
/// Current positions of equivalence classes, equal to c_invalidPosition if already deleted.
std::map<Id, int> m_classPositions;
/// Current positions of equivalence classes, equal to the empty set if already deleted.
std::map<Id, std::set<int>> m_classPositions;
/// The actual eqivalence class items and how to compute them.
ExpressionClasses& m_expressionClasses;
@ -167,6 +159,7 @@ private:
std::map<std::pair<StoreOperation::Target, Id>, StoreOperations> m_storeOperations;
/// The set of equivalence classes that should be present on the stack at the end.
std::set<Id> m_finalClasses;
std::map<int, Id> m_targetStack;
};
template <class _AssemblyItemIterator>
@ -175,6 +168,7 @@ _AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
_AssemblyItemIterator _end
)
{
assertThrow(!m_breakingItem, OptimizerException, "Invalid use of CommonSubexpressionEliminator.");
for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator); ++_iterator)
feedItem(*_iterator);
if (_iterator != _end)

22
libevmasm/ControlFlowGraph.cpp

@ -142,7 +142,7 @@ void ControlFlowGraph::removeUnusedBlocks()
BasicBlock const& block = m_blocks.at(blocksToProcess.back());
blocksToProcess.pop_back();
for (BlockId tag: block.pushedTags)
if (!neededBlocks.count(tag))
if (!neededBlocks.count(tag) && m_blocks.count(tag))
{
neededBlocks.insert(tag);
blocksToProcess.push_back(tag);
@ -191,12 +191,12 @@ void ControlFlowGraph::setPrevLinks()
if (push.type() != PushTag)
continue;
BlockId nextId(push.data());
if (m_blocks.at(nextId).prev)
if (m_blocks.count(nextId) && m_blocks.at(nextId).prev)
continue;
bool hasLoop = false;
for (BlockId id = nextId; id && !hasLoop; id = m_blocks.at(id).next)
for (BlockId id = nextId; id && m_blocks.count(id) && !hasLoop; id = m_blocks.at(id).next)
hasLoop = (id == blockId);
if (hasLoop)
if (hasLoop || !m_blocks.count(nextId))
continue;
m_blocks[nextId].prev = blockId;
@ -225,6 +225,8 @@ void ControlFlowGraph::gatherKnowledge()
{
//@todo we might have to do something like incrementing the sequence number for each JUMPDEST
assertThrow(!!workQueue.back().first, OptimizerException, "");
if (!m_blocks.count(workQueue.back().first))
continue; // too bad, we do not know the tag, probably an invalid jump
BasicBlock& block = m_blocks.at(workQueue.back().first);
KnownStatePointer state = workQueue.back().second;
workQueue.pop_back();
@ -281,6 +283,15 @@ void ControlFlowGraph::gatherKnowledge()
)
workQueue.push_back(make_pair(block.next, state->copy()));
}
// Remove all blocks we never visited here. This might happen because a tag is pushed but
// never used for a JUMP.
// Note that this invalidates some contents of pushedTags
for (auto it = m_blocks.begin(); it != m_blocks.end();)
if (!it->second.startState)
it = m_blocks.erase(it);
else
it++;
}
BasicBlocks ControlFlowGraph::rebuildCode()
@ -288,7 +299,8 @@ BasicBlocks ControlFlowGraph::rebuildCode()
map<BlockId, unsigned> pushes;
for (auto& idAndBlock: m_blocks)
for (BlockId ref: idAndBlock.second.pushedTags)
pushes[ref]++;
if (m_blocks.count(ref))
pushes[ref]++;
set<BlockId> blocksToAdd;
for (auto it: m_blocks)

52
libevmasm/KnownState.cpp

@ -160,23 +160,51 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
return op;
}
void KnownState::reduceToCommonKnowledge(KnownState const& /*_other*/)
/// Helper function for KnownState::reduceToCommonKnowledge, removes everything from
/// _this which is not in or not equal to the value in _other.
template <class _Mapping, class _KeyType> void intersect(
_Mapping& _this,
_Mapping const& _other,
function<_KeyType(_KeyType)> const& _keyTrans = [](_KeyType _k) { return _k; }
)
{
for (auto it = _this.begin(); it != _this.end();)
if (_other.count(_keyTrans(it->first)) && _other.at(_keyTrans(it->first)) == it->second)
++it;
else
it = _this.erase(it);
}
template <class _Mapping> void intersect(_Mapping& _this, _Mapping const& _other)
{
//@todo
*this = KnownState(m_expressionClasses);
intersect<_Mapping, ExpressionClasses::Id>(_this, _other, [](ExpressionClasses::Id _k) { return _k; });
}
void KnownState::reduceToCommonKnowledge(KnownState const& _other)
{
int stackDiff = m_stackHeight - _other.m_stackHeight;
function<int(int)> stackKeyTransform = [=](int _key) -> int { return _key - stackDiff; };
intersect(m_stackElements, _other.m_stackElements, stackKeyTransform);
// Use the smaller stack height. Essential to terminate in case of loops.
if (m_stackHeight > _other.m_stackHeight)
{
map<int, Id> shiftedStack;
for (auto const& stackElement: m_stackElements)
shiftedStack[stackElement.first - stackDiff] = stackElement.second;
m_stackElements = move(shiftedStack);
m_stackHeight = _other.m_stackHeight;
}
intersect(m_storageContent, _other.m_storageContent);
intersect(m_memoryContent, _other.m_memoryContent);
}
bool KnownState::operator==(const KnownState& _other) const
{
//@todo
return (
m_stackElements.empty() &&
_other.m_stackElements.empty() &&
m_storageContent.empty() &&
_other.m_storageContent.empty() &&
m_memoryContent.empty() &&
_other.m_memoryContent.empty()
);
return m_storageContent == _other.m_storageContent &&
m_memoryContent == _other.m_memoryContent &&
m_stackHeight == _other.m_stackHeight &&
m_stackElements == _other.m_stackElements;
}
ExpressionClasses::Id KnownState::stackElement(int _stackHeight, SourceLocation const& _location)

12
libsolidity/Compiler.cpp

@ -377,12 +377,16 @@ bool Compiler::visit(IfStatement const& _ifStatement)
StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _ifStatement);
compileExpression(_ifStatement.getCondition());
eth::AssemblyItem trueTag = m_context.appendConditionalJump();
m_context << eth::Instruction::ISZERO;
eth::AssemblyItem falseTag = m_context.appendConditionalJump();
eth::AssemblyItem endTag = falseTag;
_ifStatement.getTrueStatement().accept(*this);
if (_ifStatement.getFalseStatement())
{
endTag = m_context.appendJumpToNew();
m_context << falseTag;
_ifStatement.getFalseStatement()->accept(*this);
eth::AssemblyItem endTag = m_context.appendJumpToNew();
m_context << trueTag;
_ifStatement.getTrueStatement().accept(*this);
}
m_context << endTag;
checker.check();

74
mix/ClientModel.cpp

@ -194,7 +194,7 @@ QVariantMap ClientModel::contractAddresses() const
{
QVariantMap res;
for (auto const& c: m_contractAddresses)
res.insert(c.first, QString::fromStdString(toJS(c.second)));
res.insert(c.first.first, QString::fromStdString(toJS(c.second)));
return res;
}
@ -256,7 +256,9 @@ void ClientModel::setupState(QVariantMap _state)
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei();
u256 gasPrice = (qvariant_cast<QEther*>(transaction.value("gasPrice")))->toU256Wei();
QString sender = transaction.value("sender").toString();
bool isStdContract = (transaction.value("stdContract").toBool());
bool isStdContract = transaction.value("stdContract").toBool();
bool isContractCreation = transaction.value("isContractCreation").toBool();
bool isFunctionCall = transaction.value("isFunctionCall").toBool();
if (isStdContract)
{
if (contractId.isEmpty()) //TODO: This is to support old project files, remove later
@ -272,7 +274,7 @@ void ClientModel::setupState(QVariantMap _state)
{
if (contractId.isEmpty() && m_codeModel->hasContract()) //TODO: This is to support old project files, remove later
contractId = m_codeModel->contracts().keys()[0];
TransactionSettings transactionSettings(contractId, functionId, value, gas, gasAuto, gasPrice, Secret(sender.toStdString()));
TransactionSettings transactionSettings(contractId, functionId, value, gas, gasAuto, gasPrice, Secret(sender.toStdString()), isContractCreation, isFunctionCall);
transactionSettings.parameterValues = transaction.value("parameters").toMap();
if (contractId == functionId || functionId == "Constructor")
@ -308,6 +310,14 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
m_gasCosts.clear();
for (TransactionSettings const& transaction: _sequence)
{
std::pair<QString, int> ctrInstance = resolvePair(transaction.contractId);
QString address = resolveToken(ctrInstance, deployedContracts);
if (!transaction.isFunctionCall)
{
callAddress(Address(address.toStdString()), bytes(), transaction);
onNewTransaction();
continue;
}
ContractCallDataEncoder encoder;
if (!transaction.stdContractUrl.isEmpty())
{
@ -322,7 +332,7 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
else
{
//encode data
CompiledContract const& compilerRes = m_codeModel->contract(transaction.contractId);
CompiledContract const& compilerRes = m_codeModel->contract(ctrInstance.first);
QFunctionDefinition const* f = nullptr;
bytes contractCode = compilerRes.bytes();
shared_ptr<QContractDefinition> contractDef = compilerRes.sharedContract();
@ -348,32 +358,29 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
{
QSolidityType const* type = p->type();
QVariant value = transaction.parameterValues.value(p->name());
if (type->type().type == SolidityType::Type::Address && value.toString().startsWith("<") && value.toString().endsWith(">"))
if (type->type().type == SolidityType::Type::Address)
{
QStringList nb = value.toString().remove("<").remove(">").split(" - ");
value = QVariant(QString::fromStdString("0x" + dev::toHex(deployedContracts.at(nb.back().toInt()).ref())));
std::pair<QString, int> ctrParamInstance = resolvePair(value.toString());
value = QVariant(resolveToken(ctrParamInstance, deployedContracts));
}
encoder.encode(value, type->type());
}
if (transaction.functionId.isEmpty() || transaction.functionId == transaction.contractId)
if (transaction.functionId.isEmpty() || transaction.functionId == ctrInstance.first)
{
bytes param = encoder.encodedData();
contractCode.insert(contractCode.end(), param.begin(), param.end());
Address newAddress = deployContract(contractCode, transaction);
deployedContracts.push_back(newAddress);
auto contractAddressIter = m_contractAddresses.find(transaction.contractId);
if (contractAddressIter == m_contractAddresses.end() || newAddress != contractAddressIter->second)
{
m_contractAddresses[transaction.contractId] = newAddress;
m_contractNames[newAddress] = transaction.contractId;
contractAddressesChanged();
}
std::pair<QString, int> contractToken = retrieveToken(transaction.contractId, deployedContracts);
m_contractAddresses[contractToken] = newAddress;
m_contractNames[newAddress] = contractToken.first;
contractAddressesChanged();
gasCostsChanged();
}
else
{
auto contractAddressIter = m_contractAddresses.find(transaction.contractId);
auto contractAddressIter = m_contractAddresses.find(ctrInstance);
if (contractAddressIter == m_contractAddresses.end())
{
emit runFailed("Contract '" + transaction.contractId + tr(" not deployed.") + "' " + tr(" Cannot call ") + transaction.functionId);
@ -381,7 +388,7 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
emit runStateChanged();
return;
}
callContract(contractAddressIter->second, encoder.encodedData(), transaction);
callAddress(contractAddressIter->second, encoder.encodedData(), transaction);
}
m_gasCosts.append(m_client->lastExecution().gasUsed);
}
@ -405,6 +412,37 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
});
}
std::pair<QString, int> ClientModel::resolvePair(QString const& _contractId)
{
std::pair<QString, int> ret;
ret.first = _contractId;
ret.second = -1;
if (_contractId.startsWith("<") && _contractId.endsWith(">"))
{
QStringList values = ret.first.remove("<").remove(">").split(" - ");
ret.first = values[0];
ret.second = values[1].toUInt();
}
return ret;
}
QString ClientModel::resolveToken(std::pair<QString, int> const& _value, vector<Address> const& _contracts)
{
if (_value.second != -1)
return QString::fromStdString("0x" + dev::toHex(_contracts.at(_value.second).ref()));
else
return _value.first;
}
std::pair<QString, int> ClientModel::retrieveToken(QString const& _value, vector<Address> const& _contracts)
{
std::pair<QString, int> ret;
ret.first = _value;
ret.second = _contracts.size() - 1;
return ret;
}
void ClientModel::showDebugger()
{
ExecutionResult last = m_client->lastExecution();
@ -609,7 +647,7 @@ Address ClientModel::deployContract(bytes const& _code, TransactionSettings cons
return newAddress;
}
void ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr)
void ClientModel::callAddress(Address const& _contract, bytes const& _data, TransactionSettings const& _tr)
{
m_client->submitTransaction(_tr.sender, _tr.value, _contract, _data, _tr.gas, _tr.gasPrice, _tr.gasAuto);
}

17
mix/ClientModel.h

@ -53,10 +53,10 @@ struct SolidityType;
struct TransactionSettings
{
TransactionSettings() {}
TransactionSettings(QString const& _contractId, QString const& _functionId, u256 _value, u256 _gas, bool _gasAuto, u256 _gasPrice, Secret _sender):
contractId(_contractId), functionId(_functionId), value(_value), gas(_gas), gasAuto(_gasAuto), gasPrice(_gasPrice), sender(_sender) {}
TransactionSettings(QString const& _contractId, QString const& _functionId, u256 _value, u256 _gas, bool _gasAuto, u256 _gasPrice, Secret _sender, bool _isContractCreation, bool _isFunctionCall):
contractId(_contractId), functionId(_functionId), value(_value), gas(_gas), gasAuto(_gasAuto), gasPrice(_gasPrice), sender(_sender), isContractCreation(_isContractCreation), isFunctionCall(_isFunctionCall) {}
TransactionSettings(QString const& _stdContractName, QString const& _stdContractUrl):
contractId(_stdContractName), gasAuto(true), stdContractUrl(_stdContractUrl) {}
contractId(_stdContractName), gasAuto(true), stdContractUrl(_stdContractUrl), isContractCreation(true), isFunctionCall(true) {}
/// Contract name
QString contractId;
@ -76,6 +76,10 @@ struct TransactionSettings
QString stdContractUrl;
/// Sender
Secret sender;
/// Tr deploys a contract
bool isContractCreation;
/// Tr call a ctr function
bool isFunctionCall;
};
@ -222,11 +226,14 @@ private:
QVariantList gasCosts() const;
void executeSequence(std::vector<TransactionSettings> const& _sequence, std::unordered_map<Address, dev::eth::Account> const& _accounts, Secret const& _miner);
dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings());
void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
void callAddress(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
void onNewTransaction();
void onStateReset();
void showDebuggerForTransaction(ExecutionResult const& _t);
QVariant formatValue(SolidityType const& _type, dev::u256 const& _value);
QString resolveToken(std::pair<QString, int> const& _value, std::vector<Address> const& _contracts);
std::pair<QString, int> retrieveToken(QString const& _value, std::vector<Address> const& _contracts);
std::pair<QString, int> resolvePair(QString const& _contractId);
QVariant formatStorageValue(SolidityType const& _type, std::unordered_map<dev::u256, dev::u256> const& _storage, unsigned _offset, dev::u256 const& _slot);
std::atomic<bool> m_running;
@ -237,7 +244,7 @@ private:
std::unique_ptr<Web3Server> m_web3Server;
std::shared_ptr<eth::FixedAccountHolder> m_ethAccounts;
QList<u256> m_gasCosts;
std::map<QString, Address> m_contractAddresses;
std::map<std::pair<QString, int>, Address> m_contractAddresses;
std::map<Address, QString> m_contractNames;
std::map<QString, Address> m_stdContractAddresses;
std::map<Address, QString> m_stdContractNames;

62
mix/qml/QAddressView.qml

@ -8,6 +8,10 @@ Item
property alias accountRef: ctrModel
property string subType
property bool readOnly
property alias currentIndex: trCombobox.currentIndex
property alias currentText: textinput.text
property variant accounts
signal indexChanged()
id: editRoot
height: 20
width: 320
@ -17,6 +21,51 @@ Item
id: boldFont
}
function currentValue() {
return currentText;
}
function currentType()
{
return accountRef.get(trCombobox.currentIndex).type;
}
function current()
{
return accountRef.get(trCombobox.currentIndex);
}
function load()
{
accountRef.clear();
accountRef.append({"itemid": " - "});
if (subType === "contract" || subType === "address")
{
var trCr = 0;
for (var k = 0; k < transactionsModel.count; k++)
{
if (k >= transactionIndex)
break;
var tr = transactionsModel.get(k);
if (tr.functionId === tr.contractId /*&& (dec[1] === tr.contractId || item.subType === "address")*/)
{
accountRef.append({ "itemid": tr.contractId + " - " + trCr, "value": "<" + tr.contractId + " - " + trCr + ">", "type": "contract" });
trCr++;
}
}
}
if (subType === "address")
{
for (k = 0; k < accounts.length; k++)
{
if (accounts[k].address === undefined)
accounts[k].address = clientModel.address(accounts[k].secret);
accountRef.append({ "itemid": accounts[k].name, "value": "0x" + accounts[k].address, "type": "address" });
}
}
}
function init()
{
trCombobox.visible = !readOnly
@ -35,6 +84,18 @@ Item
}
}
function select(address)
{
for (var k = 0; k < accountRef.count; k++)
{
if (accountRef.get(k).value === address)
{
trCombobox.currentIndex = k;
break;
}
}
}
Rectangle {
anchors.fill: parent
radius: 4
@ -96,6 +157,7 @@ Item
{
textinput.text = "";
}
indexChanged();
}
}
}

4
mix/qml/StateDialog.qml

@ -436,7 +436,7 @@ Dialog {
model: transactionsModel
headerVisible: false
TableViewColumn {
role: "name"
role: "label"
title: qsTr("Name")
width: 150
delegate: Item {
@ -476,7 +476,7 @@ Dialog {
text: {
if (styleData.row >= 0)
return transactionsModel.get(
styleData.row).functionId
styleData.row).label
else
return ""
}

22
mix/qml/StateListModel.qml

@ -46,6 +46,7 @@ Item {
t.sender = defaultAccount; //support for old project
var r = {
type: t.type,
contractId: t.contractId,
functionId: t.functionId,
url: t.url,
@ -55,8 +56,21 @@ Item {
gasAuto: t.gasAuto,
stdContract: t.stdContract ? true : false,
parameters: {},
sender: t.sender
sender: t.sender,
isContractCreation: t.isContractCreation,
label: t.label,
isFunctionCall: t.isFunctionCall
};
if (r.isFunctionCall === undefined)
r.isFunctionCall = true;
if (!r.label)
r.label = r.contractId + " - " + r.functionId;
if (r.isContractCreation === undefined)
r.isContractCreation = r.functionId === r.contractId;
for (var key in t.parameters)
r.parameters[key] = t.parameters[key];
@ -100,6 +114,7 @@ Item {
function toPlainTransactionItem(t) {
var r = {
type: t.type,
contractId: t.contractId,
functionId: t.functionId,
url: t.url,
@ -109,7 +124,10 @@ Item {
gasPrice: { value: t.gasPrice.value, unit: t.gasPrice.unit },
stdContract: t.stdContract,
sender: t.sender,
parameters: {}
parameters: {},
isContractCreation: t.isContractCreation,
label: t.label,
isFunctionCall: t.isFunctionCall
};
for (var key in t.parameters)
r.parameters[key] = t.parameters[key];

29
mix/qml/StructView.qml

@ -75,38 +75,13 @@ Column
item.readOnly = context === "variable";
if (ptype.category === QSolidityType.Address)
{
item.accounts = accounts
item.value = getValue();
if (context === "parameter")
{
var dec = modelData.type.name.split(" ");
item.subType = dec[0];
item.accountRef.append({"itemid": " - "});
if (item.subType === "contract" || item.subType === "address")
{
var trCr = 0;
for (var k = 0; k < transactionsModel.count; k++)
{
if (k >= transactionIndex)
break;
var tr = transactionsModel.get(k);
if (tr.functionId === tr.contractId && (dec[1] === tr.contractId || item.subType === "address"))
{
item.accountRef.append({ "itemid": tr.contractId + " - " + trCr, "value": "<" + tr.contractId + " - " + trCr + ">", "type": "contract" });
trCr++;
}
}
}
if (item.subType === "address")
{
for (k = 0; k < accounts.length; k++)
{
if (accounts[k].address === undefined)
accounts[k].address = clientModel.address(accounts[k].secret);
item.accountRef.append({ "itemid": accounts[k].name, "value": "0x" + accounts[k].address, "type": "address" });
}
}
item.load();
}
item.init();
}

128
mix/qml/TransactionDialog.qml

@ -66,29 +66,48 @@ Dialog {
contractIndex = 0; //@todo suggest unused contract
contractComboBox.currentIndex = contractIndex;
loadFunctions(contractComboBox.currentValue());
recipients.accounts = senderComboBox.model;
recipients.subType = "address";
recipients.load();
recipients.init();
recipients.select(contractId);
if (item.isContractCreation)
loadFunctions(contractComboBox.currentValue());
else
loadFunctions(contractFromToken(recipients.currentValue()))
selectFunction(functionId);
trType.checked = item.isContractCreation
trType.init();
paramsModel = [];
if (functionId !== contractComboBox.currentValue())
if (item.isContractCreation)
loadCtorParameters();
else
loadParameters();
else {
var contract = codeModel.contracts[contractId];
if (contract) {
var params = contract.contract.constructor.parameters;
for (var p = 0; p < params.length; p++)
loadParameter(params[p]);
}
}
initTypeLoader();
visible = true;
valueField.focus = true;
}
function loadCtorParameters(contractId)
{
paramsModel = [];
console.log(contractId);
var contract = codeModel.contracts[contractId];
if (contract) {
var params = contract.contract.constructor.parameters;
for (var p = 0; p < params.length; p++)
loadParameter(params[p]);
}
initTypeLoader();
}
function loadFunctions(contractId)
{
functionsModel.clear();
functionsModel.append({ text: " - " });
var contract = codeModel.contracts[contractId];
if (contract) {
var functions = codeModel.contracts[contractId].contract.functions;
@ -96,9 +115,6 @@ Dialog {
functionsModel.append({ text: functions[f].name });
}
}
//append constructor
functionsModel.append({ text: contractId });
}
function selectContract(contractName)
@ -136,7 +152,7 @@ Dialog {
function loadParameters() {
paramsModel = []
if (functionComboBox.currentIndex >= 0 && functionComboBox.currentIndex < functionsModel.count) {
var contract = codeModel.contracts[contractComboBox.currentValue()];
var contract = codeModel.contracts[contractFromToken(recipients.currentValue())];
if (contract) {
var func = contract.contract.functions[functionComboBox.currentIndex];
if (func) {
@ -193,10 +209,37 @@ Dialog {
item.functionId = transactionDialog.functionId;
}
item.isContractCreation = trType.checked;
item.isFunctionCall = item.functionId !== " - ";
if (!item.isContractCreation)
{
item.contractId = recipients.currentText;
item.label = item.contractId + " " + item.functionId;
if (recipients.current().type === "address")
{
item.functionId = "";
item.isFunctionCall = false;
}
}
else
{
item.functionId = item.contractId;
item.label = qsTr("Deploy") + " " + item.contractId;
}
item.sender = senderComboBox.model[senderComboBox.currentIndex].secret;
item.parameters = paramValues;
return item;
}
function contractFromToken(token)
{
if (token.indexOf('<') === 0)
return token.replace("<", "").replace(">", "").split(" - ")[0];
return token;
}
contentItem: Rectangle {
color: transactionDialogStyle.generic.backgroundColor
ColumnLayout {
@ -238,6 +281,59 @@ Dialog {
}
}
RowLayout
{
id: rowIsContract
Layout.fillWidth: true
height: 150
CheckBox {
id: trType
onCheckedChanged:
{
init();
}
function init()
{
rowFunction.visible = !checked;
rowContract.visible = checked;
rowRecipient.visible = !checked;
paramLabel.visible = checked;
paramScroll.visible = checked;
functionComboBox.enabled = !checked;
if (checked)
loadCtorParameters(contractComboBox.currentValue());
}
text: qsTr("is contract creation")
checked: true
}
}
RowLayout
{
id: rowRecipient
Layout.fillWidth: true
height: 150
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Recipient")
}
QAddressView
{
id: recipients
onIndexChanged:
{
rowFunction.visible = current().type === "contract";
paramLabel.visible = current().type === "contract";
paramScroll.visible = current().type === "contract";
if (!rowIsContract.checked)
loadFunctions(contractFromToken(recipients.currentValue()))
}
}
}
RowLayout
{
id: rowContract
@ -260,7 +356,7 @@ Dialog {
id: contractsModel
}
onCurrentIndexChanged: {
loadFunctions(currentValue());
loadCtorParameters(currentValue());
}
}
}

5
mix/qml/js/TransactionHelper.js

@ -9,7 +9,10 @@ function defaultTransaction()
gasAuto: true,
gasPrice: createEther("100000", QEther.Wei),
parameters: {},
stdContract: false
stdContract: false,
isContractCreation: true,
label: "",
isFunctionCall: true
};
}

21
test/TestHelper.cpp

@ -552,6 +552,9 @@ void checkCallCreates(eth::Transactions _resultCallCreates, eth::Transactions _e
void userDefinedTest(std::function<void(json_spirit::mValue&, bool)> doTests)
{
if (!Options::get().singleTest)
return;
if (Options::get().singleTestFile.empty() || Options::get().singleTestName.empty())
{
cnote << "Missing user test specification\nUsage: testeth --singletest <filename> <testname>\n";
return;
@ -731,11 +734,23 @@ Options::Options()
inputLimits = true;
bigData = true;
}
else if (arg == "--singletest" && i + 2 < argc)
else if (arg == "--singletest" && i + 1 < argc)
{
singleTest = true;
singleTestFile = argv[i + 1];
singleTestName = argv[i + 2];
auto name1 = std::string{argv[i + 1]};
if (i + 1 < argc) // two params
{
auto name2 = std::string{argv[i + 2]};
if (name2[0] == '-') // not param, another option
singleTestName = std::move(name1);
else
{
singleTestFile = std::move(name1);
singleTestName = std::move(name2);
}
}
else
singleTestName = std::move(name1);
}
}
}

10
test/libdevcrypto/trie.cpp

@ -102,10 +102,13 @@ BOOST_AUTO_TEST_CASE(hex_encoded_securetrie_test)
{
next_permutation(ss.begin(), ss.end());
MemoryDB m;
EnforceRefs r(m, true);
GenericTrieDB<MemoryDB> t(&m);
MemoryDB hm;
EnforceRefs hr(hm, true);
HashedGenericTrieDB<MemoryDB> ht(&hm);
MemoryDB fm;
EnforceRefs fr(fm, true);
FatGenericTrieDB<MemoryDB> ft(&fm);
t.init();
ht.init();
@ -164,10 +167,13 @@ BOOST_AUTO_TEST_CASE(trie_test_anyorder)
{
next_permutation(ss.begin(), ss.end());
MemoryDB m;
EnforceRefs r(m, true);
GenericTrieDB<MemoryDB> t(&m);
MemoryDB hm;
EnforceRefs hr(hm, true);
HashedGenericTrieDB<MemoryDB> ht(&hm);
MemoryDB fm;
EnforceRefs fr(fm, true);
FatGenericTrieDB<MemoryDB> ft(&fm);
t.init();
ht.init();
@ -244,10 +250,13 @@ BOOST_AUTO_TEST_CASE(trie_tests_ordered)
}
MemoryDB m;
EnforceRefs r(m, true);
GenericTrieDB<MemoryDB> t(&m);
MemoryDB hm;
EnforceRefs hr(hm, true);
HashedGenericTrieDB<MemoryDB> ht(&hm);
MemoryDB fm;
EnforceRefs fr(fm, true);
FatGenericTrieDB<MemoryDB> ft(&fm);
t.init();
ht.init();
@ -360,6 +369,7 @@ BOOST_AUTO_TEST_CASE(moreTrieTests)
#endif
{
MemoryDB m;
EnforceRefs r(m, true);
GenericTrieDB<MemoryDB> d(&m);
d.init(); // initialise as empty tree.
MemTrie t;

2
test/libevm/vm.cpp

@ -432,7 +432,7 @@ void doVMTests(json_spirit::mValue& v, bool _fillin)
}
}
} } // Namespace Close
} } // namespace close
BOOST_AUTO_TEST_SUITE(VMTests)

150
test/libp2p/capability.cpp

@ -0,0 +1,150 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file capability.cpp
* @author Vladislav Gluhovsky <vlad@ethdev.com>
* @date May 2015
*/
#include <boost/test/unit_test.hpp>
#include <chrono>
#include <thread>
#include <libp2p/Common.h>
#include <libp2p/Host.h>
#include <libp2p/Session.h>
#include <libp2p/Capability.h>
#include <libp2p/HostCapability.h>
using namespace std;
using namespace dev;
using namespace dev::p2p;
struct P2PFixture
{
P2PFixture() { dev::p2p::NodeIPEndpoint::test_allowLocal = true; }
~P2PFixture() { dev::p2p::NodeIPEndpoint::test_allowLocal = false; }
};
struct VerbosityHolder
{
VerbosityHolder(): oldLogVerbosity(g_logVerbosity) { g_logVerbosity = 10; }
~VerbosityHolder() { g_logVerbosity = oldLogVerbosity; }
int oldLogVerbosity;
};
class TestCapability: public Capability
{
public:
TestCapability(Session* _s, HostCapabilityFace* _h, unsigned _idOffset): Capability(_s, _h, _idOffset), m_cntReceivedMessages(0), m_testSum(0) {}
virtual ~TestCapability() {}
int countReceivedMessages() { return m_cntReceivedMessages; }
int testSum() { return m_testSum; }
static std::string name() { return "test"; }
static u256 version() { return 2; }
static unsigned messageCount() { return UserPacket + 1; }
void sendTestMessage(int _i) { RLPStream s; sealAndSend(prep(s, UserPacket, 1) << _i); }
protected:
virtual bool interpret(unsigned _id, RLP const& _r) override;
int m_cntReceivedMessages;
int m_testSum;
};
bool TestCapability::interpret(unsigned _id, RLP const& _r)
{
cnote << "Capability::interpret(): custom message received";
++m_cntReceivedMessages;
m_testSum += _r[0].toInt();
BOOST_ASSERT(_id == UserPacket);
return (_id == UserPacket);
}
class TestHostCapability: public HostCapability<TestCapability>, public Worker
{
public:
TestHostCapability(): Worker("test") {}
virtual ~TestHostCapability() {}
void sendTestMessage(NodeId const& _id, int _x)
{
for (auto i: peerSessions())
if (_id == i.second->id)
i.first->cap<TestCapability>().get()->sendTestMessage(_x);
}
std::pair<int, int> retrieveTestData(NodeId const& _id)
{
int cnt = 0;
int checksum = 0;
for (auto i: peerSessions())
if (_id == i.second->id)
{
cnt += i.first->cap<TestCapability>().get()->countReceivedMessages();
checksum += i.first->cap<TestCapability>().get()->testSum();
}
return std::pair<int, int>(cnt, checksum);
}
};
BOOST_FIXTURE_TEST_SUITE(p2pCapability, P2PFixture)
BOOST_AUTO_TEST_CASE(capability)
{
VerbosityHolder verbosityHolder;
cnote << "Testing Capability...";
const char* const localhost = "127.0.0.1";
NetworkPreferences prefs1(localhost, 30301, false);
NetworkPreferences prefs2(localhost, 30302, false);
Host host1("Test", prefs1);
auto thc1 = host1.registerCapability(new TestHostCapability());
host1.start();
Host host2("Test", prefs2);
auto thc2 = host2.registerCapability(new TestHostCapability());
host2.start();
int const step = 10;
for (int i = 0; i < 3000 && (!host1.isStarted() || !host2.isStarted()); i += step)
this_thread::sleep_for(chrono::milliseconds(step));
BOOST_REQUIRE(host1.isStarted() && host2.isStarted());
host1.requirePeer(host2.id(), NodeIPEndpoint(bi::address::from_string(localhost), prefs2.listenPort, prefs2.listenPort));
for (int i = 0; i < 3000 && (!host1.peerCount() || !host2.peerCount()); i += step)
this_thread::sleep_for(chrono::milliseconds(step));
BOOST_REQUIRE(host1.peerCount() > 0 && host2.peerCount() > 0);
int const target = 7;
int checksum = 0;
for (int i = 0; i < target; checksum += i++)
thc2->sendTestMessage(host1.id(), i);
this_thread::sleep_for(chrono::seconds(1));
std::pair<int, int> testData = thc1->retrieveTestData(host2.id());
BOOST_REQUIRE_EQUAL(target, testData.first);
BOOST_REQUIRE_EQUAL(checksum, testData.second);
}
BOOST_AUTO_TEST_SUITE_END()

59
test/libsolidity/SolidityCompiler.cpp

@ -116,36 +116,35 @@ BOOST_AUTO_TEST_CASE(ifStatement)
bytes code = compileContract(sourceCode);
unsigned shift = 60;
unsigned boilerplateSize = 73;
bytes expectation({byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x0,
byte(Instruction::DUP1),
byte(Instruction::PUSH1), byte(0x1b + shift), // "true" target
byte(Instruction::JUMPI),
// new check "else if" condition
byte(Instruction::DUP1),
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), byte(0x13 + shift),
byte(Instruction::JUMPI),
// "else" body
byte(Instruction::PUSH1), 0x4f,
byte(Instruction::POP),
byte(Instruction::PUSH1), byte(0x17 + shift), // exit path of second part
byte(Instruction::JUMP),
// "else if" body
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x4e,
byte(Instruction::POP),
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), byte(0x1f + shift),
byte(Instruction::JUMP),
// "if" body
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x4d,
byte(Instruction::POP),
byte(Instruction::JUMPDEST),
byte(Instruction::JUMPDEST),
byte(Instruction::POP),
byte(Instruction::JUMP)});
bytes expectation({
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x0,
byte(Instruction::DUP1),
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), byte(0x0f + shift), // "false" target
byte(Instruction::JUMPI),
// "if" body
byte(Instruction::PUSH1), 0x4d,
byte(Instruction::POP),
byte(Instruction::PUSH1), byte(0x21 + shift),
byte(Instruction::JUMP),
// new check "else if" condition
byte(Instruction::JUMPDEST),
byte(Instruction::DUP1),
byte(Instruction::ISZERO),
byte(Instruction::ISZERO),
byte(Instruction::PUSH1), byte(0x1c + shift),
byte(Instruction::JUMPI),
// "else if" body
byte(Instruction::PUSH1), 0x4e,
byte(Instruction::POP),
byte(Instruction::PUSH1), byte(0x20 + shift),
byte(Instruction::JUMP),
// "else" body
byte(Instruction::JUMPDEST),
byte(Instruction::PUSH1), 0x4f,
byte(Instruction::POP),
});
checkCodePresentAt(code, expectation, boilerplateSize);
}

95
test/libsolidity/SolidityOptimizer.cpp

@ -29,6 +29,7 @@
#include <libevmasm/CommonSubexpressionEliminator.h>
#include <libevmasm/ControlFlowGraph.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/BlockDeduplicator.h>
using namespace std;
using namespace dev::eth;
@ -125,7 +126,7 @@ public:
BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end());
}
void checkCFG(AssemblyItems const& _input, AssemblyItems const& _expectation)
AssemblyItems getCFG(AssemblyItems const& _input)
{
AssemblyItems output = _input;
// Running it four times should be enough for these tests.
@ -138,6 +139,12 @@ public:
back_inserter(optItems));
output = move(optItems);
}
return output;
}
void checkCFG(AssemblyItems const& _input, AssemblyItems const& _expectation)
{
AssemblyItems output = getCFG(_input);
BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end());
}
@ -251,6 +258,63 @@ BOOST_AUTO_TEST_CASE(function_calls)
compareVersions("f(uint256)", 36);
}
BOOST_AUTO_TEST_CASE(storage_write_in_loops)
{
char const* sourceCode = R"(
contract test {
uint d;
function f(uint a) returns (uint r) {
var x = d;
for (uint i = 1; i < a * a; i++) {
r = d;
d = i;
}
}
}
)";
compileBothVersions(sourceCode);
compareVersions("f(uint256)", 0);
compareVersions("f(uint256)", 10);
compareVersions("f(uint256)", 36);
}
BOOST_AUTO_TEST_CASE(retain_information_in_branches)
{
// This tests that the optimizer knows that we already have "z == sha3(y)" inside both branches.
char const* sourceCode = R"(
contract c {
bytes32 d;
uint a;
function f(uint x, bytes32 y) returns (uint r_a, bytes32 r_d) {
bytes32 z = sha3(y);
if (x > 8) {
z = sha3(y);
a = x;
} else {
z = sha3(y);
a = x;
}
r_a = a;
r_d = d;
}
}
)";
compileBothVersions(sourceCode);
compareVersions("f(uint256,bytes32)", 0, "abc");
compareVersions("f(uint256,bytes32)", 8, "def");
compareVersions("f(uint256,bytes32)", 10, "ghi");
m_optimize = true;
bytes optimizedBytecode = compileAndRun(sourceCode, 0, "c");
size_t numSHA3s = 0;
eth::eachInstruction(optimizedBytecode, [&](Instruction _instr, u256 const&) {
if (_instr == eth::Instruction::SHA3)
numSHA3s++;
});
BOOST_CHECK_EQUAL(1, numSHA3s);
}
BOOST_AUTO_TEST_CASE(cse_intermediate_swap)
{
eth::KnownState state;
@ -868,6 +932,35 @@ BOOST_AUTO_TEST_CASE(control_flow_graph_do_not_remove_returned_to)
checkCFG(input, {u256(2)});
}
BOOST_AUTO_TEST_CASE(block_deduplicator)
{
AssemblyItems input{
AssemblyItem(PushTag, 2),
AssemblyItem(PushTag, 1),
AssemblyItem(PushTag, 3),
u256(6),
eth::Instruction::SWAP3,
eth::Instruction::JUMP,
AssemblyItem(Tag, 1),
u256(6),
eth::Instruction::SWAP3,
eth::Instruction::JUMP,
AssemblyItem(Tag, 2),
u256(6),
eth::Instruction::SWAP3,
eth::Instruction::JUMP,
AssemblyItem(Tag, 3)
};
BlockDeduplicator dedup(input);
dedup.deduplicate();
set<u256> pushTags;
for (AssemblyItem const& item: input)
if (item.type() == PushTag)
pushTags.insert(item.data());
BOOST_CHECK_EQUAL(pushTags.size(), 2);
}
BOOST_AUTO_TEST_SUITE_END()
}

Loading…
Cancel
Save