Browse Source

Merge branch 'develop' of https://github.com/ethereum/cpp-ethereum into import_route

cl-refactor
Marek Kotewicz 10 years ago
parent
commit
fe479a868a
  1. 9
      alethzero/Main.ui
  2. 13
      alethzero/MainWin.cpp
  3. 5
      cmake/EthCompilerSettings.cmake
  4. 5
      ethminer/MinerAux.h
  5. 18
      libethash-cl/ethash_cl_miner.cpp
  6. 9
      libethash-cl/ethash_cl_miner.h
  7. 25
      libethcore/Common.h
  8. 3
      libethcore/Ethash.cpp
  9. 3
      libethcore/Ethash.h
  10. 10
      libethereum/BlockChain.cpp
  11. 2
      libethereum/BlockChain.h
  12. 4
      libethereum/Client.cpp
  13. 2
      libethereum/Client.h
  14. 25
      libethereum/CommonNet.h
  15. 5
      libethereum/DownloadMan.h
  16. 132
      libethereum/EthereumHost.cpp
  17. 32
      libethereum/EthereumHost.h
  18. 59
      libethereum/ExtVM.cpp
  19. 48
      libevmasm/CommonSubexpressionEliminator.cpp
  20. 25
      libp2p/Host.cpp
  21. 6
      libp2p/Host.h
  22. 30
      libp2p/RLPXFrameCoder.cpp
  23. 51
      libp2p/RLPXFrameCoder.h
  24. 0
      libp2p/RLPXSocket.cpp
  25. 56
      libp2p/RLPXSocket.h
  26. 6
      libp2p/RLPxHandshake.cpp
  27. 9
      libp2p/RLPxHandshake.h
  28. 35
      libp2p/Session.cpp
  29. 11
      libp2p/Session.h
  30. 5
      libsolidity/Compiler.cpp
  31. 259
      mix/ClientModel.cpp
  32. 41
      mix/ClientModel.h
  33. 7
      mix/ContractCallDataEncoder.cpp
  34. 2
      mix/ContractCallDataEncoder.h
  35. 84
      mix/MachineStates.h
  36. 88
      mix/MixClient.cpp
  37. 2
      mix/MixClient.h
  38. 17
      mix/QContractDefinition.cpp
  39. 8
      mix/QContractDefinition.h
  40. 31
      mix/QFunctionDefinition.cpp
  41. 7
      mix/QFunctionDefinition.h
  42. 16
      mix/QVariableDeclaration.cpp
  43. 9
      mix/QVariableDeclaration.h
  44. 5
      mix/qml.qrc
  45. 15
      mix/qml/Application.qml
  46. 346
      mix/qml/Block.qml
  47. 480
      mix/qml/BlockChain.qml
  48. 158
      mix/qml/Debugger.qml
  49. 60
      mix/qml/MainContent.qml
  50. 31
      mix/qml/QAddressView.qml
  51. 67
      mix/qml/ScenarioButton.qml
  52. 57
      mix/qml/ScenarioExecution.qml
  53. 175
      mix/qml/ScenarioLoader.qml
  54. 117
      mix/qml/StateListModel.qml
  55. 1
      mix/qml/StructView.qml
  56. 14
      mix/qml/TransactionDialog.qml
  57. BIN
      mix/qml/img/newIcon.png
  58. BIN
      mix/qml/img/newIcon@2x.png
  59. 108
      mix/qml/js/ProjectModel.js
  60. 3
      mix/qml/js/TransactionHelper.js
  61. 22
      mix/res.qrc
  62. 8
      test/libsolidity/SolidityOptimizer.cpp

9
alethzero/Main.ui

@ -44,7 +44,14 @@
<string>0 bytes used</string>
</property>
</widget>
</item>
</item>
<item>
<widget class="QLabel" name="syncStatus">
<property name="text">
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="chainStatus">
<property name="text">

13
alethzero/MainWin.cpp

@ -198,6 +198,7 @@ Main::Main(QWidget *parent) :
statusBar()->addPermanentWidget(ui->balance);
statusBar()->addPermanentWidget(ui->peerCount);
statusBar()->addPermanentWidget(ui->mineStatus);
statusBar()->addPermanentWidget(ui->syncStatus);
statusBar()->addPermanentWidget(ui->chainStatus);
statusBar()->addPermanentWidget(ui->blockCount);
@ -1245,9 +1246,15 @@ void Main::refreshBlockCount()
{
auto d = ethereum()->blockChain().details();
BlockQueueStatus b = ethereum()->blockQueueStatus();
HashChainStatus h = ethereum()->hashChainStatus();
ui->chainStatus->setText(QString("%10/%11%12 hashes %3 importing %4 ready %5 verifying %6 unverified %7 future %8 unknown %9 bad %1 #%2")
.arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet").arg(d.number).arg(b.importing).arg(b.verified).arg(b.verifying).arg(b.unverified).arg(b.future).arg(b.unknown).arg(b.bad).arg(h.received).arg(h.estimated ? "~" : "").arg(h.total));
SyncStatus sync = ethereum()->syncStatus();
QString syncStatus = EthereumHost::stateName(sync.state);
if (sync.state == SyncState::HashesParallel || sync.state == SyncState::HashesSingle)
syncStatus += QString(": %1/%2%3").arg(sync.hashesReceived).arg(sync.hashesEstimated ? "~" : "").arg(sync.hashesTotal);
if (sync.state == SyncState::Blocks || sync.state == SyncState::NewBlocks)
syncStatus += QString(": %1/%2").arg(sync.blocksReceived).arg(sync.blocksTotal);
ui->syncStatus->setText(syncStatus);
ui->chainStatus->setText(QString("%3 importing %4 ready %5 verifying %6 unverified %7 future %8 unknown %9 bad %1 #%2")
.arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet").arg(d.number).arg(b.importing).arg(b.verified).arg(b.verifying).arg(b.unverified).arg(b.future).arg(b.unknown).arg(b.bad));
}
void Main::on_turboMining_triggered()

5
cmake/EthCompilerSettings.cmake

@ -55,11 +55,6 @@ else ()
message(WARNING "Your compiler is not tested, if you run into any issues, we'd welcome any patches.")
endif ()
# Set stack memory limits
if (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-stack_size,1000000")
endif()
if (PROFILING AND (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
set(CMAKE_CXX_FLAGS "-g ${CMAKE_CXX_FLAGS}")
set(CMAKE_C_FLAGS "-g ${CMAKE_C_FLAGS}")

5
ethminer/MinerAux.h

@ -134,6 +134,8 @@ public:
m_clAllowCPU = true;
else if (arg == "--cl-extragpu-mem" && i + 1 < argc)
m_extraGPUMemory = 1000000 * stol(argv[++i]);
else if (arg == "--force-single-chunk")
m_forceSingleChunk = true;
else if (arg == "--phone-home" && i + 1 < argc)
{
string m = argv[++i];
@ -271,6 +273,7 @@ public:
m_openclDevice,
m_clAllowCPU,
m_extraGPUMemory,
m_forceSingleChunk,
m_currentBlock
))
{
@ -318,6 +321,7 @@ public:
<< " --list-devices List the detected OpenCL devices and exit." <<endl
<< " --current-block Let the miner know the current block number at configuration time. Will help determine DAG size and required GPU memory." <<endl
<< " --cl-extragpu-mem Set the memory (in MB) you believe your GPU requires for stuff other than mining. Windows rendering e.t.c.." <<endl
<< " --force-single-chunk Force DAG uploading in a single chunk against OpenCL's judgement. Use at your own risk." <<endl
;
}
@ -507,6 +511,7 @@ private:
unsigned m_miningThreads = UINT_MAX;
bool m_shouldListDevices = false;
bool m_clAllowCPU = false;
bool m_forceSingleChunk = false;
boost::optional<uint64_t> m_currentBlock;
// default value is 350MB of GPU memory for other stuff (windows system rendering, e.t.c.)
unsigned m_extraGPUMemory = 350000000;

18
libethash-cl/ethash_cl_miner.cpp

@ -137,9 +137,15 @@ unsigned ethash_cl_miner::getNumDevices(unsigned _platformId)
return devices.size();
}
bool ethash_cl_miner::configureGPU(bool _allowCPU, unsigned _extraGPUMemory, boost::optional<uint64_t> _currentBlock)
bool ethash_cl_miner::configureGPU(
bool _allowCPU,
unsigned _extraGPUMemory,
bool _forceSingleChunk,
boost::optional<uint64_t> _currentBlock
)
{
s_allowCPU = _allowCPU;
s_forceSingleChunk = _forceSingleChunk;
s_extraRequiredGPUMem = _extraGPUMemory;
// by default let's only consider the DAG of the first epoch
uint64_t dagSize = _currentBlock ? ethash_get_datasize(*_currentBlock) : 1073739904U;
@ -168,6 +174,7 @@ bool ethash_cl_miner::configureGPU(bool _allowCPU, unsigned _extraGPUMemory, boo
}
bool ethash_cl_miner::s_allowCPU = false;
bool ethash_cl_miner::s_forceSingleChunk = false;
unsigned ethash_cl_miner::s_extraRequiredGPUMem;
bool ethash_cl_miner::searchForAllDevices(function<bool(cl::Device const&)> _callback)
@ -284,15 +291,18 @@ bool ethash_cl_miner::init(
// configure chunk number depending on max allocateable memory
cl_ulong result;
device.getInfo(CL_DEVICE_MAX_MEM_ALLOC_SIZE, &result);
if (result >= _dagSize)
if (s_forceSingleChunk || result >= _dagSize)
{
m_dagChunksNum = 1;
ETHCL_LOG("Using 1 big chunk. Max OpenCL allocateable memory is" << result);
ETHCL_LOG(
((result <= _dagSize && s_forceSingleChunk) ? "Forcing single chunk. Good luck!\n" : "") <<
"Using 1 big chunk. Max OpenCL allocateable memory is " << result
);
}
else
{
m_dagChunksNum = 4;
ETHCL_LOG("Using 4 chunks. Max OpenCL allocateable memory is" << result);
ETHCL_LOG("Using 4 chunks. Max OpenCL allocateable memory is " << result);
}
if (strncmp("OpenCL 1.0", device_version.c_str(), 10) == 0)

9
libethash-cl/ethash_cl_miner.h

@ -41,7 +41,12 @@ public:
static unsigned getNumDevices(unsigned _platformId = 0);
static std::string platform_info(unsigned _platformId = 0, unsigned _deviceId = 0);
static void listDevices();
static bool configureGPU(bool _allowCPU, unsigned _extraGPUMemory, boost::optional<uint64_t> _currentBlock);
static bool configureGPU(
bool _allowCPU,
unsigned _extraGPUMemory,
bool _forceSingleChunk,
boost::optional<uint64_t> _currentBlock
);
bool init(
uint8_t const* _dag,
@ -74,6 +79,8 @@ private:
unsigned m_workgroup_size;
bool m_opencl_1_1;
/// Force dag upload to GPU in a single chunk even if OpenCL thinks you can't do it. Use at your own risk.
static bool s_forceSingleChunk;
/// Allow CPU to appear as an OpenCL device or not. Default is false
static bool s_allowCPU;
/// GPU memory required for other things, like window rendering e.t.c.

25
libethcore/Common.h

@ -123,28 +123,43 @@ struct ImportRequirements
class Signal
{
public:
using Callback = std::function<void()>;
class HandlerAux
{
friend class Signal;
public:
~HandlerAux() { if (m_s) m_s->m_fire.erase(m_i); m_s = nullptr; }
void reset() { m_s = nullptr; }
void fire() { m_h(); }
private:
HandlerAux(unsigned _i, Signal* _s): m_i(_i), m_s(_s) {}
HandlerAux(unsigned _i, Signal* _s, Callback const& _h): m_i(_i), m_s(_s), m_h(_h) {}
unsigned m_i = 0;
Signal* m_s = nullptr;
Callback m_h;
};
using Callback = std::function<void()>;
~Signal()
{
for (auto const& h : m_fire)
h.second->reset();
}
std::shared_ptr<HandlerAux> add(Callback const& _h) { auto n = m_fire.empty() ? 0 : (m_fire.rbegin()->first + 1); m_fire[n] = _h; return std::shared_ptr<HandlerAux>(new HandlerAux(n, this)); }
std::shared_ptr<HandlerAux> add(Callback const& _h)
{
auto n = m_fire.empty() ? 0 : (m_fire.rbegin()->first + 1);
auto h = std::shared_ptr<HandlerAux>(new HandlerAux(n, this, _h));
m_fire[n] = h;
return h;
}
void operator()() { for (auto const& f: m_fire) f.second(); }
void operator()() { for (auto const& f: m_fire) f.second->fire(); }
private:
std::map<unsigned, Callback> m_fire;
std::map<unsigned, std::shared_ptr<Signal::HandlerAux>> m_fire;
};
using Handler = std::shared_ptr<Signal::HandlerAux>;

3
libethcore/Ethash.cpp

@ -386,12 +386,13 @@ bool Ethash::GPUMiner::configureGPU(
unsigned _deviceId,
bool _allowCPU,
unsigned _extraGPUMemory,
bool _forceSingleChunk,
boost::optional<uint64_t> _currentBlock
)
{
s_platformId = _platformId;
s_deviceId = _deviceId;
return ethash_cl_miner::configureGPU(_allowCPU, _extraGPUMemory, _currentBlock);
return ethash_cl_miner::configureGPU(_allowCPU, _extraGPUMemory, _forceSingleChunk, _currentBlock);
}
#endif

3
libethcore/Ethash.h

@ -88,7 +88,7 @@ public:
static unsigned instances() { return s_numInstances > 0 ? s_numInstances : std::thread::hardware_concurrency(); }
static std::string platformInfo();
static void listDevices() {}
static bool configureGPU(unsigned, unsigned, bool, unsigned, boost::optional<uint64_t>) { return false; }
static bool configureGPU(unsigned, unsigned, bool, unsigned, bool, boost::optional<uint64_t>) { return false; }
static void setNumInstances(unsigned _instances) { s_numInstances = std::min<unsigned>(_instances, std::thread::hardware_concurrency()); }
protected:
void kickOff() override
@ -122,6 +122,7 @@ public:
unsigned _deviceId,
bool _allowCPU,
unsigned _extraGPUMemory,
bool _forceSingleChunk,
boost::optional<uint64_t> _currentBlock
);
static void setNumInstances(unsigned _instances) { s_numInstances = std::min<unsigned>(_instances, getNumDevices()); }

10
libethereum/BlockChain.cpp

@ -360,7 +360,7 @@ pair<ImportResult, ImportRoute> BlockChain::attemptImport(bytes const& _block, O
{
try
{
return make_pair(ImportResult::Success, import(verifyBlock(_block, m_onBad), _stateDB, _ir));
return make_pair(ImportResult::Success, import(verifyBlock(_block, m_onBad, _ir), _stateDB, _ir));
}
catch (UnknownParent&)
{
@ -1066,12 +1066,16 @@ bytes BlockChain::block(h256 const& _hash) const
return m_blocks[_hash];
}
VerifiedBlockRef BlockChain::verifyBlock(bytes const& _block, function<void(Exception&)> const& _onBad)
VerifiedBlockRef BlockChain::verifyBlock(bytes const& _block, function<void(Exception&)> const& _onBad, ImportRequirements::value _ir)
{
VerifiedBlockRef res;
try
{
res.info.populate(_block, CheckEverything);
Strictness strictness = Strictness::CheckEverything;
if (_ir & ~ImportRequirements::ValidNonce)
strictness = Strictness::IgnoreNonce;
res.info.populate(_block, strictness);
res.info.verifyInternals(&_block);
}
catch (Exception& ex)

2
libethereum/BlockChain.h

@ -271,7 +271,7 @@ public:
void garbageCollect(bool _force = false);
/// Verify block and prepare it for enactment
static VerifiedBlockRef verifyBlock(bytes const& _block, std::function<void(Exception&)> const& _onBad = std::function<void(Exception&)>());
static VerifiedBlockRef verifyBlock(bytes const& _block, std::function<void(Exception&)> const& _onBad = std::function<void(Exception&)>(), ImportRequirements::value _ir = ImportRequirements::Default);
/// Change the function that is called with a bad block.
template <class T> void setOnBad(T const& _t) { m_onBad = _t; }

4
libethereum/Client.cpp

@ -913,8 +913,8 @@ void Client::flushTransactions()
doWork();
}
HashChainStatus Client::hashChainStatus() const
SyncStatus Client::syncStatus() const
{
auto h = m_host.lock();
return h ? h->status() : HashChainStatus { 0, 0, false };
return h ? h->status() : SyncStatus();
}

2
libethereum/Client.h

@ -157,7 +157,7 @@ public:
/// Get some information on the block queue.
BlockQueueStatus blockQueueStatus() const { return m_bq.status(); }
/// Get some information on the block queue.
HashChainStatus hashChainStatus() const;
SyncStatus syncStatus() const;
/// Get the block queue.
BlockQueue const& blockQueue() const { return m_bq; }

25
libethereum/CommonNet.h

@ -77,18 +77,27 @@ enum class Asking
Nothing
};
enum class Syncing
enum class SyncState
{
Waiting,
Executing,
Done
Idle, ///< Initial chain sync complete. Waiting for new packets
WaitingQueue, ///< Block downloading paused. Waiting for block queue to process blocks and free space
HashesNegotiate, ///< Waiting for first hashes to arrive
HashesSingle, ///< Locked on and downloading hashes from a single peer
HashesParallel, ///< Downloading hashes from multiple peers over
Blocks, ///< Downloading blocks
NewBlocks, ///< Downloading blocks learned from NewHashes packet
Size /// Must be kept last
};
struct HashChainStatus
struct SyncStatus
{
unsigned total;
unsigned received;
bool estimated;
SyncState state = SyncState::Idle;
unsigned hashesTotal = 0;
unsigned hashesReceived = 0;
bool hashesEstimated = false;
unsigned blocksTotal = 0;
unsigned blocksReceived = 0;
};
}

5
libethereum/DownloadMan.h

@ -253,11 +253,6 @@ public:
return m_got.full();
}
unsigned gotCount() const
{
return m_got.size();
}
size_t chainSize() const { ReadGuard l(m_lock); return m_chainCount; }
size_t chainEmpty() const { ReadGuard l(m_lock); return m_chainCount == 0; }
void foreachSub(std::function<void(HashDownloadSub const&)> const& _f) const { ReadGuard l(x_subs); for(auto i: m_subs) _f(*i); }

132
libethereum/EthereumHost.cpp

@ -41,6 +41,8 @@ using namespace p2p;
unsigned const EthereumHost::c_oldProtocolVersion = 60; //TODO: remove this once v61+ is common
unsigned const c_chainReorgSize = 30000;
char const* const EthereumHost::s_stateNames[static_cast<int>(SyncState::Size)] = {"Idle", "WaitingQueue", "HashesNegotiate", "HashesSingle", "HashesParallel", "Blocks", "NewBlocks" };
EthereumHost::EthereumHost(BlockChain const& _ch, TransactionQueue& _tq, BlockQueue& _bq, u256 _networkId):
HostCapability<EthereumPeer>(),
Worker ("ethsync"),
@ -49,6 +51,7 @@ EthereumHost::EthereumHost(BlockChain const& _ch, TransactionQueue& _tq, BlockQu
m_bq (_bq),
m_networkId (_networkId)
{
setState(SyncState::HashesNegotiate);
m_latestBlockSent = _ch.currentHash();
m_hashMan.reset(m_chain.number() + 1);
m_bqRoomAvailable = m_bq.onRoomAvailable([this](){ m_continueSync = true; });
@ -79,8 +82,7 @@ void EthereumHost::reset()
foreachPeer([](EthereumPeer* _p) { _p->abortSync(); });
m_man.resetToChain(h256s());
m_hashMan.reset(m_chain.number() + 1);
m_needSyncBlocks = true;
m_needSyncHashes = true;
setState(SyncState::HashesNegotiate);
m_syncingLatestHash = h256();
m_syncingTotalDifficulty = 0;
m_latestBlockSent = h256();
@ -88,6 +90,22 @@ void EthereumHost::reset()
m_hashes.clear();
}
void EthereumHost::resetSyncTo(h256 const& _h)
{
setState(SyncState::HashesNegotiate);
m_syncingLatestHash = _h;
}
void EthereumHost::setState(SyncState _s)
{
if (m_state != _s)
{
clog(NetAllDetail) << "SyncState changed from " << stateName(m_state) << " to " << stateName(_s);
m_state = _s;
}
}
void EthereumHost::doWork()
{
bool netChange = ensureInitialised();
@ -110,6 +128,7 @@ void EthereumHost::doWork()
if (m_continueSync)
{
m_continueSync = false;
RecursiveGuard l(x_sync);
continueSync();
}
@ -247,6 +266,7 @@ void EthereumHost::maintainBlocks(h256 const& _currentHash)
void EthereumHost::onPeerStatus(EthereumPeer* _peer)
{
RecursiveGuard l(x_sync);
DEV_INVARIANT_CHECK;
if (_peer->m_genesisHash != m_chain.genesisHash())
_peer->disable("Invalid genesis hash");
else if (_peer->m_protocolVersion != protocolVersion() && _peer->m_protocolVersion != c_oldProtocolVersion)
@ -266,13 +286,14 @@ void EthereumHost::onPeerStatus(EthereumPeer* _peer)
_peer->m_expectedHashes = (unsigned)_peer->m_latestBlockNumber - m_chain.number();
if (_peer->m_expectedHashes > estimatedHashes)
_peer->disable("Too many hashes");
else if (m_needSyncHashes && m_hashMan.chainSize() < _peer->m_expectedHashes)
else if (needHashes() && m_hashMan.chainSize() < _peer->m_expectedHashes)
m_hashMan.resetToRange(m_chain.number() + 1, _peer->m_expectedHashes);
}
else
_peer->m_expectedHashes = estimatedHashes;
continueSync(_peer);
}
DEV_INVARIANT_CHECK;
}
unsigned EthereumHost::estimateHashes()
@ -301,6 +322,7 @@ void EthereumHost::onPeerHashes(EthereumPeer* _peer, h256s const& _hashes)
void EthereumHost::onPeerHashes(EthereumPeer* _peer, h256s const& _hashes, bool _complete)
{
DEV_INVARIANT_CHECK;
if (_hashes.empty())
{
_peer->m_hashSub.doneFetch();
@ -367,46 +389,52 @@ void EthereumHost::onPeerHashes(EthereumPeer* _peer, h256s const& _hashes, bool
}
if (_complete)
{
m_needSyncBlocks = true;
continueSync();
clog(NetMessageSummary) << "Start new blocks download...";
m_syncingLatestHash = h256();
setState(SyncState::NewBlocks);
m_man.resetToChain(m_hashes);
m_hashes.clear();
m_hashMan.reset(m_chain.number() + 1);
continueSync(_peer);
}
else if (syncByNumber && m_hashMan.isComplete())
{
// Done our chain-get.
m_needSyncHashes = false;
clog(NetNote) << "Hashes download complete.";
// 1/100th for each useful block hash.
_peer->addRating(m_man.chainSize() / 100); //TODO: what about other peers?
m_hashMan.reset(m_chain.number() + 1);
continueSync();
onPeerDoneHashes(_peer, false);
}
else if (m_hashes.size() > _peer->m_expectedHashes)
{
_peer->disable("Too many hashes");
m_hashes.clear();
m_syncingLatestHash = h256();
setState(SyncState::HashesNegotiate);
continueSync(); ///Try with some other peer, keep the chain
}
else
continueSync(_peer); /// Grab next hashes
DEV_INVARIANT_CHECK;
}
void EthereumHost::onPeerDoneHashes(EthereumPeer* _peer, bool _localChain)
{
assert(_peer->m_asking == Asking::Nothing);
m_needSyncHashes = false;
m_syncingLatestHash = h256();
setState(SyncState::Blocks);
if (_peer->m_protocolVersion != protocolVersion() || _localChain)
{
m_man.resetToChain(m_hashes);
m_hashes.clear();
m_hashMan.reset(m_chain.number() + 1);
_peer->addRating(m_man.chainSize() / 100); //TODO: what about other peers?
}
m_hashMan.reset(m_chain.number() + 1);
m_hashes.clear();
continueSync();
}
void EthereumHost::onPeerBlocks(EthereumPeer* _peer, RLP const& _r)
{
RecursiveGuard l(x_sync);
DEV_INVARIANT_CHECK;
_peer->setAsking(Asking::Nothing);
unsigned itemCount = _r.itemCount();
clog(NetMessageSummary) << "Blocks (" << dec << itemCount << "entries)" << (itemCount ? "" : ": NoMoreBlocks");
@ -426,6 +454,7 @@ void EthereumHost::onPeerBlocks(EthereumPeer* _peer, RLP const& _r)
unsigned unknown = 0;
unsigned got = 0;
unsigned repeated = 0;
h256 lastUnknown;
for (unsigned i = 0; i < itemCount; ++i)
{
@ -454,6 +483,7 @@ void EthereumHost::onPeerBlocks(EthereumPeer* _peer, RLP const& _r)
break;
case ImportResult::UnknownParent:
lastUnknown = h;
unknown++;
break;
@ -468,13 +498,22 @@ void EthereumHost::onPeerBlocks(EthereumPeer* _peer, RLP const& _r)
}
clog(NetMessageSummary) << dec << success << "imported OK," << unknown << "with unknown parents," << future << "with future timestamps," << got << " already known," << repeated << " repeats received.";
if (m_state == SyncState::NewBlocks && unknown > 0)
{
_peer->m_latestHash = lastUnknown;
resetSyncTo(lastUnknown);
}
continueSync(_peer);
DEV_INVARIANT_CHECK;
}
void EthereumHost::onPeerNewHashes(EthereumPeer* _peer, h256s const& _hashes)
{
RecursiveGuard l(x_sync);
if (isSyncing_UNSAFE() || _peer->isConversing())
DEV_INVARIANT_CHECK;
if (isSyncing() || _peer->isConversing())
{
clog(NetMessageSummary) << "Ignoring new hashes since we're already downloading.";
return;
@ -482,12 +521,14 @@ void EthereumHost::onPeerNewHashes(EthereumPeer* _peer, h256s const& _hashes)
clog(NetNote) << "New block hash discovered: syncing without help.";
_peer->m_syncHashNumber = 0;
onPeerHashes(_peer, _hashes, true);
DEV_INVARIANT_CHECK;
}
void EthereumHost::onPeerNewBlock(EthereumPeer* _peer, RLP const& _r)
{
RecursiveGuard l(x_sync);
if (isSyncing_UNSAFE() || _peer->isConversing())
DEV_INVARIANT_CHECK;
if ((isSyncing() || _peer->isConversing()) && m_state != SyncState::NewBlocks)
{
clog(NetMessageSummary) << "Ignoring new blocks since we're already downloading.";
return;
@ -527,9 +568,7 @@ void EthereumHost::onPeerNewBlock(EthereumPeer* _peer, RLP const& _r)
clog(NetMessageSummary) << "Received block with no known parent. Resyncing...";
_peer->m_latestHash = h;
_peer->m_totalDifficulty = difficulty;
m_needSyncHashes = true;
m_needSyncBlocks = true;
m_syncingLatestHash = h;
resetSyncTo(h);;
sync = true;
}
}
@ -543,6 +582,7 @@ void EthereumHost::onPeerNewBlock(EthereumPeer* _peer, RLP const& _r)
if (sync)
continueSync();
}
DEV_INVARIANT_CHECK;
}
void EthereumHost::onPeerTransactions(EthereumPeer* _peer, RLP const& _r)
@ -587,6 +627,8 @@ void EthereumHost::onPeerAborting(EthereumPeer* _peer)
void EthereumHost::continueSync()
{
if (m_state == SyncState::WaitingQueue)
setState(m_lastActiveState);
clog(NetAllDetail) << "Continuing sync for all peers";
foreachPeer([&](EthereumPeer* _p)
{
@ -597,10 +639,11 @@ void EthereumHost::continueSync()
void EthereumHost::continueSync(EthereumPeer* _peer)
{
DEV_INVARIANT_CHECK;
assert(_peer->m_asking == Asking::Nothing);
bool otherPeerV60Sync = false;
bool otherPeerV61Sync = false;
if (m_needSyncHashes)
if (needHashes())
{
if (!peerShouldGrabChain(_peer))
{
@ -632,7 +675,7 @@ void EthereumHost::continueSync(EthereumPeer* _peer)
}
if (_peer->m_protocolVersion == protocolVersion() && !m_hashMan.isComplete())
{
m_syncingV61 = true;
setState(SyncState::HashesParallel);
_peer->requestHashes(); /// v61+ and not catching up to a particular hash
}
else
@ -646,19 +689,19 @@ void EthereumHost::continueSync(EthereumPeer* _peer)
if (_peer->m_totalDifficulty >= m_syncingTotalDifficulty)
{
_peer->requestHashes(m_syncingLatestHash);
m_syncingV61 = false;
m_estimatedHashes = _peer->m_expectedHashes;
setState(SyncState::HashesSingle);
m_estimatedHashes = _peer->m_expectedHashes - (_peer->m_protocolVersion == protocolVersion() ? 0 : c_chainReorgSize);
}
else
_peer->setIdle();
}
}
else if (m_needSyncBlocks)
else if (needBlocks())
{
if (m_man.isComplete())
{
// Done our chain-get.
m_needSyncBlocks = false;
setState(SyncState::Idle);
clog(NetNote) << "Chain download complete.";
// 1/100th for each useful block hash.
_peer->addRating(m_man.chainSize() / 100); //TODO: what about other peers?
@ -679,6 +722,8 @@ void EthereumHost::continueSync(EthereumPeer* _peer)
else if (m_bq.knownFull())
{
clog(NetAllDetail) << "Waiting for block queue before downloading blocks";
m_lastActiveState = m_state;
setState(SyncState::WaitingQueue);
_peer->setIdle();
}
else
@ -687,6 +732,7 @@ void EthereumHost::continueSync(EthereumPeer* _peer)
}
else
_peer->setIdle();
DEV_INVARIANT_CHECK;
}
bool EthereumHost::peerCanHelp(EthereumPeer* _peer) const
@ -733,16 +779,42 @@ bool EthereumHost::peerShouldGrabChain(EthereumPeer* _peer) const
}
}
bool EthereumHost::isSyncing_UNSAFE() const
bool EthereumHost::isSyncing() const
{
return m_needSyncBlocks || m_needSyncHashes;
return m_state != SyncState::Idle;
}
HashChainStatus EthereumHost::status()
SyncStatus EthereumHost::status() const
{
RecursiveGuard l(x_sync);
if (m_syncingV61)
return HashChainStatus { static_cast<unsigned>(m_hashMan.chainSize()), static_cast<unsigned>(m_hashMan.gotCount()), false };
return HashChainStatus { m_estimatedHashes > 0 ? m_estimatedHashes - c_chainReorgSize : 0, static_cast<unsigned>(m_hashes.size()), m_estimatedHashes > 0 };
SyncStatus res;
res.state = m_state;
if (m_state == SyncState::HashesParallel)
{
res.hashesReceived = m_hashMan.hashesGot().size();
res.hashesTotal = m_hashMan.chainSize();
}
else if (m_state == SyncState::HashesSingle)
{
res.hashesTotal = m_estimatedHashes;
res.hashesReceived = static_cast<unsigned>(m_hashes.size());
res.hashesEstimated = true;
}
else if (m_state == SyncState::Blocks || m_state == SyncState::NewBlocks || m_state == SyncState::WaitingQueue)
{
res.blocksTotal = m_man.chainSize();
res.blocksReceived = m_man.blocksGot().size();
}
return res;
}
bool EthereumHost::invariants() const
{
if (m_state == SyncState::HashesNegotiate && !m_hashes.empty())
return false;
if (needBlocks() && (m_syncingLatestHash || !m_hashes.empty()))
return false;
return true;
}

32
libethereum/EthereumHost.h

@ -54,9 +54,10 @@ class BlockQueue;
* @warning None of this is thread-safe. You have been warned.
* @doWork Syncs to peers and sends new blocks and transactions.
*/
class EthereumHost: public p2p::HostCapability<EthereumPeer>, Worker
class EthereumHost: public p2p::HostCapability<EthereumPeer>, Worker, HasInvariants
{
public:
/// Start server, but don't listen.
EthereumHost(BlockChain const& _ch, TransactionQueue& _tq, BlockQueue& _bq, u256 _networkId);
@ -70,7 +71,7 @@ public:
void reset();
DownloadMan const& downloadMan() const { return m_man; }
bool isSyncing() const { RecursiveGuard l(x_sync); return isSyncing_UNSAFE(); }
bool isSyncing() const;
bool isBanned(p2p::NodeId const& _id) const { return !!m_banned.count(_id); }
void noteNewTransactions() { m_newTransactions = true; }
@ -87,16 +88,21 @@ public:
DownloadMan& downloadMan() { return m_man; }
HashDownloadMan& hashDownloadMan() { return m_hashMan; }
BlockChain const& chain() { return m_chain; }
HashChainStatus status();
SyncStatus status() const;
static char const* stateName(SyncState _s) { return s_stateNames[static_cast<int>(_s)]; }
static unsigned const c_oldProtocolVersion;
private:
static char const* const s_stateNames[static_cast<int>(SyncState::Size)];
std::tuple<std::vector<std::shared_ptr<EthereumPeer>>, std::vector<std::shared_ptr<EthereumPeer>>, std::vector<std::shared_ptr<p2p::Session>>> randomSelection(unsigned _percent = 25, std::function<bool(EthereumPeer*)> const& _allow = [](EthereumPeer const*){ return true; });
void foreachPeerPtr(std::function<void(std::shared_ptr<EthereumPeer>)> const& _f) const;
void foreachPeer(std::function<void(EthereumPeer*)> const& _f) const;
bool isSyncing_UNSAFE() const;
void resetSyncTo(h256 const& _h);
bool needHashes() const { return m_state == SyncState::HashesNegotiate || m_state == SyncState::HashesSingle || m_state == SyncState::HashesParallel; }
bool needBlocks() const { return m_state == SyncState::Blocks || m_state == SyncState::NewBlocks; }
/// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network.
void doWork();
@ -127,6 +133,9 @@ private:
bool peerCanHelp(EthereumPeer* _peer) const;
unsigned estimateHashes();
void estimatePeerHashes(EthereumPeer* _peer);
void setState(SyncState _s);
bool invariants() const override;
BlockChain const& m_chain;
TransactionQueue& m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain.
@ -147,14 +156,13 @@ private:
bool m_newBlocks = false;
mutable RecursiveMutex x_sync;
bool m_needSyncHashes = true; ///< Indicates if need to downlad hashes
bool m_needSyncBlocks = true; ///< Indicates if we still need to download some blocks
h256 m_syncingLatestHash; ///< Latest block's hash, as of the current sync.
u256 m_syncingTotalDifficulty; ///< Latest block's total difficulty, as of the current sync.
h256s m_hashes; ///< List of hashes with unknown block numbers. Used for PV60 chain downloading and catching up to a particular unknown
unsigned m_estimatedHashes = 0; ///< Number of estimated hashes for the last peer over PV60. Used for status reporting only.
bool m_syncingV61 = false; ///< True if recent activity was over pv61+. Used for status reporting only.
bool m_continueSync = false; ///< True when the block queue has processed a block; we should restart grabbing blocks.
SyncState m_state = SyncState::Idle; ///< Current sync state
SyncState m_lastActiveState = SyncState::Idle; ///< Saved state before entering waiting queue mode
h256 m_syncingLatestHash; ///< Latest block's hash, as of the current sync.
u256 m_syncingTotalDifficulty; ///< Latest block's total difficulty, as of the current sync.
h256s m_hashes; ///< List of hashes with unknown block numbers. Used for PV60 chain downloading and catching up to a particular unknown
unsigned m_estimatedHashes = 0; ///< Number of estimated hashes for the last peer over PV60. Used for status reporting only.
bool m_continueSync = false; ///< True when the block queue has processed a block; we should restart grabbing blocks.
};
}

59
libethereum/ExtVM.cpp

@ -20,18 +20,69 @@
*/
#include "ExtVM.h"
#include <exception>
#include <boost/thread.hpp>
#include "Executive.h"
using namespace std;
using namespace dev;
using namespace dev::eth;
namespace
{
static unsigned const c_depthLimit = 1024;
/// Upper bound of stack space needed by single CALL/CREATE execution. Set experimentally.
static size_t const c_singleExecutionStackSize = 12 * 1024;
/// Standard OSX thread stack limit. Should be reasonable for other platforms too.
static size_t const c_defaultStackSize = 512 * 1024;
/// On what depth execution should be offloaded to additional separated stack space.
static unsigned const c_offloadPoint = c_defaultStackSize / c_singleExecutionStackSize;
void goOnOffloadedStack(Executive& _e, OnOpFunc const& _onOp)
{
// Set new stack size enouth to handle the rest of the calls up to the limit.
boost::thread::attributes attrs;
attrs.set_stack_size((c_depthLimit - c_offloadPoint) * c_singleExecutionStackSize);
// Create new thread with big stack and join immediately.
// TODO: It is possible to switch the implementation to Boost.Context or similar when the API is stable.
std::exception_ptr exception;
boost::thread{attrs, [&]{
try
{
_e.go(_onOp);
}
catch (...)
{
exception = std::current_exception(); // Catch all exceptions to be rethrown in parent thread.
}
}}.join();
if (exception)
std::rethrow_exception(exception);
}
void go(unsigned _depth, Executive& _e, OnOpFunc const& _onOp)
{
// If in the offloading point we need to switch to additional separated stack space.
// Current stack is too small to handle more CALL/CREATE executions.
// It needs to be done only once as newly allocated stack space it enough to handle
// the rest of the calls up to the depth limit (c_depthLimit).
if (_depth == c_offloadPoint)
goOnOffloadedStack(_e, _onOp);
else
_e.go(_onOp);
}
}
bool ExtVM::call(CallParameters& _p)
{
Executive e(m_s, lastHashes, depth + 1);
if (!e.call(_p, gasPrice, origin))
{
e.go(_p.onOp);
go(depth, e, _p.onOp);
e.accrueSubState(sub);
}
_p.gas = e.gas();
@ -47,7 +98,7 @@ h160 ExtVM::create(u256 _endowment, u256& io_gas, bytesConstRef _code, OnOpFunc
Executive e(m_s, lastHashes, depth + 1);
if (!e.create(myAddress, _endowment, gasPrice, io_gas, _code, origin))
{
e.go(_onOp);
go(depth, e, _onOp);
e.accrueSubState(sub);
}
io_gas = e.gas();

48
libevmasm/CommonSubexpressionEliminator.cpp

@ -79,31 +79,43 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _co
void CommonSubexpressionEliminator::optimizeBreakingItem()
{
if (!m_breakingItem || *m_breakingItem != AssemblyItem(Instruction::JUMPI))
if (!m_breakingItem)
return;
ExpressionClasses& classes = m_state.expressionClasses();
SourceLocation const& location = m_breakingItem->getLocation();
AssemblyItem::JumpType jumpType = m_breakingItem->getJumpType();
Id condition = m_state.stackElement(m_state.stackHeight() - 1, location);
Id zero = m_state.expressionClasses().find(u256(0));
if (m_state.expressionClasses().knownToBeDifferent(condition, zero))
if (*m_breakingItem == AssemblyItem(Instruction::JUMPI))
{
feedItem(AssemblyItem(Instruction::SWAP1, location), true);
feedItem(AssemblyItem(Instruction::POP, location), true);
AssemblyItem::JumpType jumpType = m_breakingItem->getJumpType();
AssemblyItem item(Instruction::JUMP, location);
item.setJumpType(jumpType);
m_breakingItem = m_state.expressionClasses().storeItem(item);
return;
Id condition = m_state.stackElement(m_state.stackHeight() - 1, location);
if (classes.knownNonZero(condition))
{
feedItem(AssemblyItem(Instruction::SWAP1, location), true);
feedItem(AssemblyItem(Instruction::POP, location), true);
AssemblyItem item(Instruction::JUMP, location);
item.setJumpType(jumpType);
m_breakingItem = classes.storeItem(item);
}
else if (classes.knownZero(condition))
{
AssemblyItem it(Instruction::POP, location);
feedItem(it, true);
feedItem(it, true);
m_breakingItem = nullptr;
}
}
Id negatedCondition = m_state.expressionClasses().find(Instruction::ISZERO, {condition});
if (m_state.expressionClasses().knownToBeDifferent(negatedCondition, zero))
else if (*m_breakingItem == AssemblyItem(Instruction::RETURN))
{
AssemblyItem it(Instruction::POP, location);
feedItem(it, true);
feedItem(it, true);
m_breakingItem = nullptr;
Id size = m_state.stackElement(m_state.stackHeight() - 1, location);
if (classes.knownZero(size))
{
feedItem(AssemblyItem(Instruction::POP, location), true);
feedItem(AssemblyItem(Instruction::POP, location), true);
AssemblyItem item(Instruction::STOP, location);
m_breakingItem = classes.storeItem(item);
}
}
}

25
libp2p/Host.cpp

@ -61,17 +61,22 @@ ReputationManager::ReputationManager()
void ReputationManager::noteRude(Session const& _s, std::string const& _sub)
{
m_nodes[make_pair(_s.id(), _s.info().clientVersion)].subs[_sub].isRude = true;
DEV_WRITE_GUARDED(x_nodes)
m_nodes[make_pair(_s.id(), _s.info().clientVersion)].subs[_sub].isRude = true;
}
bool ReputationManager::isRude(Session const& _s, std::string const& _sub) const
{
auto nit = m_nodes.find(make_pair(_s.id(), _s.info().clientVersion));
if (nit == m_nodes.end())
return false;
auto sit = nit->second.subs.find(_sub);
bool ret = sit == nit->second.subs.end() ? false : sit->second.isRude;
return _sub.empty() ? ret : (ret || isRude(_s));
DEV_READ_GUARDED(x_nodes)
{
auto nit = m_nodes.find(make_pair(_s.id(), _s.info().clientVersion));
if (nit == m_nodes.end())
return false;
auto sit = nit->second.subs.find(_sub);
bool ret = sit == nit->second.subs.end() ? false : sit->second.isRude;
return _sub.empty() ? ret : (ret || isRude(_s));
}
return false;
}
Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bytesConstRef _restoreNetwork):
@ -191,7 +196,7 @@ void Host::doneWorking()
m_sessions.clear();
}
void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint)
void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameCoder* _io, std::shared_ptr<RLPXSocket> const& _s)
{
// session maybe ingress or egress so m_peers and node table entries may not exist
shared_ptr<Peer> p;
@ -211,7 +216,7 @@ void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io
}
if (p->isOffline())
p->m_lastConnected = std::chrono::system_clock::now();
p->endpoint.address = _endpoint.address();
p->endpoint.address = _s->remoteEndpoint().address();
auto protocolVersion = _rlp[0].toInt<unsigned>();
auto clientVersion = _rlp[1].toString();
@ -230,7 +235,7 @@ void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io
clog(NetMessageSummary) << "Hello: " << clientVersion << "V[" << protocolVersion << "]" << _id << showbase << capslog.str() << dec << listenPort;
// create session so disconnects are managed
auto ps = make_shared<Session>(this, _io, p, PeerSessionInfo({_id, clientVersion, _endpoint.address().to_string(), listenPort, chrono::steady_clock::duration(), _rlp[2].toSet<CapDesc>(), 0, map<string, string>()}));
auto ps = make_shared<Session>(this, _io, _s, p, PeerSessionInfo({_id, clientVersion, p->endpoint.address.to_string(), listenPort, chrono::steady_clock::duration(), _rlp[2].toSet<CapDesc>(), 0, map<string, string>()}));
if (protocolVersion < dev::p2p::c_protocolVersion - 1)
{
ps->disconnect(IncompatibleProtocol);

6
libp2p/Host.h

@ -40,7 +40,8 @@
#include "HostCapability.h"
#include "Network.h"
#include "Peer.h"
#include "RLPxFrameIO.h"
#include "RLPXSocket.h"
#include "RLPXFrameCoder.h"
#include "Common.h"
namespace ba = boost::asio;
namespace bi = ba::ip;
@ -99,6 +100,7 @@ public:
private:
std::unordered_map<std::pair<p2p::NodeId, std::string>, Reputation> m_nodes; ///< Nodes that were impolite while syncing. We avoid syncing from these if possible.
SharedMutex mutable x_nodes;
};
/**
@ -196,7 +198,7 @@ public:
NodeId id() const { return m_alias.pub(); }
/// Validates and starts peer session, taking ownership of _io. Disconnects and returns false upon error.
void startPeerSession(Public const& _id, RLP const& _hello, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint);
void startPeerSession(Public const& _id, RLP const& _hello, RLPXFrameCoder* _io, std::shared_ptr<RLPXSocket> const& _s);
protected:
void onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e);

30
libp2p/RLPxFrameIO.cpp → libp2p/RLPXFrameCoder.cpp

@ -14,16 +14,14 @@
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 RLPXFrameIO.cpp
/** @file RLPXFrameCoder.cpp
* @author Alex Leverington <nessence@gmail.com>
* @date 2015
*/
#include "RLPxFrameIO.h"
#include "RLPXFrameCoder.h"
#include <libdevcore/Assertions.h>
#include "Host.h"
#include "Session.h"
#include "Peer.h"
#include "RLPxHandshake.h"
using namespace std;
@ -31,7 +29,7 @@ using namespace dev;
using namespace dev::p2p;
using namespace CryptoPP;
RLPXFrameIO::RLPXFrameIO(RLPXHandshake const& _init): m_socket(_init.m_socket)
RLPXFrameCoder::RLPXFrameCoder(RLPXHandshake const& _init)
{
// we need:
// originated?
@ -94,7 +92,7 @@ RLPXFrameIO::RLPXFrameIO(RLPXHandshake const& _init): m_socket(_init.m_socket)
m_ingressMac.Update(keyMaterial.data(), keyMaterial.size());
}
void RLPXFrameIO::writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes)
void RLPXFrameCoder::writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes)
{
// _packet = type || rlpList()
@ -126,7 +124,7 @@ void RLPXFrameIO::writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes)
egressDigest().ref().copyTo(macRef);
}
bool RLPXFrameIO::authAndDecryptHeader(bytesRef io)
bool RLPXFrameCoder::authAndDecryptHeader(bytesRef io)
{
asserts(io.size() == h256::size);
updateIngressMACWithHeader(io);
@ -138,7 +136,7 @@ bool RLPXFrameIO::authAndDecryptHeader(bytesRef io)
return true;
}
bool RLPXFrameIO::authAndDecryptFrame(bytesRef io)
bool RLPXFrameCoder::authAndDecryptFrame(bytesRef io)
{
bytesRef cipherText(io.cropped(0, io.size() - h128::size));
updateIngressMACWithFrame(cipherText);
@ -149,7 +147,7 @@ bool RLPXFrameIO::authAndDecryptFrame(bytesRef io)
return true;
}
h128 RLPXFrameIO::egressDigest()
h128 RLPXFrameCoder::egressDigest()
{
SHA3_256 h(m_egressMac);
h128 digest;
@ -157,7 +155,7 @@ h128 RLPXFrameIO::egressDigest()
return digest;
}
h128 RLPXFrameIO::ingressDigest()
h128 RLPXFrameCoder::ingressDigest()
{
SHA3_256 h(m_ingressMac);
h128 digest;
@ -165,29 +163,29 @@ h128 RLPXFrameIO::ingressDigest()
return digest;
}
void RLPXFrameIO::updateEgressMACWithHeader(bytesConstRef _headerCipher)
void RLPXFrameCoder::updateEgressMACWithHeader(bytesConstRef _headerCipher)
{
updateMAC(m_egressMac, _headerCipher.cropped(0, 16));
}
void RLPXFrameIO::updateEgressMACWithFrame(bytesConstRef _cipher)
void RLPXFrameCoder::updateEgressMACWithFrame(bytesConstRef _cipher)
{
m_egressMac.Update(_cipher.data(), _cipher.size());
updateMAC(m_egressMac);
}
void RLPXFrameIO::updateIngressMACWithHeader(bytesConstRef _headerCipher)
void RLPXFrameCoder::updateIngressMACWithHeader(bytesConstRef _headerCipher)
{
updateMAC(m_ingressMac, _headerCipher.cropped(0, 16));
}
void RLPXFrameIO::updateIngressMACWithFrame(bytesConstRef _cipher)
void RLPXFrameCoder::updateIngressMACWithFrame(bytesConstRef _cipher)
{
m_ingressMac.Update(_cipher.data(), _cipher.size());
updateMAC(m_ingressMac);
}
void RLPXFrameIO::updateMAC(SHA3_256& _mac, bytesConstRef _seed)
void RLPXFrameCoder::updateMAC(SHA3_256& _mac, bytesConstRef _seed)
{
if (_seed.size() && _seed.size() != h128::size)
asserts(false);

51
libp2p/RLPxFrameIO.h → libp2p/RLPXFrameCoder.h

@ -14,7 +14,7 @@
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 RLPXFrameIO.h
/** @file RLPXFrameCoder.h
* @author Alex Leverington <nessence@gmail.com>
* @date 2015
*/
@ -23,13 +23,10 @@
#pragma once
#include <memory>
#include <libdevcrypto/Common.h>
#include <libdevcore/Guards.h>
#include <libdevcrypto/ECDHE.h>
#include <libdevcrypto/CryptoPP.h>
#include <libdevcore/Guards.h>
#include "Common.h"
namespace ba = boost::asio;
namespace bi = boost::asio::ip;
namespace dev
{
@ -39,45 +36,21 @@ namespace p2p
class RLPXHandshake;
/**
* @brief Encoder/decoder transport for RLPx connections established by RLPXHandshake.
* Managed (via shared_ptr) socket for use by RLPXHandshake and RLPXFrameIO.
*
* Thread Safety
* Distinct Objects: Safe.
* Shared objects: Unsafe.
* * an instance method must not be called concurrently
* * a writeSingleFramePacket can be called concurrent to authAndDecryptHeader OR authAndDecryptFrame
*/
class RLPXSocket: public std::enable_shared_from_this<RLPXSocket>
{
public:
RLPXSocket(bi::tcp::socket* _socket): m_socket(std::move(*_socket)) {}
~RLPXSocket() { close(); }
bool isConnected() const { return m_socket.is_open(); }
void close() { try { boost::system::error_code ec; m_socket.shutdown(bi::tcp::socket::shutdown_both, ec); if (m_socket.is_open()) m_socket.close(); } catch (...){} }
bi::tcp::endpoint remoteEndpoint() { boost::system::error_code ec; return m_socket.remote_endpoint(ec); }
bi::tcp::socket& ref() { return m_socket; }
protected:
bi::tcp::socket m_socket;
};
/**
* @brief Encoder/decoder transport for RLPx connections established by RLPXHandshake.
* @brief Encoder/decoder transport for RLPx connection established by RLPXHandshake.
*
* Thread Safety
* Distinct Objects: Safe.
* Shared objects: Unsafe.
*/
class RLPXFrameIO
class RLPXFrameCoder
{
friend class RLPXFrameIOMux;
friend class Session;
public:
/// Constructor.
/// Requires instance of RLPXHandshake which has completed first two phases of handshake.
RLPXFrameIO(RLPXHandshake const& _init);
~RLPXFrameIO() {}
RLPXFrameCoder(RLPXHandshake const& _init);
~RLPXFrameCoder() {}
/// Encrypt _packet as RLPx frame.
void writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes);
@ -93,7 +66,7 @@ public:
/// Return first 16 bytes of current digest from ingress mac.
h128 ingressDigest();
protected:
/// Update state of egress MAC with frame header.
void updateEgressMACWithHeader(bytesConstRef _headerCipher);
@ -106,9 +79,7 @@ protected:
/// Update state of ingress MAC with frame.
void updateIngressMACWithFrame(bytesConstRef _cipher);
bi::tcp::socket& socket() { return m_socket->ref(); }
private:
/// Update state of _mac.
void updateMAC(CryptoPP::SHA3_256& _mac, bytesConstRef _seed = bytesConstRef());
@ -125,9 +96,7 @@ private:
CryptoPP::SHA3_256 m_egressMac; ///< State of MAC for egress ciphertext.
CryptoPP::SHA3_256 m_ingressMac; ///< State of MAC for ingress ciphertext.
std::shared_ptr<RLPXSocket> m_socket;
};
}
}
}

0
libp2p/RLPXSocket.cpp

56
libp2p/RLPXSocket.h

@ -0,0 +1,56 @@
/*
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 RLPXSocket.h
* @author Alex Leverington <nessence@gmail.com>
* @date 2015
*/
#pragma once
#include "Common.h"
namespace dev
{
namespace p2p
{
/**
* @brief Shared pointer wrapper for ASIO TCP socket.
*
* Thread Safety
* Distinct Objects: Safe.
* Shared objects: Unsafe.
* * an instance method must not be called concurrently
*/
class RLPXSocket: public std::enable_shared_from_this<RLPXSocket>
{
public:
/// Constructor. Dereferences and takes ownership of _socket.
RLPXSocket(bi::tcp::socket* _socket): m_socket(std::move(*_socket)) {}
~RLPXSocket() { close(); }
bool isConnected() const { return m_socket.is_open(); }
void close() { try { boost::system::error_code ec; m_socket.shutdown(bi::tcp::socket::shutdown_both, ec); if (m_socket.is_open()) m_socket.close(); } catch (...){} }
bi::tcp::endpoint remoteEndpoint() { try { return m_socket.remote_endpoint(); } catch (...){ return bi::tcp::endpoint(); } }
bi::tcp::socket& ref() { return m_socket; }
protected:
bi::tcp::socket m_socket;
};
}
}

6
libp2p/RLPxHandshake.cpp

@ -179,7 +179,7 @@ void RLPXHandshake::transition(boost::system::error_code _ech)
/// This pointer will be freed if there is an error otherwise
/// it will be passed to Host which will take ownership.
m_io = new RLPXFrameIO(*this);
m_io = new RLPXFrameCoder(*this);
// old packet format
// 5 arguments, HelloPacket
@ -200,7 +200,7 @@ void RLPXHandshake::transition(boost::system::error_code _ech)
}
else if (m_nextState == ReadHello)
{
// Authenticate and decrypt initial hello frame with initial RLPXFrameIO
// Authenticate and decrypt initial hello frame with initial RLPXFrameCoder
// and request m_host to start session.
m_nextState = StartSession;
@ -269,7 +269,7 @@ void RLPXHandshake::transition(boost::system::error_code _ech)
try
{
RLP rlp(frame.cropped(1), RLP::ThrowOnFail | RLP::FailIfTooSmall);
m_host->startPeerSession(m_remote, rlp, m_io, m_socket->remoteEndpoint());
m_host->startPeerSession(m_remote, rlp, m_io, m_socket);
}
catch (std::exception const& _e)
{

9
libp2p/RLPxHandshake.h

@ -25,7 +25,8 @@
#include <memory>
#include <libdevcrypto/Common.h>
#include <libdevcrypto/ECDHE.h>
#include "RLPxFrameIO.h"
#include "RLPXSocket.h"
#include "RLPXFrameCoder.h"
#include "Common.h"
namespace ba = boost::asio;
namespace bi = boost::asio::ip;
@ -36,7 +37,7 @@ namespace p2p
{
/**
* @brief Setup inbound or outbound connection for communication over RLPXFrameIO.
* @brief Setup inbound or outbound connection for communication over RLPXFrameCoder.
* RLPx Spec: https://github.com/ethereum/devp2p/blob/master/rlpx.md#encrypted-handshake
*
* @todo Implement StartSession transition via lambda which is passed to constructor.
@ -47,7 +48,7 @@ namespace p2p
*/
class RLPXHandshake: public std::enable_shared_from_this<RLPXHandshake>
{
friend class RLPXFrameIO;
friend class RLPXFrameCoder;
/// Sequential states of handshake
enum State
@ -122,7 +123,7 @@ protected:
/// Used to read and write RLPx encrypted frames for last step of handshake authentication.
/// Passed onto Host which will take ownership.
RLPXFrameIO* m_io = nullptr;
RLPXFrameCoder* m_io = nullptr;
std::shared_ptr<RLPXSocket> m_socket; ///< Socket.
boost::asio::deadline_timer m_idleTimer; ///< Timer which enforces c_timeout.

35
libp2p/Session.cpp

@ -27,23 +27,24 @@
#include <libdevcore/CommonIO.h>
#include <libdevcore/StructuredLogger.h>
#include <libethcore/Exceptions.h>
#include "RLPxHandshake.h"
#include "Host.h"
#include "Capability.h"
using namespace std;
using namespace dev;
using namespace dev::p2p;
Session::Session(Host* _s, RLPXFrameIO* _io, std::shared_ptr<Peer> const& _n, PeerSessionInfo _info):
m_server(_s),
Session::Session(Host* _h, RLPXFrameCoder* _io, std::shared_ptr<RLPXSocket> const& _s, std::shared_ptr<Peer> const& _n, PeerSessionInfo _info):
m_server(_h),
m_io(_io),
m_socket(m_io->socket()),
m_socket(_s),
m_peer(_n),
m_info(_info),
m_ping(chrono::steady_clock::time_point::max())
{
m_peer->m_lastDisconnect = NoDisconnect;
m_lastReceived = m_connect = chrono::steady_clock::now();
m_info.socketId = _io->socket().native_handle();
m_info.socketId = m_socket->ref().native_handle();
}
Session::~Session()
@ -59,11 +60,12 @@ Session::~Session()
try
{
if (m_socket.is_open())
bi::tcp::socket& socket = m_socket->ref();
if (socket.is_open())
{
boost::system::error_code ec;
m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
m_socket.close();
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
socket.close();
}
}
catch (...){}
@ -308,7 +310,7 @@ void Session::send(bytes&& _msg)
if (!checkPacket(msg))
clog(NetWarn) << "INVALID PACKET CONSTRUCTED!";
if (!m_socket.is_open())
if (!m_socket->ref().is_open())
return;
bool doWrite = false;
@ -331,7 +333,7 @@ void Session::write()
out = &m_writeQueue[0];
}
auto self(shared_from_this());
ba::async_write(m_socket, ba::buffer(*out), [this, self](boost::system::error_code ec, std::size_t /*length*/)
ba::async_write(m_socket->ref(), ba::buffer(*out), [this, self](boost::system::error_code ec, std::size_t /*length*/)
{
ThreadContext tc(info().id.abridged());
ThreadContext tc2(info().clientVersion);
@ -357,13 +359,14 @@ void Session::drop(DisconnectReason _reason)
{
if (m_dropped)
return;
if (m_socket.is_open())
bi::tcp::socket& socket = m_socket->ref();
if (socket.is_open())
try
{
clog(NetConnect) << "Closing " << m_socket.remote_endpoint() << "(" << reasonOf(_reason) << ")";
clog(NetConnect) << "Closing " << socket.remote_endpoint() << "(" << reasonOf(_reason) << ")";
boost::system::error_code ec;
m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
m_socket.close();
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
socket.close();
}
catch (...) {}
@ -384,7 +387,7 @@ void Session::disconnect(DisconnectReason _reason)
m_peer->endpoint, // TODO: may not be 100% accurate
m_server->peerCount()
);
if (m_socket.is_open())
if (m_socket->ref().is_open())
{
RLPStream s;
prep(s, DisconnectPacket, 1) << (int)_reason;
@ -406,7 +409,7 @@ void Session::doRead()
return;
auto self(shared_from_this());
ba::async_read(m_socket, boost::asio::buffer(m_data, h256::size), [this,self](boost::system::error_code ec, std::size_t length)
ba::async_read(m_socket->ref(), boost::asio::buffer(m_data, h256::size), [this,self](boost::system::error_code ec, std::size_t length)
{
ThreadContext tc(info().id.abridged());
ThreadContext tc2(info().clientVersion);
@ -443,7 +446,7 @@ void Session::doRead()
/// read padded frame and mac
auto tlen = frameSize + ((16 - (frameSize % 16)) % 16) + h128::size;
ba::async_read(m_socket, boost::asio::buffer(m_data, tlen), [this, self, headerRLP, frameSize, tlen](boost::system::error_code ec, std::size_t length)
ba::async_read(m_socket->ref(), boost::asio::buffer(m_data, tlen), [this, self, headerRLP, frameSize, tlen](boost::system::error_code ec, std::size_t length)
{
ThreadContext tc(info().id.abridged());
ThreadContext tc2(info().clientVersion);

11
libp2p/Session.h

@ -33,7 +33,8 @@
#include <libdevcore/RLP.h>
#include <libdevcore/RangeMask.h>
#include <libdevcore/Guards.h>
#include "RLPxHandshake.h"
#include "RLPXFrameCoder.h"
#include "RLPXSocket.h"
#include "Common.h"
namespace dev
@ -55,7 +56,7 @@ class Session: public std::enable_shared_from_this<Session>
friend class HostCapabilityFace;
public:
Session(Host* _server, RLPXFrameIO* _io, std::shared_ptr<Peer> const& _n, PeerSessionInfo _info);
Session(Host* _server, RLPXFrameCoder* _io, std::shared_ptr<RLPXSocket> const& _s, std::shared_ptr<Peer> const& _n, PeerSessionInfo _info);
virtual ~Session();
void start();
@ -63,7 +64,7 @@ public:
void ping();
bool isConnected() const { return m_socket.is_open(); }
bool isConnected() const { return m_socket->ref().is_open(); }
NodeId id() const;
unsigned socketId() const { return m_info.socketId; }
@ -107,8 +108,8 @@ private:
Host* m_server; ///< The host that owns us. Never null.
RLPXFrameIO* m_io; ///< Transport over which packets are sent.
bi::tcp::socket& m_socket; ///< Socket for the peer's connection.
RLPXFrameCoder* m_io; ///< Transport over which packets are sent.
std::shared_ptr<RLPXSocket> m_socket; ///< Socket of peer's connection.
Mutex x_writeQueue; ///< Mutex for the write queue.
std::deque<bytes> m_writeQueue; ///< The write queue.
std::array<byte, 16777216> m_data; ///< Buffer for ingress packet data.

5
libsolidity/Compiler.cpp

@ -298,7 +298,10 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters)
stackDepth -= type->getSizeOnStack();
}
// note that the stack is not cleaned up here
m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN;
if (dataOffset == 0)
m_context << eth::Instruction::STOP;
else
m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN;
}
void Compiler::registerStateVariables(ContractDefinition const& _contract)

259
mix/ClientModel.cpp

@ -30,6 +30,7 @@
#include <jsonrpccpp/server.h>
#include <libethcore/CommonJS.h>
#include <libethereum/Transaction.h>
#include <libdevcore/FixedHash.h>
#include "DebuggingStateWrapper.h"
#include "Exceptions.h"
#include "QContractDefinition.h"
@ -82,7 +83,6 @@ ClientModel::ClientModel():
qRegisterMetaType<QCallData*>("QCallData");
qRegisterMetaType<RecordLogEntry*>("RecordLogEntry*");
connect(this, &ClientModel::runComplete, this, &ClientModel::showDebugger, Qt::QueuedConnection);
m_client.reset(new MixClient(QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString()));
m_ethAccounts = make_shared<FixedAccountHolder>([=](){return m_client.get();}, std::vector<KeyPair>());
@ -111,7 +111,7 @@ QString ClientModel::apiCall(QString const& _message)
void ClientModel::mine()
{
if (m_running || m_mining)
if (m_mining)
BOOST_THROW_EXCEPTION(ExecutionStateException());
m_mining = true;
emit miningStarted();
@ -206,15 +206,17 @@ QVariantList ClientModel::gasCosts() const
return res;
}
void ClientModel::setupState(QVariantMap _state)
void ClientModel::setupScenario(QVariantMap _scenario)
{
QVariantList stateAccounts = _state.value("accounts").toList();
QVariantList stateContracts = _state.value("contracts").toList();
QVariantList transactions = _state.value("transactions").toList();
onStateReset();
WriteGuard(x_queueTransactions);
m_running = true;
unordered_map<Address, Account> accounts;
std::vector<KeyPair> userAccounts;
QVariantList blocks = _scenario.value("blocks").toList();
QVariantList stateAccounts = _scenario.value("accounts").toList();
m_accounts.clear();
m_accountsSecret.clear();
for (auto const& b: stateAccounts)
{
QVariantMap account = b.toMap();
@ -222,7 +224,7 @@ void ClientModel::setupState(QVariantMap _state)
if (account.contains("secret"))
{
KeyPair key(Secret(account.value("secret").toString().toStdString()));
userAccounts.push_back(key);
m_accountsSecret.push_back(key);
address = key.address();
}
else if (account.contains("address"))
@ -230,29 +232,70 @@ void ClientModel::setupState(QVariantMap _state)
if (!address)
continue;
accounts[address] = Account(qvariant_cast<QEther*>(account.value("balance"))->toU256Wei(), Account::NormalCreation);
m_accounts[address] = Account(qvariant_cast<QEther*>(account.value("balance"))->toU256Wei(), Account::NormalCreation);
}
for (auto const& c: stateContracts)
m_ethAccounts->setAccounts(m_accountsSecret);
bool trToExecute = false;
for (auto const& b: blocks)
{
QVariantMap contract = c.toMap();
Address address = Address(fromHex(contract.value("address").toString().toStdString()));
Account account(qvariant_cast<QEther*>(contract.value("balance"))->toU256Wei(), Account::ContractConception);
bytes code = fromHex(contract.value("code").toString().toStdString());
account.setCode(code);
QVariantMap storageMap = contract.value("storage").toMap();
for(auto s = storageMap.cbegin(); s != storageMap.cend(); ++s)
account.setStorage(fromBigEndian<u256>(fromHex(s.key().toStdString())), fromBigEndian<u256>(fromHex(s.value().toString().toStdString())));
accounts[address] = account;
QVariantList transactions = b.toMap().value("transactions").toList();
m_queueTransactions.push_back(transactions);
trToExecute = transactions.size() > 0;
}
m_client->resetState(m_accounts, Secret(_scenario.value("miner").toMap().value("secret").toString().toStdString()));
if (m_queueTransactions.count() > 0 && trToExecute)
{
setupExecutionChain();
processNextTransactions();
}
else
m_running = false;
}
void ClientModel::setupExecutionChain()
{
connect(this, &ClientModel::newBlock, this, &ClientModel::processNextTransactions, Qt::QueuedConnection);
connect(this, &ClientModel::runFailed, this, &ClientModel::stopExecution, Qt::QueuedConnection);
connect(this, &ClientModel::runStateChanged, this, &ClientModel::finalizeBlock, Qt::QueuedConnection);
}
void ClientModel::stopExecution()
{
disconnect(this, &ClientModel::newBlock, this, &ClientModel::processNextTransactions);
disconnect(this, &ClientModel::runStateChanged, this, &ClientModel::finalizeBlock);
disconnect(this, &ClientModel::runFailed, this, &ClientModel::stopExecution);
m_running = false;
}
void ClientModel::finalizeBlock()
{
m_queueTransactions.pop_front();// pop last execution group. The last block is never mined (pending block)
if (m_queueTransactions.size() > 0)
mine();
else
{
stopExecution();
emit runComplete();
}
}
void ClientModel::processNextTransactions()
{
WriteGuard(x_queueTransactions);
vector<TransactionSettings> transactionSequence;
for (auto const& t: transactions)
for (auto const& t: m_queueTransactions.front())
{
QVariantMap transaction = t.toMap();
QString contractId = transaction.value("contractId").toString();
QString functionId = transaction.value("functionId").toString();
u256 gas = boost::get<u256>(qvariant_cast<QBigInt*>(transaction.value("gas"))->internalValue());
bool gasAuto = transaction.value("gasAuto").toBool();
u256 gas = 0;
if (transaction.value("gas").data())
gas = boost::get<u256>(qvariant_cast<QBigInt*>(transaction.value("gas"))->internalValue());
else
gasAuto = true;
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei();
u256 gasPrice = (qvariant_cast<QEther*>(transaction.value("gasPrice")))->toU256Wei();
QString sender = transaction.value("sender").toString();
@ -260,7 +303,8 @@ void ClientModel::setupState(QVariantMap _state)
bool isFunctionCall = transaction.value("isFunctionCall").toBool();
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()), isContractCreation, isFunctionCall);
Secret f = Secret(sender.toStdString());
TransactionSettings transactionSettings(contractId, functionId, value, gas, gasAuto, gasPrice, f, isContractCreation, isFunctionCall);
transactionSettings.parameterValues = transaction.value("parameters").toMap();
if (contractId == functionId || functionId == "Constructor")
@ -268,35 +312,27 @@ void ClientModel::setupState(QVariantMap _state)
transactionSequence.push_back(transactionSettings);
}
m_ethAccounts->setAccounts(userAccounts);
executeSequence(transactionSequence, accounts, Secret(_state.value("miner").toMap().value("secret").toString().toStdString()));
executeSequence(transactionSequence);
}
void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence, std::unordered_map<Address, Account> const& _accounts, Secret const& _miner)
void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence)
{
if (m_running)
{
qWarning() << "Waiting for current execution to complete";
m_runFuture.waitForFinished();
}
m_running = true;
emit runStarted();
emit runStateChanged();
m_client->resetState(_accounts, _miner);
//run sequence
m_runFuture = QtConcurrent::run([=]()
{
try
{
vector<Address> deployedContracts;
onStateReset();
m_gasCosts.clear();
for (TransactionSettings const& transaction: _sequence)
{
std::pair<QString, int> ctrInstance = resolvePair(transaction.contractId);
QString address = resolveToken(ctrInstance, deployedContracts);
QString address = resolveToken(ctrInstance);
if (!transaction.isFunctionCall)
{
callAddress(Address(address.toStdString()), bytes(), transaction);
@ -319,12 +355,7 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
break;
}
if (!f)
{
emit runFailed("Function '" + transaction.functionId + tr("' not found. Please check transactions or the contract code."));
m_running = false;
emit runStateChanged();
return;
}
if (!transaction.functionId.isEmpty())
encoder.encode(f);
for (QVariableDeclaration const* p: f->parametersList())
@ -334,7 +365,7 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
if (type->type().type == SolidityType::Type::Address && value.toString().startsWith("<"))
{
std::pair<QString, int> ctrParamInstance = resolvePair(value.toString());
value = QVariant(resolveToken(ctrParamInstance, deployedContracts));
value = QVariant(resolveToken(ctrParamInstance));
}
encoder.encode(value, type->type());
}
@ -344,8 +375,7 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
bytes param = encoder.encodedData();
contractCode.insert(contractCode.end(), param.begin(), param.end());
Address newAddress = deployContract(contractCode, transaction);
deployedContracts.push_back(newAddress);
std::pair<QString, int> contractToken = retrieveToken(transaction.contractId, deployedContracts);
std::pair<QString, int> contractToken = retrieveToken(transaction.contractId);
m_contractAddresses[contractToken] = newAddress;
m_contractNames[newAddress] = contractToken.first;
contractAddressesChanged();
@ -355,18 +385,12 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
{
auto contractAddressIter = m_contractAddresses.find(ctrInstance);
if (contractAddressIter == m_contractAddresses.end())
{
emit runFailed("Contract '" + transaction.contractId + tr(" not deployed.") + "' " + tr(" Cannot call ") + transaction.functionId);
m_running = false;
emit runStateChanged();
return;
}
callAddress(contractAddressIter->second, encoder.encodedData(), transaction);
}
m_gasCosts.append(m_client->lastExecution().gasUsed);
onNewTransaction();
}
m_running = false;
emit runComplete();
}
catch(boost::exception const&)
@ -379,37 +403,54 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
cerr << boost::current_exception_diagnostic_information();
emit runFailed(e.what());
}
m_running = false;
emit runStateChanged();
});
}
void ClientModel::executeTr(QVariantMap _tr)
{
WriteGuard(x_queueTransactions);
QVariantList trs;
trs.push_back(_tr);
m_queueTransactions.push_back(trs);
if (!m_running)
{
m_running = true;
setupExecutionChain();
processNextTransactions();
}
}
std::pair<QString, int> ClientModel::resolvePair(QString const& _contractId)
{
std::pair<QString, int> ret = std::make_pair(_contractId, 0);
if (_contractId.startsWith("<") && _contractId.endsWith(">"))
{
QStringList values = ret.first.remove("<").remove(">").split(" - ");
ret = std::make_pair(values[0], values[1].toUInt());
}
return ret;
std::pair<QString, int> ret = std::make_pair(_contractId, 0);
if (_contractId.startsWith("<") && _contractId.endsWith(">"))
{
QStringList values = ret.first.remove("<").remove(">").split(" - ");
ret = std::make_pair(values[0], values[1].toUInt());
}
if (_contractId.startsWith("0x"))
ret = std::make_pair(_contractId, -2);
return ret;
}
QString ClientModel::resolveToken(std::pair<QString, int> const& _value, vector<Address> const& _contracts)
QString ClientModel::resolveToken(std::pair<QString, int> const& _value)
{
if (_contracts.size() > 0)
return QString::fromStdString("0x" + dev::toHex(_contracts.at(_value.second).ref()));
if (_value.second == -2) //-2: first contains a real address
return _value.first;
else if (m_contractAddresses.size() > 0)
return QString::fromStdString("0x" + dev::toHex(m_contractAddresses[_value].ref()));
else
return _value.first;
}
std::pair<QString, int> ClientModel::retrieveToken(QString const& _value, vector<Address> const& _contracts)
std::pair<QString, int> ClientModel::retrieveToken(QString const& _value)
{
std::pair<QString, int> ret;
ret.first = _value;
ret.second = _contracts.size() - 1;
return ret;
std::pair<QString, int> ret;
ret.first = _value;
ret.second = m_contractAddresses.size();
return ret;
}
void ClientModel::showDebugger()
@ -633,7 +674,7 @@ RecordLogEntry* ClientModel::lastBlock() const
strGas << blockInfo.gasUsed;
stringstream strNumber;
strNumber << blockInfo.number;
RecordLogEntry* record = new RecordLogEntry(0, QString::fromStdString(strNumber.str()), tr(" - Block - "), tr("Hash: ") + QString(QString::fromStdString(dev::toHex(blockInfo.hash().ref()))), QString(), QString(), QString(), false, RecordLogEntry::RecordType::Block, QString::fromStdString(strGas.str()));
RecordLogEntry* record = new RecordLogEntry(0, QString::fromStdString(strNumber.str()), tr(" - Block - "), tr("Hash: ") + QString(QString::fromStdString(dev::toHex(blockInfo.hash().ref()))), QString(), QString(), QString(), false, RecordLogEntry::RecordType::Block, QString::fromStdString(strGas.str()), QString(), tr("Block"), QVariantMap(), QVariantList());
QQmlEngine::setObjectOwnership(record, QQmlEngine::JavaScriptOwnership);
return record;
}
@ -644,6 +685,7 @@ void ClientModel::onStateReset()
m_contractNames.clear();
m_stdContractAddresses.clear();
m_stdContractNames.clear();
m_queueTransactions.clear();
emit stateCleared();
}
@ -692,27 +734,110 @@ void ClientModel::onNewTransaction()
Address contractAddress = (bool)tr.address ? tr.address : tr.contractAddress;
auto contractAddressIter = m_contractNames.find(contractAddress);
QVariantMap inputParameters;
QVariantList logs;
if (contractAddressIter != m_contractNames.end())
{
ContractCallDataEncoder encoder;
CompiledContract const& compilerRes = m_codeModel->contract(contractAddressIter->second);
const QContractDefinition* def = compilerRes.contract();
contract = def->name();
if (creation)
function = contract;
if (abi)
{
QFunctionDefinition const* funcDef = def->getFunction(functionHash);
if (funcDef)
{
function = funcDef->name();
ContractCallDataEncoder encoder;
QStringList returnValues = encoder.decode(funcDef->returnParameters(), tr.result.output);
returned += "(";
returned += returnValues.join(", ");
returned += ")";
bytes data = tr.inputParameters;
data.erase(data.begin(), data.begin() + 4);
QStringList parameters = encoder.decode(funcDef->parametersList(), data);
for (int k = 0; k < parameters.length(); ++k)
inputParameters.insert(funcDef->parametersList().at(k)->name(), parameters.at(k));
}
}
// Fill generated logs and decode parameters
for (auto const& log: tr.logs)
{
QVariantMap l;
l.insert("address", QString::fromStdString(log.address.hex()));
std::ostringstream s;
s << log.data;
l.insert("data", QString::fromStdString(s.str()));
std::ostringstream streamTopic;
streamTopic << log.topics;
l.insert("topic", QString::fromStdString(streamTopic.str()));
auto const& sign = log.topics.front(); // first hash supposed to be the event signature. To check
auto dataIterator = log.data.begin();
int topicDataIndex = 1;
for (auto const& event: def->eventsList())
{
if (sign == event->fullHash())
{
QVariantList paramsList;
l.insert("name", event->name());
for (auto const& e: event->parametersList())
{
bytes data;
QString param;
if (!e->isIndexed())
{
data = bytes(dataIterator, dataIterator + 32);
dataIterator = dataIterator + 32;
}
else
{
data = log.topics.at(topicDataIndex).asBytes();
topicDataIndex++;
}
param = encoder.decode(e, data);
QVariantMap p;
p.insert("indexed", e->isIndexed());
p.insert("value", param);
p.insert("name", e->name());
paramsList.push_back(p);
}
l.insert("param", paramsList);
break;
}
}
logs.push_back(l);
}
}
QString sender;
for (auto const& secret: m_accountsSecret)
{
if (secret.address() == tr.sender)
{
sender = QString::fromStdString(dev::toHex(secret.secret().ref()));
break;
}
}
QString label;
if (function != QObject::tr("<none>"))
label = contract + "." + function + "()";
else
label = contract;
if (!creation)
for (auto const& ctr: m_contractAddresses)
{
if (ctr.second == tr.address)
{
contract = "<" + ctr.first.first + " - " + QString::number(ctr.first.second) + ">";
break;
}
}
RecordLogEntry* log = new RecordLogEntry(recordIndex, transactionIndex, contract, function, value, address, returned, tr.isCall(), RecordLogEntry::RecordType::Transaction, gasUsed);
RecordLogEntry* log = new RecordLogEntry(recordIndex, transactionIndex, contract, function, value, address, returned, tr.isCall(), RecordLogEntry::RecordType::Transaction,
gasUsed, sender, label, inputParameters, logs);
QQmlEngine::setObjectOwnership(log, QQmlEngine::JavaScriptOwnership);
emit newRecord(log);
}

41
mix/ClientModel.h

@ -30,12 +30,13 @@
#include <QVariantMap>
#include <QFuture>
#include <QVariableDeclaration.h>
#include <libethereum/Account.h>
#include "MachineStates.h"
namespace dev
{
namespace eth { class Account; class FixedAccountHolder; }
namespace eth { class FixedAccountHolder; }
namespace mix
{
@ -108,6 +109,14 @@ class RecordLogEntry: public QObject
Q_PROPERTY(RecordType type MEMBER m_type CONSTANT)
/// Gas used
Q_PROPERTY(QString gasUsed MEMBER m_gasUsed CONSTANT)
/// Sender
Q_PROPERTY(QString sender MEMBER m_sender CONSTANT)
/// label
Q_PROPERTY(QString label MEMBER m_label CONSTANT)
/// input parameters
Q_PROPERTY(QVariantMap parameters MEMBER m_inputParameters CONSTANT)
/// logs
Q_PROPERTY(QVariantList logs MEMBER m_logs CONSTANT)
public:
enum RecordType
@ -118,8 +127,10 @@ public:
RecordLogEntry():
m_recordIndex(0), m_call(false), m_type(RecordType::Transaction) {}
RecordLogEntry(unsigned _recordIndex, QString _transactionIndex, QString _contract, QString _function, QString _value, QString _address, QString _returned, bool _call, RecordType _type, QString _gasUsed):
m_recordIndex(_recordIndex), m_transactionIndex(_transactionIndex), m_contract(_contract), m_function(_function), m_value(_value), m_address(_address), m_returned(_returned), m_call(_call), m_type(_type), m_gasUsed(_gasUsed) {}
RecordLogEntry(unsigned _recordIndex, QString _transactionIndex, QString _contract, QString _function, QString _value, QString _address, QString _returned, bool _call, RecordType _type, QString _gasUsed,
QString _sender, QString _label, QVariantMap _inputParameters, QVariantList _logs):
m_recordIndex(_recordIndex), m_transactionIndex(_transactionIndex), m_contract(_contract), m_function(_function), m_value(_value), m_address(_address), m_returned(_returned), m_call(_call), m_type(_type), m_gasUsed(_gasUsed),
m_sender(_sender), m_label(_label), m_inputParameters(_inputParameters), m_logs(_logs) {}
private:
unsigned m_recordIndex;
@ -132,6 +143,10 @@ private:
bool m_call;
RecordType m_type;
QString m_gasUsed;
QString m_sender;
QString m_label;
QVariantMap m_inputParameters;
QVariantList m_logs;
};
/**
@ -170,9 +185,11 @@ public:
Q_INVOKABLE QString toHex(QString const& _int);
public slots:
/// Setup state, run transaction sequence, show debugger for the last transaction
/// Setup scenario, run transaction sequence, show debugger for the last transaction
/// @param _state JS object with state configuration
void setupState(QVariantMap _state);
void setupScenario(QVariantMap _scenario);
/// Execute the given @param _tr on the current state
void executeTr(QVariantMap _tr);
/// Show the debugger for a specified record
Q_INVOKABLE void debugRecord(unsigned _index);
/// Show the debugger for an empty record
@ -224,17 +241,21 @@ private:
RecordLogEntry* lastBlock() const;
QVariantMap contractAddresses() const;
QVariantList gasCosts() const;
void executeSequence(std::vector<TransactionSettings> const& _sequence, std::unordered_map<Address, dev::eth::Account> const& _accounts, Secret const& _miner);
void executeSequence(std::vector<TransactionSettings> const& _sequence);
dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings());
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);
QString resolveToken(std::pair<QString, int> const& _value);
std::pair<QString, int> retrieveToken(QString const& _value);
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);
void processNextTransactions();
void finalizeBlock();
void stopExecution();
void setupExecutionChain();
std::atomic<bool> m_running;
std::atomic<bool> m_mining;
@ -243,12 +264,16 @@ private:
std::unique_ptr<RpcConnector> m_rpcConnector;
std::unique_ptr<Web3Server> m_web3Server;
std::shared_ptr<eth::FixedAccountHolder> m_ethAccounts;
std::unordered_map<Address, eth::Account> m_accounts;
std::vector<KeyPair> m_accountsSecret;
QList<u256> m_gasCosts;
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;
CodeModel* m_codeModel = nullptr;
QList<QVariantList> m_queueTransactions;
mutable boost::shared_mutex x_queueTransactions;
};
}

7
mix/ContractCallDataEncoder.cpp

@ -29,6 +29,7 @@
#include "QVariableDefinition.h"
#include "QFunctionDefinition.h"
#include "ContractCallDataEncoder.h"
using namespace std;
using namespace dev;
using namespace dev::solidity;
using namespace dev::mix;
@ -227,6 +228,12 @@ QVariant ContractCallDataEncoder::decode(SolidityType const& _type, bytes const&
BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Parameter declaration not found"));
}
QString ContractCallDataEncoder::decode(QVariableDeclaration* const& _param, bytes _value)
{
SolidityType const& type = _param->type()->type();
return decode(type, _value).toString();
}
QStringList ContractCallDataEncoder::decode(QList<QVariableDeclaration*> const& _returnParameters, bytes _value)
{
bytesConstRef value(&_value);

2
mix/ContractCallDataEncoder.h

@ -48,6 +48,8 @@ public:
void encode(QVariant const& _data, SolidityType const& _type);
/// Decode variable in order to be sent to QML view.
QStringList decode(QList<QVariableDeclaration*> const& _dec, bytes _value);
/// Decode @param _parameter
QString decode(QVariableDeclaration* const& _param, bytes _value);
/// Decode single variable
QVariant decode(SolidityType const& _type, bytes const& _value);
/// Get all encoded data encoded by encode function.

84
mix/MachineStates.h

@ -37,57 +37,59 @@ namespace dev
namespace mix
{
/**
/**
* @brief Store information about a machine state.
*/
struct MachineState
{
uint64_t steps;
dev::u256 curPC;
dev::eth::Instruction inst;
dev::bigint newMemSize;
dev::u256 gas;
dev::u256s stack;
dev::bytes memory;
dev::bigint gasCost;
std::unordered_map<dev::u256, dev::u256> storage;
std::vector<unsigned> levels;
unsigned codeIndex;
unsigned dataIndex;
};
struct MachineState
{
uint64_t steps;
dev::u256 curPC;
dev::eth::Instruction inst;
dev::bigint newMemSize;
dev::u256 gas;
dev::u256s stack;
dev::bytes memory;
dev::bigint gasCost;
std::unordered_map<dev::u256, dev::u256> storage;
std::vector<unsigned> levels;
unsigned codeIndex;
unsigned dataIndex;
};
/**
/**
* @brief Executed conract code info
*/
struct MachineCode
{
dev::Address address;
bytes code;
};
struct MachineCode
{
dev::Address address;
bytes code;
};
/**
/**
* @brief Store information about a machine states.
*/
struct ExecutionResult
{
ExecutionResult(): transactionIndex(std::numeric_limits<unsigned>::max()) {}
struct ExecutionResult
{
ExecutionResult(): transactionIndex(std::numeric_limits<unsigned>::max()) {}
std::vector<MachineState> machineStates;
std::vector<bytes> transactionData;
std::vector<MachineCode> executionCode;
dev::eth::ExecutionResult result;
dev::Address address;
dev::Address sender;
dev::Address contractAddress;
dev::u256 value;
dev::u256 gasUsed;
unsigned transactionIndex;
unsigned executonIndex = 0;
std::vector<MachineState> machineStates;
std::vector<bytes> transactionData;
std::vector<MachineCode> executionCode;
dev::eth::ExecutionResult result;
dev::Address address;
dev::Address sender;
dev::Address contractAddress;
dev::u256 value;
dev::u256 gasUsed;
unsigned transactionIndex;
unsigned executonIndex = 0;
bytes inputParameters;
eth::LocalisedLogEntries logs;
bool isCall() const { return transactionIndex == std::numeric_limits<unsigned>::max(); }
bool isConstructor() const { return !isCall() && !address; }
};
bool isCall() const { return transactionIndex == std::numeric_limits<unsigned>::max(); }
bool isConstructor() const { return !isCall() && !address; }
};
using ExecutionResults = std::vector<ExecutionResult>;
using ExecutionResults = std::vector<ExecutionResult>;
}
}

88
mix/MixClient.cpp

@ -22,6 +22,7 @@
#include "MixClient.h"
#include <vector>
#include <utility>
#include <libdevcore/Exceptions.h>
#include <libethereum/CanonBlockChain.h>
#include <libethereum/Transaction.h>
@ -58,9 +59,9 @@ bytes MixBlockChain::createGenesisBlock(h256 _stateRoot)
{
RLPStream block(3);
block.appendList(15)
<< h256() << EmptyListSHA3 << h160() << _stateRoot << EmptyTrie << EmptyTrie
<< LogBloom() << c_mixGenesisDifficulty << 0 << c_genesisGasLimit << 0 << (unsigned)0
<< std::string() << h256() << h64(u64(42));
<< h256() << EmptyListSHA3 << h160() << _stateRoot << EmptyTrie << EmptyTrie
<< LogBloom() << c_mixGenesisDifficulty << 0 << c_genesisGasLimit << 0 << (unsigned)0
<< std::string() << h256() << h64(u64(42));
block.appendRaw(RLPEmptyList);
block.appendRaw(RLPEmptyList);
return block.out();
@ -78,8 +79,10 @@ MixClient::~MixClient()
void MixClient::resetState(std::unordered_map<Address, Account> const& _accounts, Secret const& _miner)
{
WriteGuard l(x_state);
Guard fl(x_filtersWatches);
m_filters.clear();
for (auto& i: m_specialFilters)
i.second.clear();
@ -185,19 +188,19 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
levels.resize(ext.depth);
machineStates.push_back(MachineState{
steps,
vm.curPC(),
inst,
newMemSize,
static_cast<u256>(gas),
vm.stack(),
vm.memory(),
gasCost,
ext.state().storage(ext.myAddress),
std::move(levels),
codeIndex,
dataIndex
});
steps,
vm.curPC(),
inst,
newMemSize,
static_cast<u256>(gas),
vm.stack(),
vm.memory(),
gasCost,
ext.state().storage(ext.myAddress),
std::move(levels),
codeIndex,
dataIndex
});
};
execution.go(onOp);
@ -219,7 +222,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Out of stack"));
case TransactionException::StackUnderflow:
BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Stack underflow"));
//these should not happen in mix
//these should not happen in mix
case TransactionException::Unknown:
case TransactionException::BadInstruction:
case TransactionException::BadJumpDestination:
@ -230,6 +233,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
}
ExecutionResult d;
d.inputParameters = t.data();
d.result = er;
d.machineStates = machineStates;
d.executionCode = std::move(codes);
@ -248,29 +252,19 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
if (!_call)
{
t = _gasAuto ? replaceGas(_t, d.gasUsed, _secret) : _t;
er =_state.execute(lastHashes, t);
er = _state.execute(lastHashes, t);
if (t.isCreation() && _state.code(d.contractAddress).empty())
BOOST_THROW_EXCEPTION(OutOfGas() << errinfo_comment("Not enough gas for contract deployment"));
d.gasUsed = er.gasUsed + er.gasRefunded + er.gasForDeposit + c_callStipend;
// collect watches
h256Set changed;
Guard l(x_filtersWatches);
for (std::pair<h256 const, eth::InstalledFilter>& i: m_filters)
if (compareBlockHashes(i.second.filter.latest(), bc().currentHash()) > 0)
{
// acceptable number.
auto m = i.second.filter.matches(_state.receipt(_state.pending().size() - 1));
if (m.size())
{
// filter catches them
for (LogEntry const& l: m)
i.second.changes.push_back(LocalisedLogEntry(l));
changed.insert(i.first);
}
}
changed.insert(dev::eth::PendingChangedFilter);
m_specialFilters.at(dev::eth::PendingChangedFilter).push_back(t.sha3());
noteChanged(changed);
LocalisedLogEntries logs;
TransactionReceipt const& tr = _state.receipt(_state.pending().size() - 1);
//auto trHash = _state.pending().at(_state.pending().size() - 1).sha3();
LogEntries le = tr.log();
if (le.size())
for (unsigned j = 0; j < le.size(); ++j)
logs.insert(logs.begin(), LocalisedLogEntry(le[j]));
d.logs = logs;
}
WriteGuard l(x_executions);
m_executions.emplace_back(std::move(d));
@ -284,8 +278,7 @@ void MixClient::mine()
bc().import(m_state.blockData(), m_state.db(), ImportRequirements::Default & ~ImportRequirements::ValidNonce);
m_state.sync(bc());
m_startState = m_state;
h256Set changed { dev::eth::ChainChangedFilter };
noteChanged(changed);
h256Set changed { dev::eth::PendingChangedFilter, dev::eth::ChainChangedFilter };
}
ExecutionResult MixClient::lastExecution() const
@ -372,23 +365,6 @@ dev::eth::ExecutionResult MixClient::create(Address const& _from, u256 _value, b
return lastExecution().result;
}
void MixClient::noteChanged(h256Set const& _filters)
{
for (auto& i: m_watches)
if (_filters.count(i.second.id))
{
if (m_filters.count(i.second.id))
i.second.changes += m_filters.at(i.second.id).changes;
else if (m_specialFilters.count(i.second.id))
for (h256 const& hash: m_specialFilters.at(i.second.id))
i.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, hash));
}
for (auto& i: m_filters)
i.second.changes.clear();
for (auto& i: m_specialFilters)
i.second.clear();
}
eth::BlockInfo MixClient::blockInfo() const
{
ReadGuard l(x_state);

2
mix/MixClient.h

@ -25,6 +25,7 @@
#include <vector>
#include <string>
#include <libethereum/ExtVM.h>
#include <libethereum/ClientBase.h>
#include <libethereum/Client.h>
#include "MachineStates.h"
@ -88,7 +89,6 @@ protected:
private:
void executeTransaction(dev::eth::Transaction const& _t, eth::State& _state, bool _call, bool _gasAuto, dev::Secret const& _secret = dev::Secret());
void noteChanged(h256Set const& _filters);
dev::eth::Transaction replaceGas(dev::eth::Transaction const& _t, dev::u256 const& _gas, dev::Secret const& _secret = dev::Secret());
eth::State m_state;

17
mix/QContractDefinition.cpp

@ -39,8 +39,23 @@ QContractDefinition::QContractDefinition(QObject* _parent, dev::solidity::Contra
else
m_constructor = new QFunctionDefinition(parent);
std::vector<std::string> found;
for (auto const& f: _contract->getDefinedFunctions())
{
m_functions.append(new QFunctionDefinition(parent, f));
found.push_back(f->getName());
}
for (auto const& it: _contract->getInterfaceFunctions())
m_functions.append(new QFunctionDefinition(parent, it.second));
{
if (std::find(found.begin(), found.end(), it.second->getDeclaration().getName()) == found.end())
m_functions.append(new QFunctionDefinition(parent, it.second));
}
for (auto const& it: _contract->getEvents())
m_events.append(new QFunctionDefinition(parent, it));
}
QFunctionDefinition const* QContractDefinition::getFunction(dev::FixedHash<4> _hash) const

8
mix/QContractDefinition.h

@ -37,6 +37,7 @@ class QContractDefinition: public QBasicNodeDefinition
Q_OBJECT
Q_PROPERTY(QQmlListProperty<dev::mix::QFunctionDefinition> functions READ functions CONSTANT)
Q_PROPERTY(dev::mix::QFunctionDefinition* constructor READ constructor CONSTANT)
Q_PROPERTY(QQmlListProperty<dev::mix::QFunctionDefinition> events READ events CONSTANT)
public:
QContractDefinition(QObject* _parent, solidity::ContractDefinition const* _contract);
@ -44,12 +45,19 @@ public:
QQmlListProperty<QFunctionDefinition> functions() const { return QQmlListProperty<QFunctionDefinition>(const_cast<QContractDefinition*>(this), const_cast<QContractDefinition*>(this)->m_functions); }
/// Get the constructor of the contract.
QFunctionDefinition* constructor() const { return m_constructor; }
/// Get all the functions of the contract.
QList<QFunctionDefinition*> const& functionsList() const { return m_functions; }
/// Find function by hash, returns nullptr if not found
QFunctionDefinition const* getFunction(dev::FixedHash<4> _hash) const;
/// Get events
QQmlListProperty<QFunctionDefinition> events() const { return QQmlListProperty<QFunctionDefinition>(const_cast<QContractDefinition*>(this), const_cast<QContractDefinition*>(this)->m_events); }
/// Get events
QList<QFunctionDefinition*> const& eventsList() const { return m_events; }
private:
QList<QFunctionDefinition*> m_functions;
QFunctionDefinition* m_constructor;
QList<QFunctionDefinition*> m_events;
};
}

31
mix/QFunctionDefinition.cpp

@ -28,15 +28,40 @@
using namespace dev::solidity;
using namespace dev::mix;
QFunctionDefinition::QFunctionDefinition(QObject* _parent, dev::solidity::FunctionTypePointer const& _f): QBasicNodeDefinition(_parent, &_f->getDeclaration()), m_hash(dev::sha3(_f->externalSignature()))
QFunctionDefinition::QFunctionDefinition(QObject* _parent, dev::solidity::FunctionTypePointer const& _f): QBasicNodeDefinition(_parent, &_f->getDeclaration()), m_hash(dev::sha3(_f->externalSignature())),
m_fullHash(dev::sha3(_f->externalSignature()))
{
init(_f);
}
QFunctionDefinition::QFunctionDefinition(QObject* _parent, ASTPointer<FunctionDefinition> const& _f): QBasicNodeDefinition(_parent, _f.get()), m_hash(dev::sha3(_f->externalSignature())),
m_fullHash(dev::sha3(_f->externalSignature()))
{
for (unsigned i = 0; i < _f->getParameters().size(); ++i)
m_parameters.append(new QVariableDeclaration(parent(), _f->getParameters().at(i)));
for (unsigned i = 0; i < _f->getReturnParameters().size(); ++i)
m_returnParameters.append(new QVariableDeclaration(parent(), _f->getReturnParameters().at(i)));
}
QFunctionDefinition::QFunctionDefinition(QObject* _parent, ASTPointer<dev::solidity::EventDefinition> const& _e): QBasicNodeDefinition(_parent, _e.get())
{
for (unsigned i = 0; i < _e->getParameters().size(); ++i)
m_parameters.append(new QVariableDeclaration(parent(), _e->getParameters().at(i)));
FunctionTypePointer _f = std::make_shared<FunctionType>(*_e);
m_hash = (FixedHash<4>)dev::sha3(_f->externalSignature(_e->getName()));
m_fullHash = dev::sha3(_f->externalSignature(_e->getName()));
}
void QFunctionDefinition::init(dev::solidity::FunctionTypePointer _f)
{
auto paramNames = _f->getParameterNames();
auto paramTypes = _f->getParameterTypes();
auto returnNames = _f->getReturnParameterNames();
auto returnTypes = _f->getReturnParameterTypes();
for (unsigned i = 0; i < paramNames.size(); ++i)
m_parameters.append(new QVariableDeclaration(_parent, paramNames[i], paramTypes[i].get()));
m_parameters.append(new QVariableDeclaration(parent(), paramNames[i], paramTypes[i].get()));
for (unsigned i = 0; i < returnNames.size(); ++i)
m_returnParameters.append(new QVariableDeclaration(_parent, returnNames[i], returnTypes[i].get()));
m_returnParameters.append(new QVariableDeclaration(parent(), returnNames[i], returnTypes[i].get()));
}

7
mix/QFunctionDefinition.h

@ -41,6 +41,10 @@ public:
QFunctionDefinition(){}
QFunctionDefinition(QObject* _parent): QBasicNodeDefinition(_parent) {}
QFunctionDefinition(QObject* _parent, solidity::FunctionTypePointer const& _f);
QFunctionDefinition(QObject* _parent, solidity::ASTPointer<solidity::EventDefinition> const& _f);
QFunctionDefinition(QObject* _parent, solidity::ASTPointer<solidity::FunctionDefinition> const& _f);
/// Init members
void init(dev::solidity::FunctionTypePointer _f);
/// Get all input parameters of this function.
QList<QVariableDeclaration*> const& parametersList() const { return m_parameters; }
/// Get all input parameters of this function as QML property.
@ -49,10 +53,13 @@ public:
QList<QVariableDeclaration*> returnParameters() const { return m_returnParameters; }
/// Get the hash of this function declaration on the contract ABI.
FixedHash<4> hash() const { return m_hash; }
/// Get the full hash of this function declaration on the contract ABI.
FixedHash<32> fullHash() const { return m_fullHash; }
private:
int m_index;
FixedHash<4> m_hash;
FixedHash<32> m_fullHash;
QList<QVariableDeclaration*> m_parameters;
QList<QVariableDeclaration*> m_returnParameters;
void initQParameters();

16
mix/QVariableDeclaration.cpp

@ -24,26 +24,28 @@
#include <libsolidity/AST.h>
#include "CodeModel.h"
using namespace solidity;
namespace dev
{
namespace mix
{
QVariableDeclaration::QVariableDeclaration(QObject* _parent, solidity::VariableDeclaration const* _v):
QBasicNodeDefinition(_parent, _v),
m_type(new QSolidityType(this, CodeModel::nodeType(_v->getType().get())))
QVariableDeclaration::QVariableDeclaration(QObject* _parent, ASTPointer<VariableDeclaration> const _v):
QBasicNodeDefinition(_parent, _v.get()),
m_type(new QSolidityType(this, CodeModel::nodeType(_v->getType().get()))), m_isIndexed(_v->isIndexed())
{
}
QVariableDeclaration::QVariableDeclaration(QObject* _parent, std::string const& _name, SolidityType const& _type):
QVariableDeclaration::QVariableDeclaration(QObject* _parent, std::string const& _name, SolidityType const& _type, bool _isIndexed):
QBasicNodeDefinition(_parent, _name),
m_type(new QSolidityType(_parent, _type))
m_type(new QSolidityType(_parent, _type)), m_isIndexed(_isIndexed)
{
}
QVariableDeclaration::QVariableDeclaration(QObject* _parent, std::string const& _name, solidity::Type const* _type):
QVariableDeclaration::QVariableDeclaration(QObject* _parent, std::string const& _name, solidity::Type const* _type, bool _isIndexed):
QBasicNodeDefinition(_parent, _name),
m_type(new QSolidityType(this, CodeModel::nodeType(_type)))
m_type(new QSolidityType(this, CodeModel::nodeType(_type))), m_isIndexed(_isIndexed)
{
}

9
mix/QVariableDeclaration.h

@ -21,6 +21,7 @@
#include <QDebug>
#include <QVariantList>
#include <libsolidity/AST.h>
#include "QBasicNodeDefinition.h"
#include "SolidityType.h"
@ -82,14 +83,16 @@ class QVariableDeclaration: public QBasicNodeDefinition
public:
QVariableDeclaration() {}
QVariableDeclaration(QObject* _parent, solidity::VariableDeclaration const* _v);
QVariableDeclaration(QObject* _parent, std::string const& _name, SolidityType const& _type);
QVariableDeclaration(QObject* _parent, std::string const& _name, solidity::Type const* _type);
QVariableDeclaration(QObject* _parent, solidity::ASTPointer<solidity::VariableDeclaration> const _v);
QVariableDeclaration(QObject* _parent, std::string const& _name, SolidityType const& _type, bool _isIndexed = false);
QVariableDeclaration(QObject* _parent, std::string const& _name, solidity::Type const* _type, bool _isIndexed = false);
QSolidityType* type() const { return m_type; }
void setType(QSolidityType* _type) { m_type = _type; }
bool isIndexed() { return m_isIndexed; }
private:
QSolidityType* m_type;
bool m_isIndexed;
};

5
mix/qml.qrc

@ -64,5 +64,10 @@
<file>qml/js/ansi2html.js</file>
<file>qml/js/NetworkDeployment.js</file>
<file>qml/js/InputValidator.js</file>
<file>qml/Block.qml</file>
<file>qml/BlockChain.qml</file>
<file>qml/ScenarioExecution.qml</file>
<file>qml/ScenarioLoader.qml</file>
<file>qml/ScenarioButton.qml</file>
</qresource>
</RCC>

15
mix/qml/Application.qml

@ -128,10 +128,8 @@ ApplicationWindow {
MenuSeparator {}
MenuItem { action: toggleProjectNavigatorAction }
MenuItem { action: showHideRightPanelAction }
MenuItem { action: toggleTransactionLogAction }
MenuItem { action: toggleWebPreviewAction }
MenuItem { action: toggleWebPreviewOrientationAction }
//MenuItem { action: toggleCallsInLog }
}
}
@ -211,8 +209,8 @@ ApplicationWindow {
id: toggleAssemblyDebuggingAction
text: qsTr("Show VM Code")
shortcut: "Ctrl+Alt+V"
onTriggered: mainContent.rightPane.assemblyMode = !mainContent.rightPane.assemblyMode;
checked: mainContent.rightPane.assemblyMode;
onTriggered: mainContent.debuggerPanel.assemblyMode = !mainContent.debuggerPanel.assemblyMode;
checked: mainContent.debuggerPanel.assemblyMode;
enabled: true
}
@ -225,15 +223,6 @@ ApplicationWindow {
onTriggered: mainContent.toggleWebPreview();
}
Action {
id: toggleTransactionLogAction
text: qsTr("Show States and Transactions")
shortcut: "Alt+1"
checkable: true
checked: mainContent.rightPane.transactionLog.visible
onTriggered: mainContent.rightPane.transactionLog.visible = !mainContent.rightPane.transactionLog.visible
}
Action {
id: toggleProjectNavigatorAction
text: qsTr("Show Project Navigator")

346
mix/qml/Block.qml

@ -0,0 +1,346 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1
import Qt.labs.settings 1.0
import "js/Debugger.js" as Debugger
import "js/ErrorLocationFormater.js" as ErrorLocationFormater
import "."
ColumnLayout
{
id: root
property variant transactions
property string status
property int number
property int blockWidth: Layout.preferredWidth - statusWidth - horizontalMargin
property int horizontalMargin: 10
property int trHeight: 30
spacing: 0
property int openedTr: 0
property int blockIndex
property variant scenario
function calculateHeight()
{
if (transactions)
{
if (index >= 0)
return 30 + 30 * transactions.count + openedTr
else
return 30
}
else
return 30
}
onOpenedTrChanged:
{
Layout.preferredHeight = calculateHeight()
height = calculateHeight()
}
RowLayout
{
Layout.preferredHeight: trHeight
Layout.preferredWidth: blockWidth
id: rowHeader
Rectangle
{
color: "#DEDCDC"
Layout.preferredWidth: blockWidth
Layout.preferredHeight: trHeight
radius: 4
anchors.left: parent.left
anchors.leftMargin: statusWidth + 5
Label {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: horizontalMargin
text:
{
if (status === "mined")
return qsTr("BLOCK") + " " + number
else
return qsTr("BLOCK") + " pending"
}
}
}
}
Repeater // List of transactions
{
id: transactionRepeater
model: transactions
RowLayout
{
id: rowTransaction
Layout.preferredHeight: trHeight
function displayContent()
{
logsText.text = ""
if (index >= 0 && transactions.get(index).logs && transactions.get(index).logs.count)
{
for (var k = 0; k < transactions.get(index).logs.count; k++)
{
var log = transactions.get(index).logs.get(k)
if (log.name)
logsText.text += log.name + ":\n"
else
logsText.text += "log:\n"
if (log.param)
for (var i = 0; i < log.param.count; i++)
{
var p = log.param.get(i)
logsText.text += p.name + " = " + p.value + " - indexed:" + p.indexed + "\n"
}
else{
logsText.text += "From : " + log.address + "\n"
}
}
logsText.text += "\n\n"
}
rowDetailedContent.visible = !rowDetailedContent.visible
}
Rectangle
{
id: trSaveStatus
Layout.preferredWidth: statusWidth
Layout.preferredHeight: trHeight
color: "transparent"
anchors.top: parent.top
property bool saveStatus
Image {
id: saveStatusImage
source: "qrc:/qml/img/recyclediscard@2x.png"
width: statusWidth
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Component.onCompleted:
{
if (index >= 0)
saveStatus = transactions.get(index).saveStatus
}
onSaveStatusChanged:
{
if (saveStatus)
saveStatusImage.source = "qrc:/qml/img/recyclekeep@2x.png"
else
saveStatusImage.source = "qrc:/qml/img/recyclediscard@2x.png"
if (index >= 0)
transactions.get(index).saveStatus = saveStatus
}
MouseArea {
id: statusMouseArea
anchors.fill: parent
onClicked:
{
parent.saveStatus = !parent.saveStatus
}
}
}
Rectangle
{
Layout.preferredWidth: blockWidth
Layout.preferredHeight: parent.height
color: "#DEDCDC"
id: rowContentTr
anchors.top: parent.top
ColumnLayout
{
anchors.top: parent.top
spacing: 10
RowLayout
{
anchors.top: parent.top
anchors.verticalCenter: parent.verticalCenter
spacing: cellSpacing
Text
{
id: hash
anchors.left: parent.left
anchors.leftMargin: horizontalMargin
Layout.preferredWidth: fromWidth
elide: Text.ElideRight
maximumLineCount: 1
text: {
if (index >= 0)
return transactions.get(index).sender
else
return ""
}
}
Text
{
id: func
text: {
if (index >= 0)
parent.userFrienldyToken(transactions.get(index).label)
else
return ""
}
elide: Text.ElideRight
maximumLineCount: 1
Layout.preferredWidth: toWidth
}
function userFrienldyToken(value)
{
if (value && value.indexOf("<") === 0)
{
if (value.split("> ")[1] === " - ")
return value.split(" - ")[0].replace("<", "")
else
return value.split(" - ")[0].replace("<", "") + "." + value.split("> ")[1] + "()";
}
else
return value
}
Text
{
id: returnValue
elide: Text.ElideRight
maximumLineCount: 1
Layout.preferredWidth: valueWidth
text: {
if (index >= 0 && transactions.get(index).returned)
return transactions.get(index).returned
else
return ""
}
}
Rectangle
{
Layout.preferredWidth: logsWidth
Layout.preferredHeight: trHeight - 10
width: logsWidth
color: "transparent"
Text
{
id: logs
anchors.left: parent.left
anchors.leftMargin: 10
text: {
if (index >= 0 && transactions.get(index).logs && transactions.get(index).logs.count)
return transactions.get(index).logs.count
else
return ""
}
}
MouseArea {
anchors.fill: parent
onClicked: {
rowTransaction.displayContent();
}
}
}
Rectangle
{
Layout.preferredWidth: debugActionWidth
Layout.preferredHeight: trHeight - 10
color: "transparent"
Image {
source: "qrc:/qml/img/edit.png"
width: 18
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
MouseArea
{
anchors.fill: parent
onClicked:
{
transactionDialog.stateAccounts = scenario.accounts
transactionDialog.execute = false
transactionDialog.open(index, blockIndex, transactions.get(index))
}
}
}
Rectangle
{
Layout.preferredWidth: debugActionWidth
Layout.preferredHeight: trHeight - 10
color: "transparent"
Image {
id: debugImg
source: "qrc:/qml/img/rightarrow@2x.png"
width: statusWidth
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
visible: transactions.get(index).recordIndex !== undefined
}
MouseArea
{
anchors.fill: parent
onClicked:
{
if (transactions.get(index).recordIndex !== undefined)
{
debugTrRequested = [ blockIndex, index ]
clientModel.debugRecord(transactions.get(index).recordIndex);
}
}
}
}
}
RowLayout
{
id: rowDetailedContent
visible: false
Layout.preferredHeight:{
if (index >= 0 && transactions.get(index).logs)
return 100 * transactions.get(index).logs.count
else
return 100
}
onVisibleChanged:
{
var lognb = transactions.get(index).logs.count
if (visible)
{
rowContentTr.Layout.preferredHeight = trHeight + 100 * lognb
openedTr += 100 * lognb
}
else
{
rowContentTr.Layout.preferredHeight = trHeight
openedTr -= 100 * lognb
}
}
Text {
anchors.left: parent.left
anchors.leftMargin: horizontalMargin
id: logsText
}
}
}
}
}
}
}

480
mix/qml/BlockChain.qml

@ -0,0 +1,480 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1
import Qt.labs.settings 1.0
import org.ethereum.qml.QEther 1.0
import "js/Debugger.js" as Debugger
import "js/ErrorLocationFormater.js" as ErrorLocationFormater
import "js/TransactionHelper.js" as TransactionHelper
import "js/QEtherHelper.js" as QEtherHelper
import "."
ColumnLayout {
id: blockChainPanel
property variant model
spacing: 0
property int previousWidth
property variant debugTrRequested: []
signal chainChanged
onChainChanged: {
reBuildNeeded.start()
}
onWidthChanged:
{
if (width <= 630 || previousWidth <= 630)
{
fromWidth = 100
toWidth = 100
valueWidth = 200
}
else
{
var diff = (width - previousWidth) / 3;
fromWidth = fromWidth + diff < 100 ? 100 : fromWidth + diff
toWidth = toWidth + diff < 100 ? 100 : toWidth + diff
valueWidth = valueWidth + diff < 200 ? 200 : valueWidth + diff
}
previousWidth = width
}
function load(scenario)
{
if (!scenario)
return;
if (model)
chainChanged()
model = scenario
blockModel.clear()
for (var b in model.blocks)
blockModel.append(model.blocks[b])
previousWidth = width
}
property int statusWidth: 30
property int fromWidth: 100
property int toWidth: 100
property int valueWidth: 200
property int logsWidth: 50
property int debugActionWidth: 50
property int horizontalMargin: 10
property int cellSpacing: 10
RowLayout
{
id: header
spacing: 0
Layout.preferredHeight: 25
Image {
id: debugImage
source: "qrc:/qml/img/recycleicon@2x.png"
Layout.preferredWidth: statusWidth
Layout.preferredHeight: 25
fillMode: Image.PreserveAspectFit
}
Rectangle
{
Layout.preferredWidth: fromWidth + cellSpacing
Label
{
anchors.verticalCenter: parent.verticalCenter
text: "From"
anchors.left: parent.left
anchors.leftMargin: horizontalMargin + 5
}
}
Label
{
text: "To"
Layout.preferredWidth: toWidth + cellSpacing
}
Label
{
text: "Value"
Layout.preferredWidth: valueWidth + cellSpacing
}
Label
{
text: "Logs"
Layout.preferredWidth: logsWidth + cellSpacing
}
Label
{
text: ""
Layout.preferredWidth: debugActionWidth
}
}
Rectangle
{
Layout.preferredHeight: 500
Layout.preferredWidth: parent.width
border.color: "#cccccc"
border.width: 2
color: "white"
ScrollView
{
id: blockChainScrollView
anchors.fill: parent
anchors.topMargin: 10
ColumnLayout
{
id: blockChainLayout
width: parent.width
spacing: 10
Repeater // List of blocks
{
id: blockChainRepeater
model: blockModel
Block
{
scenario: blockChainPanel.model
Layout.preferredWidth: blockChainScrollView.width
Layout.preferredHeight:
{
return calculateHeight()
}
blockIndex: index
transactions:
{
if (index >= 0)
return blockModel.get(index).transactions
else
return []
}
status:
{
if (index >= 0)
return blockModel.get(index).status
else
return ""
}
number:
{
if (index >= 0)
return blockModel.get(index).number
else
return 0
}
}
}
}
}
}
ListModel
{
id: blockModel
function appendBlock(block)
{
blockModel.append(block);
}
function appendTransaction(tr)
{
blockModel.get(blockModel.count - 1).transactions.append(tr)
}
function removeTransaction(blockIndex, trIndex)
{
blockModel.get(blockIndex).transactions.remove(trIndex)
}
function removeLastBlock()
{
blockModel.remove(blockModel.count - 1)
}
function removeBlock(index)
{
blockModel.remove(index)
}
function getTransaction(block, tr)
{
return blockModel.get(block).transactions.get(tr)
}
function setTransaction(blockIndex, trIndex, tr)
{
blockModel.get(blockIndex).transactions.set(trIndex, tr)
}
function setTransactionProperty(blockIndex, trIndex, propertyName, value)
{
blockModel.get(blockIndex).transactions.set(trIndex, { propertyName: value })
}
}
Rectangle
{
Layout.preferredWidth: parent.width
RowLayout
{
width: 4 * 100
anchors.top: parent.top
anchors.topMargin: 10
spacing: 0
ScenarioButton {
id: rebuild
text: qsTr("Rebuild")
onClicked:
{
if (ensureNotFuturetime.running)
return;
reBuildNeeded.stop()
var retBlocks = [];
var bAdded = 0;
for (var j = 0; j < model.blocks.length; j++)
{
var b = model.blocks[j];
var block = {
hash: b.hash,
number: b.number,
transactions: [],
status: b.status
}
for (var k = 0; k < model.blocks[j].transactions.length; k++)
{
if (blockModel.get(j).transactions.get(k).saveStatus)
{
var tr = model.blocks[j].transactions[k]
tr.saveStatus = true
block.transactions.push(tr);
}
}
if (block.transactions.length > 0)
{
bAdded++
block.number = bAdded
block.status = "mined"
retBlocks.push(block)
}
}
if (retBlocks.length === 0)
retBlocks.push(projectModel.stateListModel.createEmptyBlock())
else
{
var last = retBlocks[retBlocks.length - 1]
last.number = -1
last.status = "pending"
}
model.blocks = retBlocks
blockModel.clear()
for (var j = 0; j < model.blocks.length; j++)
blockModel.append(model.blocks[j])
ensureNotFuturetime.start()
clientModel.setupScenario(model);
}
Layout.preferredWidth: 100
Layout.preferredHeight: 30
buttonShortcut: ""
sourceImg: "qrc:/qml/img/recycleicon@2x.png"
Timer
{
id: reBuildNeeded
repeat: true
interval: 1000
running: false
onTriggered: {
if (!parent.fillColor || parent.fillColor === "white")
parent.fillColor = "orange"
else
parent.fillColor = "white"
}
onRunningChanged: {
if (!running)
parent.fillColor = "white"
}
}
}
ScenarioButton {
id: addTransaction
text: qsTr("Add Transaction")
onClicked:
{
var lastBlock = model.blocks[model.blocks.length - 1];
if (lastBlock.status === "mined")
{
var newblock = projectModel.stateListModel.createEmptyBlock()
blockModel.appendBlock(newblock)
model.blocks.push(newblock);
}
var item = TransactionHelper.defaultTransaction()
transactionDialog.stateAccounts = model.accounts
transactionDialog.execute = true
transactionDialog.open(model.blocks[model.blocks.length - 1].transactions.length, model.blocks.length - 1, item)
}
Layout.preferredWidth: 100
Layout.preferredHeight: 30
buttonShortcut: ""
sourceImg: "qrc:/qml/img/sendtransactionicon@2x.png"
}
Timer
{
id: ensureNotFuturetime
interval: 1000
repeat: false
running: false
}
ScenarioButton {
id: addBlockBtn
text: qsTr("Add Block")
onClicked:
{
if (ensureNotFuturetime.running)
return
if (clientModel.mining || clientModel.running)
return
if (model.blocks.length > 0)
{
var lastBlock = model.blocks[model.blocks.length - 1]
if (lastBlock.status === "pending")
{
ensureNotFuturetime.start()
clientModel.mine()
}
else
addNewBlock()
}
else
addNewBlock()
}
function addNewBlock()
{
var block = projectModel.stateListModel.createEmptyBlock()
model.blocks.push(block)
blockModel.appendBlock(block)
}
Layout.preferredWidth: 100
Layout.preferredHeight: 30
buttonShortcut: ""
sourceImg: "qrc:/qml/img/addblock@2x.png"
}
Connections
{
target: clientModel
onNewBlock:
{
if (!clientModel.running)
{
var lastBlock = model.blocks[model.blocks.length - 1]
lastBlock.status = "mined"
lastBlock.number = model.blocks.length
var lastB = blockModel.get(model.blocks.length - 1)
lastB.status = "mined"
lastB.number = model.blocks.length
addBlockBtn.addNewBlock()
}
}
onStateCleared:
{
}
onNewRecord:
{
var blockIndex = parseInt(_r.transactionIndex.split(":")[0]) - 1
var trIndex = parseInt(_r.transactionIndex.split(":")[1])
if (blockIndex <= model.blocks.length - 1)
{
var item = model.blocks[blockIndex]
if (trIndex <= item.transactions.length - 1)
{
var tr = item.transactions[trIndex]
tr.returned = _r.returned
tr.recordIndex = _r.recordIndex
tr.logs = _r.logs
tr.sender = _r.sender
var trModel = blockModel.getTransaction(blockIndex, trIndex)
trModel.returned = _r.returned
trModel.recordIndex = _r.recordIndex
trModel.logs = _r.logs
trModel.sender = _r.sender
blockModel.setTransaction(blockIndex, trIndex, trModel)
return;
}
}
// tr is not in the list.
var itemTr = TransactionHelper.defaultTransaction()
itemTr.saveStatus = false
itemTr.functionId = _r.function
itemTr.contractId = _r.contract
itemTr.gasAuto = true
itemTr.parameters = _r.parameters
itemTr.isContractCreation = itemTr.functionId === itemTr.contractId
itemTr.label = _r.label
itemTr.isFunctionCall = itemTr.functionId !== ""
itemTr.returned = _r.returned
itemTr.value = QEtherHelper.createEther(_r.value, QEther.Wei)
itemTr.sender = _r.sender
itemTr.recordIndex = _r.recordIndex
itemTr.logs = _r.logs
model.blocks[model.blocks.length - 1].transactions.push(itemTr)
blockModel.appendTransaction(itemTr)
}
onMiningComplete:
{
}
}
ScenarioButton {
id: newAccount
text: qsTr("New Account")
onClicked: {
model.accounts.push(projectModel.stateListModel.newAccount("1000000", QEther.Ether))
}
Layout.preferredWidth: 100
Layout.preferredHeight: 30
buttonShortcut: ""
sourceImg: "qrc:/qml/img/newaccounticon@2x.png"
}
}
}
TransactionDialog {
id: transactionDialog
property bool execute
onAccepted: {
var item = transactionDialog.getItem()
if (execute)
{
var lastBlock = model.blocks[model.blocks.length - 1];
if (lastBlock.status === "mined")
{
var newBlock = projectModel.stateListModel.createEmptyBlock();
model.blocks.push(newBlock);
blockModel.appendBlock(newBlock)
}
if (!clientModel.running)
clientModel.executeTr(item)
}
else {
model.blocks[blockIndex].transactions[transactionIndex] = item
blockModel.setTransaction(blockIndex, transactionIndex, item)
chainChanged()
}
}
}
}

158
mix/qml/Debugger.qml

@ -11,7 +11,6 @@ import "."
Rectangle {
id: debugPanel
property alias transactionLog: transactionLog
property alias debugSlider: statesSlider
property alias solLocals: solLocals
property alias solStorage: solStorage
@ -23,7 +22,7 @@ Rectangle {
signal debugExecuteLocation(string documentId, var location)
property string compilationErrorMessage
property bool assemblyMode: false
signal panelClosed
objectName: "debugPanel"
color: "#ededed"
clip: true
@ -40,6 +39,11 @@ Rectangle {
machineStates.updateHeight();
}
function setTr(tr)
{
trName.text = tr.label
}
function displayCompilationErrorIfAny()
{
debugScrollArea.visible = false;
@ -61,7 +65,6 @@ Rectangle {
{
Debugger.init(data);
debugScrollArea.visible = true;
compilationErrorArea.visible = false;
machineStates.visible = true;
}
if (giveFocus)
@ -97,96 +100,77 @@ Rectangle {
Settings {
id: splitSettings
property alias transactionLogHeight: transactionLog.height
property alias callStackHeight: callStackRect.height
property alias storageHeightSettings: storageRect.height
property alias memoryDumpHeightSettings: memoryRect.height
property alias callDataHeightSettings: callDataRect.height
property alias transactionLogVisible: transactionLog.visible
property alias solCallStackHeightSettings: solStackRect.height
property alias solStorageHeightSettings: solStorageRect.height
property alias solLocalsHeightSettings: solLocalsRect.height
}
Rectangle
{
visible: false;
id: compilationErrorArea
width: parent.width - 20
height: 600
color: "#ededed"
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: 10
ColumnLayout
ColumnLayout {
id: debugScrollArea
anchors.fill: parent
//orientation: Qt.Vertical
spacing: 0
RowLayout
{
width: parent.width
anchors.top: parent.top
spacing: 15
Layout.preferredWidth: parent.width
Layout.preferredHeight: 30
Rectangle
{
height: 15
Button {
text: qsTr("Back to Debugger")
onClicked: {
debugScrollArea.visible = true;
compilationErrorArea.visible = false;
machineStates.visible = true;
}
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
color: "transparent"
Text {
anchors.centerIn: parent
text: qsTr("Current Transaction")
}
}
RowLayout
{
height: 100
ColumnLayout
Rectangle
{
Text {
color: "red"
id: errorLocation
anchors.left: parent.left
anchors.leftMargin: 10
width: 30
height: parent.height
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
Image {
source: "qrc:/qml/img/leftarrow@2x.png"
width: parent.width
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
}
Text {
color: "#4a4a4a"
id: errorDetail
MouseArea
{
anchors.fill: parent
onClicked:
{
Debugger.init(null);
panelClosed()
}
}
}
}
}
RowLayout
{
Layout.preferredWidth: parent.width
Layout.preferredHeight: 30
Rectangle
{
width: parent.width - 6
height: 2
color: "#d0d0d0"
}
RowLayout
{
Text
{
color: "#4a4a4a"
id: errorLine
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
color: "#2C79D3"
Text {
id: trName
color: "white"
anchors.centerIn: parent
}
}
}
}
Splitter {
id: debugScrollArea
anchors.fill: parent
orientation: Qt.Vertical
TransactionLog {
id: transactionLog
Layout.fillWidth: true
Layout.minimumHeight: 130
height: 250
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: machineStates.sideMargin
anchors.rightMargin: machineStates.sideMargin
anchors.topMargin: machineStates.sideMargin
}
ScrollView
{
@ -230,32 +214,6 @@ Rectangle {
spacing: 3
layoutDirection: Qt.LeftToRight
StepActionImage
{
id: playAction
enabledStateImg: "qrc:/qml/img/play_button.png"
disableStateImg: "qrc:/qml/img/play_button.png"
buttonLeft: true
onClicked: projectModel.stateListModel.runState(transactionLog.selectedStateIndex)
width: 23
buttonShortcut: "Ctrl+Shift+F8"
buttonTooltip: qsTr("Start Debugging")
visible: true
Layout.alignment: Qt.AlignLeft
}
StepActionImage
{
id: pauseAction
enabledStateImg: "qrc:/qml/img/stop_button2x.png"
disableStateImg: "qrc:/qml/img/stop_button2x.png"
onClicked: Debugger.init(null);
width: 23
buttonShortcut: "Ctrl+Shift+F9"
buttonTooltip: qsTr("Stop Debugging")
visible: true
}
StepActionImage
{
id: runBackAction;
@ -562,12 +520,12 @@ Rectangle {
}
Rectangle {
id: separator
width: parent.width;
height: 1;
color: "#cccccc"
anchors.bottom: parent.bottom
}
id: separator
width: parent.width;
height: 1;
color: "#cccccc"
anchors.bottom: parent.bottom
}
}
}
}

60
mix/qml/MainContent.qml

@ -20,13 +20,14 @@ Rectangle {
anchors.fill: parent
id: root
property alias rightViewVisible: rightView.visible
property alias rightViewVisible: scenarioExe.visible
property alias webViewVisible: webPreview.visible
property alias webView: webPreview
property alias projectViewVisible: projectList.visible
property alias projectNavigator: projectList
property alias runOnProjectLoad: mainSettings.runOnProjectLoad
property alias rightPane: rightView
property alias rightPane: scenarioExe
property alias debuggerPanel: debugPanel
property alias codeEditor: codeEditor
property bool webViewHorizontal: codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally
property bool firstCompile: true
@ -43,7 +44,7 @@ Rectangle {
}
Connections {
target: rightView
target: debugPanel
onDebugExecuteLocation: {
codeEditor.highlightExecution(documentId, location);
}
@ -52,7 +53,7 @@ Rectangle {
Connections {
target: codeEditor
onBreakpointsChanged: {
rightPane.setBreakpoints(codeEditor.getBreakpoints());
debugPanel.setBreakpoints(codeEditor.getBreakpoints());
}
}
@ -63,20 +64,20 @@ Rectangle {
}
function toggleRightView() {
rightView.visible = !rightView.visible;
scenarioExe.visible = !scenarioExe.visible;
}
function ensureRightView() {
rightView.visible = true;
scenarioExe.visible = true;
}
function rightViewIsVisible()
{
return rightView.visible;
return scenarioExe.visible;
}
function hideRightView() {
rightView.visible = false;
scenarioExe.visible = lfalse;
}
function toggleWebPreview() {
@ -98,8 +99,8 @@ Rectangle {
function displayCompilationErrorIfAny()
{
rightView.visible = true;
rightView.displayCompilationErrorIfAny();
scenarioExe.visible = true;
scenarioExe.displayCompilationErrorIfAny();
}
Settings {
@ -153,7 +154,7 @@ Rectangle {
id: splitSettings
property alias projectWidth: projectList.width
property alias contentViewWidth: contentView.width
property alias rightViewWidth: rightView.width
property alias rightViewWidth: scenarioExe.width
}
Splitter
@ -198,14 +199,45 @@ Rectangle {
}
}
Debugger {
ScenarioExecution
{
id: scenarioExe;
visible: false;
id: rightView;
Layout.fillHeight: true
Keys.onEscapePressed: visible = false
Layout.minimumWidth: 515
Layout.minimumWidth: 650
anchors.right: parent.right
}
Debugger
{
id: debugPanel
visible: false
Layout.fillHeight: true
Keys.onEscapePressed: visible = false
Layout.minimumWidth: 650
anchors.right: parent.right
}
Connections {
target: clientModel
onDebugDataReady: {
scenarioExe.visible = false
debugPanel.visible = true
if (scenarioExe.bc.debugTrRequested)
{
debugPanel.setTr(scenarioExe.bc.model.blocks[scenarioExe.bc.debugTrRequested[0]].transactions[scenarioExe.bc.debugTrRequested[1]])
}
}
}
Connections {
target: debugPanel
onPanelClosed: {
debugPanel.visible = false
scenarioExe.visible = true
}
}
}
}
}

31
mix/qml/QAddressView.qml

@ -39,21 +39,26 @@ Item
{
accountRef.clear();
accountRef.append({"itemid": " - "});
if (subType === "contract" || subType === "address")
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 (blockChainPanel)
for (var k = 0; k < blockChainPanel.model.blocks.length; k++)
{
if (k > blockIndex)
break;
for (var i = 0; i < blockChainPanel.model.blocks[k].transactions.length; i++)
{
if (i > transactionIndex)
break;
var tr = blockChainPanel.model.blocks[k].transactions[i]
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")
{

67
mix/qml/ScenarioButton.qml

@ -0,0 +1,67 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.0
import QtQuick.Controls.Styles 1.1
Rectangle {
id: buttonActionContainer
property string text
property string buttonShortcut
property string sourceImg
property string fillColor
signal clicked
Rectangle {
id: contentRectangle
anchors.fill: parent
border.color: "#cccccc"
border.width: 1
radius: 4
color: parent.fillColor ? parent.fillColor : "white"
Image {
id: debugImage
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: parent.bottom
bottomMargin: debugImg.pressed ? 0 : 2;
topMargin: debugImg.pressed ? 2 : 0;
}
source: sourceImg
fillMode: Image.PreserveAspectFit
height: 30
}
Button {
anchors.fill: parent
id: debugImg
action: buttonAction
style: ButtonStyle {
background: Rectangle {
color: "transparent"
}
}
}
Action {
id: buttonAction
shortcut: buttonShortcut
onTriggered: {
buttonActionContainer.clicked();
}
}
}
Rectangle
{
anchors.top: contentRectangle.bottom
anchors.topMargin: 15
width: parent.width
Text
{
text: buttonActionContainer.text
anchors.centerIn: parent
}
}
}

57
mix/qml/ScenarioExecution.qml

@ -0,0 +1,57 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1
import Qt.labs.settings 1.0
import "js/Debugger.js" as Debugger
import "js/ErrorLocationFormater.js" as ErrorLocationFormater
import "."
Rectangle {
color: "#ededed"
property alias bc: blockChain
Connections
{
target: projectModel
onProjectLoaded: {
loader.init()
}
}
Column
{
anchors.margins: 10
anchors.fill: parent
spacing: 10
ScenarioLoader
{
height: 70
width: parent.width
id: loader
}
Rectangle
{
width: parent.width
height: 1
color: "#cccccc"
}
Connections
{
target: loader
onLoaded: {
blockChain.load(scenario)
}
}
BlockChain
{
id: blockChain
width: parent.width
}
}
}

175
mix/qml/ScenarioLoader.qml

@ -0,0 +1,175 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.0
import QtQuick.Dialogs 1.1
import Qt.labs.settings 1.0
import "js/Debugger.js" as Debugger
import "js/ErrorLocationFormater.js" as ErrorLocationFormater
import "."
RowLayout
{
signal restored(variant scenario)
signal saved(variant scenario)
signal duplicated(variant scenario)
signal loaded(variant scenario)
spacing: 0
function init()
{
scenarioList.load()
}
id: blockChainSelector
Dialog {
id: newStateWin
modality: Qt.ApplicationModal
title: qsTr("New Project");
width: 320
height: 120
visible: false
contentItem: Rectangle {
anchors.fill: parent
anchors.margins: 10
RowLayout {
anchors.verticalCenter: parent.verticalCenter
Text {
text: qsTr("Name:")
}
Rectangle
{
Layout.preferredWidth: 250
Layout.preferredHeight: parent.height
border.width: 1
border.color: "#cccccc"
TextInput
{
anchors.fill: parent
id: stateName
}
}
}
RowLayout
{
anchors.bottom: parent.bottom
anchors.right: parent.right;
function acceptAndClose()
{
var item = projectModel.stateListModel.createDefaultState();
item.title = stateName.text
projectModel.stateListModel.appendState(item)
projectModel.stateListModel.save()
close()
scenarioList.currentIndex = projectModel.stateListModel.count - 1
}
function close()
{
newStateWin.close()
stateName.text = ""
}
Button {
id: okButton;
enabled: stateName.text !== ""
text: qsTr("OK");
onClicked: {
parent.acceptAndClose();
}
}
Button {
text: qsTr("Cancel");
onClicked: parent.close();
}
}
}
}
ComboBox
{
id: scenarioList
model: projectModel.stateListModel
textRole: "title"
onCurrentIndexChanged:
{
restoreScenario.restore()
}
function load()
{
var state = projectModel.stateListModel.getState(currentIndex)
loaded(state)
}
}
Row
{
Layout.preferredWidth: 100 * 4
Layout.preferredHeight: 30
spacing: 0
ScenarioButton {
id: restoreScenario
width: 100
height: 30
buttonShortcut: ""
sourceImg: "qrc:/qml/img/restoreicon@2x.png"
onClicked: {
restore()
}
text: qsTr("Restore")
function restore()
{
var state = projectModel.stateListModel.reloadStateFromFromProject(scenarioList.currentIndex)
restored(state)
loaded(state)
}
}
ScenarioButton {
id: saveScenario
text: qsTr("Save")
onClicked: {
projectModel.saveProjectFile()
saved(state)
}
width: 100
height: 30
buttonShortcut: ""
sourceImg: "qrc:/qml/img/saveicon@2x.png"
}
ScenarioButton
{
id: duplicateScenario
text: qsTr("Duplicate")
onClicked: {
projectModel.stateListModel.duplicateState(scenarioList.currentIndex)
duplicated(state)
}
width: 100
height: 30
buttonShortcut: ""
sourceImg: "qrc:/qml/img/duplicateicon@2x.png"
}
ScenarioButton {
id: addScenario
width: 100
height: 30
buttonShortcut: ""
sourceImg: "qrc:/qml/img/plus.png"
onClicked: {
newStateWin.open()
}
text: qsTr("New")
}
}
}

117
mix/qml/StateListModel.qml

@ -20,13 +20,29 @@ Item {
s.accounts = [stateListModel.newAccount("1000000", QEther.Ether, defaultAccount)]; //support for old project
if (!s.contracts)
s.contracts = [];
return {
title: s.title,
transactions: s.transactions.filter(function(t) { return !t.stdContract; }).map(fromPlainTransactionItem), //support old projects by filtering std contracts
accounts: s.accounts.map(fromPlainAccountItem),
contracts: s.contracts.map(fromPlainAccountItem),
miner: s.miner
};
var ret = {};
ret.title = s.title;
ret.transactions = s.transactions.filter(function(t) { return !t.stdContract; }).map(fromPlainTransactionItem); //support old projects by filtering std contracts
if (s.blocks)
ret.blocks = s.blocks.map(fromPlainBlockItem);
ret.accounts = s.accounts.map(fromPlainAccountItem);
ret.contracts = s.contracts.map(fromPlainAccountItem);
ret.miner = s.miner;
// support old projects
if (!ret.blocks)
{
ret.blocks = [{
hash: "",
number: -1,
transactions: [],
status: "pending"
}]
for (var j in ret.transactions)
ret.blocks[0].transactions.push(fromPlainTransactionItem(toPlainTransactionItem(ret.transactions[j])))
}
return ret;
}
function fromPlainAccountItem(t)
@ -58,9 +74,13 @@ Item {
sender: t.sender,
isContractCreation: t.isContractCreation,
label: t.label,
isFunctionCall: t.isFunctionCall
isFunctionCall: t.isFunctionCall,
saveStatus: t.saveStatus
};
if (r.saveStatus === undefined)
r.saveStatus = true
if (r.isFunctionCall === undefined)
r.isFunctionCall = true;
@ -76,9 +96,21 @@ Item {
return r;
}
function fromPlainBlockItem(b)
{
var r = {
hash: b.hash,
number: b.number,
transactions: b.transactions.filter(function(t) { return !t.stdContract; }).map(fromPlainTransactionItem), //support old projects by filtering std contracts
status: b.status
}
return r;
}
function toPlainStateItem(s) {
return {
title: s.title,
blocks: s.blocks.map(toPlainBlockItem),
transactions: s.transactions.map(toPlainTransactionItem),
accounts: s.accounts.map(toPlainAccountItem),
contracts: s.contracts.map(toPlainAccountItem),
@ -96,6 +128,17 @@ Item {
return '';
}
function toPlainBlockItem(b)
{
var r = {
hash: b.hash,
number: b.number,
transactions: b.transactions.map(toPlainTransactionItem),
status: b.status
}
return r;
}
function toPlainAccountItem(t)
{
return {
@ -125,7 +168,8 @@ Item {
parameters: {},
isContractCreation: t.isContractCreation,
label: t.label,
isFunctionCall: t.isFunctionCall
isFunctionCall: t.isFunctionCall,
saveStatus: t.saveStatus
};
for (var key in t.parameters)
r.parameters[key] = t.parameters[key];
@ -146,6 +190,8 @@ Item {
projectData.states.push(toPlainStateItem(stateList[i]));
}
projectData.defaultStateIndex = stateListModel.defaultStateIndex;
stateListModel.data = projectData
}
onNewProject: {
var state = toPlainStateItem(stateListModel.createDefaultState());
@ -170,6 +216,11 @@ Item {
id: stateDialog
onAccepted: {
var item = stateDialog.getItem();
saveState(item);
}
function saveState(item)
{
if (stateDialog.stateIndex < stateListModel.count) {
if (stateDialog.isDefault)
stateListModel.defaultStateIndex = stateIndex;
@ -190,6 +241,7 @@ Item {
ListModel {
id: stateListModel
property int defaultStateIndex: 0
property variant data
signal defaultStateChanged;
signal stateListModelReady;
signal stateRun(int index)
@ -208,12 +260,32 @@ Item {
return { name: name, secret: _secret, balance: QEtherHelper.createEther(_balance, _unit), address: address };
}
function duplicateState(index)
{
var state = stateList[index]
var item = fromPlainStateItem(toPlainStateItem(state))
item.title = qsTr("Copy of") + " " + state.title
appendState(item)
save()
}
function createEmptyBlock()
{
return {
hash: "",
number: -1,
transactions: [],
status: "pending"
}
}
function createDefaultState() {
var item = {
title: "",
transactions: [],
accounts: [],
contracts: []
contracts: [],
blocks: [{ status: "pending", number: -1, hash: "", transactions: []}]
};
var account = newAccount("1000000", QEther.Ether, defaultAccount)
@ -228,6 +300,7 @@ Item {
ctorTr.label = qsTr("Deploy") + " " + ctorTr.contractId;
ctorTr.sender = item.accounts[0].secret;
item.transactions.push(ctorTr);
item.blocks[0].transactions.push(ctorTr)
}
return item;
}
@ -284,10 +357,20 @@ Item {
stateDialog.open(stateListModel.count, item, false);
}
function appendState(item)
{
stateListModel.append(item);
stateList.push(item);
}
function editState(index) {
stateDialog.open(index, stateList[index], defaultStateIndex === index);
}
function getState(index) {
return stateList[index];
}
function debugDefaultState() {
if (defaultStateIndex >= 0 && defaultStateIndex < stateList.length)
runState(defaultStateIndex);
@ -295,7 +378,7 @@ Item {
function runState(index) {
var item = stateList[index];
clientModel.setupState(item);
clientModel.setupScenario(item);
stateRun(index);
}
@ -322,8 +405,20 @@ Item {
return stateList[defaultStateIndex].title;
}
function reloadStateFromFromProject(index)
{
if (data)
{
var item = fromPlainStateItem(data.states[index])
stateListModel.set(index, item)
stateList[index] = item
return item
}
}
function loadStatesFromProject(projectData)
{
data = projectData
if (!projectData.states)
projectData.states = [];
if (projectData.defaultStateIndex !== undefined)

1
mix/qml/StructView.qml

@ -9,6 +9,7 @@ Column
property alias members: repeater.model //js array
property variant accounts
property var value: ({})
property int blockIndex
property int transactionIndex
property string context
Layout.fillWidth: true

14
mix/qml/TransactionDialog.qml

@ -17,6 +17,7 @@ Dialog {
visible: false
title: qsTr("Edit Transaction")
property int transactionIndex
property int blockIndex
property alias gas: gasValueEdit.gasValue;
property alias gasAuto: gasAutoCheck.checked;
property alias gasPrice: gasPriceField.value;
@ -27,21 +28,24 @@ Dialog {
property var paramsModel: [];
property bool useTransactionDefaultValue: false
property alias stateAccounts: senderComboBox.model
property bool saveStatus
signal accepted;
StateDialogStyle {
id: transactionDialogStyle
}
function open(index, item) {
function open(index, blockIdx, item) {
rowFunction.visible = !useTransactionDefaultValue;
rowValue.visible = !useTransactionDefaultValue;
rowGas.visible = !useTransactionDefaultValue;
rowGasPrice.visible = !useTransactionDefaultValue;
transactionIndex = index;
typeLoader.transactionIndex = index;
transactionIndex = index
blockIndex = blockIdx
typeLoader.transactionIndex = index
typeLoader.blockIndex = blockIdx
saveStatus = item.saveStatus
gasValueEdit.gasValue = item.gas;
gasAutoCheck.checked = item.gasAuto ? true : false;
gasPriceField.value = item.gasPrice;
@ -229,7 +233,7 @@ Dialog {
item.functionId = item.contractId;
item.label = qsTr("Deploy") + " " + item.contractId;
}
item.saveStatus = saveStatus
item.sender = senderComboBox.model[senderComboBox.currentIndex].secret;
item.parameters = paramValues;
return item;

BIN
mix/qml/img/newIcon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

BIN
mix/qml/img/newIcon@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

108
mix/qml/js/ProjectModel.js

@ -82,9 +82,9 @@ function saveProjectFile()
};
for (var i = 0; i < projectListModel.count; i++)
projectData.files.push({
title: projectListModel.get(i).name,
fileName: projectListModel.get(i).fileName,
});
title: projectListModel.get(i).name,
fileName: projectListModel.get(i).fileName,
});
projectFileSaving(projectData);
var json = JSON.stringify(projectData, null, "\t");
@ -98,50 +98,52 @@ function saveProjectFile()
function loadProject(path) {
closeProject(function() {
console.log("Loading project at " + path);
var projectFile = path + projectFileName;
var json = fileIo.readFile(projectFile);
var projectData = JSON.parse(json);
if (projectData.deploymentDir)
projectModel.deploymentDir = projectData.deploymentDir
if (projectData.packageHash)
deploymentDialog.packageHash = projectData.packageHash
if (projectData.packageBase64)
deploymentDialog.packageBase64 = projectData.packageBase64
if (projectData.applicationUrlEth)
deploymentDialog.applicationUrlEth = projectData.applicationUrlEth
if (projectData.applicationUrlHttp)
deploymentDialog.applicationUrlHttp = projectData.applicationUrlHttp
if (!projectData.title) {
var parts = path.split("/");
projectData.title = parts[parts.length - 2];
}
deploymentAddresses = projectData.deploymentAddresses ? projectData.deploymentAddresses : [];
projectTitle = projectData.title;
projectPath = path;
if (!projectData.files)
projectData.files = [];
for(var i = 0; i < projectData.files.length; i++) {
var entry = projectData.files[i];
if (typeof(entry) === "string")
addFile(entry); //TODO: remove old project file support
else
addFile(entry.fileName, entry.title);
}
if (mainApplication.trackLastProject)
projectSettings.lastProjectPath = path;
projectLoading(projectData);
projectLoaded()
//TODO: move this to codemodel
var contractSources = {};
for (var d = 0; d < listModel.count; d++) {
var doc = listModel.get(d);
if (doc.isContract)
contractSources[doc.documentId] = fileIo.readFile(doc.path);
}
codeModel.reset(contractSources);
console.log("Loading project at " + path);
var projectFile = path + projectFileName;
var json = fileIo.readFile(projectFile);
if (!json)
return;
var projectData = JSON.parse(json);
if (projectData.deploymentDir)
projectModel.deploymentDir = projectData.deploymentDir
if (projectData.packageHash)
deploymentDialog.packageHash = projectData.packageHash
if (projectData.packageBase64)
deploymentDialog.packageBase64 = projectData.packageBase64
if (projectData.applicationUrlEth)
deploymentDialog.applicationUrlEth = projectData.applicationUrlEth
if (projectData.applicationUrlHttp)
deploymentDialog.applicationUrlHttp = projectData.applicationUrlHttp
if (!projectData.title) {
var parts = path.split("/");
projectData.title = parts[parts.length - 2];
}
deploymentAddresses = projectData.deploymentAddresses ? projectData.deploymentAddresses : [];
projectTitle = projectData.title;
projectPath = path;
if (!projectData.files)
projectData.files = [];
for(var i = 0; i < projectData.files.length; i++) {
var entry = projectData.files[i];
if (typeof(entry) === "string")
addFile(entry); //TODO: remove old project file support
else
addFile(entry.fileName, entry.title);
}
if (mainApplication.trackLastProject)
projectSettings.lastProjectPath = path;
projectLoading(projectData);
projectLoaded()
//TODO: move this to codemodel
var contractSources = {};
for (var d = 0; d < listModel.count; d++) {
var doc = listModel.get(d);
if (doc.isContract)
contractSources[doc.documentId] = fileIo.readFile(doc.path);
}
codeModel.reset(contractSources);
});
}
@ -160,12 +162,12 @@ function addFile(fileName, title) {
path: p,
fileName: fileName,
name: title !== undefined ? title : fileName,
documentId: fileName,
syntaxMode: syntaxMode,
isText: isContract || isHtml || isCss || isJs,
isContract: isContract,
isHtml: isHtml,
groupName: groupName
documentId: fileName,
syntaxMode: syntaxMode,
isText: isContract || isHtml || isCss || isJs,
isContract: isContract,
isHtml: isHtml,
groupName: groupName
};
projectListModel.append(docData);

3
mix/qml/js/TransactionHelper.js

@ -12,7 +12,8 @@ function defaultTransaction()
stdContract: false,
isContractCreation: true,
label: "",
isFunctionCall: true
isFunctionCall: true,
saveStatus: true
};
}

22
mix/res.qrc

@ -68,5 +68,27 @@
<file>qml/img/warningicon.png</file>
<file>qml/img/warningicon@2x.png</file>
<file>qml/QAddressView.qml</file>
<file>qml/img/addblock.png</file>
<file>qml/img/addblock@2x.png</file>
<file>qml/img/duplicateicon.png</file>
<file>qml/img/duplicateicon@2x.png</file>
<file>qml/img/leftarrow.png</file>
<file>qml/img/leftarrow@2x.png</file>
<file>qml/img/newaccounticon.png</file>
<file>qml/img/newaccounticon@2x.png</file>
<file>qml/img/recyclediscard.png</file>
<file>qml/img/recyclediscard@2x.png</file>
<file>qml/img/recycleicon.png</file>
<file>qml/img/recycleicon@2x.png</file>
<file>qml/img/recyclekeep.png</file>
<file>qml/img/recyclekeep@2x.png</file>
<file>qml/img/restoreicon.png</file>
<file>qml/img/restoreicon@2x.png</file>
<file>qml/img/rightarrow.png</file>
<file>qml/img/rightarrow@2x.png</file>
<file>qml/img/saveicon.png</file>
<file>qml/img/saveicon@2x.png</file>
<file>qml/img/sendtransactionicon.png</file>
<file>qml/img/sendtransactionicon@2x.png</file>
</qresource>
</RCC>

8
test/libsolidity/SolidityOptimizer.cpp

@ -944,6 +944,14 @@ BOOST_AUTO_TEST_CASE(cse_access_previous_sequence)
// 0, SLOAD, 1, ADD, SSTORE, 0 SLOAD
}
BOOST_AUTO_TEST_CASE(cse_optimise_return)
{
checkCSE(
AssemblyItems{u256(0), u256(7), Instruction::RETURN},
AssemblyItems{Instruction::STOP}
);
}
BOOST_AUTO_TEST_CASE(control_flow_graph_remove_unused)
{
// remove parts of the code that are unused

Loading…
Cancel
Save