diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 6e79403aa..975ac84ee 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -6,8 +6,8 @@ 0 0 - 1504 - 798 + 1711 + 1138 @@ -83,11 +83,6 @@ - - - Tab 2 - - @@ -99,7 +94,7 @@ 0 0 - 1504 + 1711 20 @@ -131,9 +126,18 @@ + + + &Debug + + + + + + @@ -403,7 +407,7 @@ - 442 + 510 360 @@ -437,29 +441,26 @@ - - - - &Amount - - - value + + + + Qt::Vertical + + + + Qt::NoFocus + + - - - - - - - - - - 430000000 + + + + &Gas - - 0 + + gas @@ -479,32 +480,6 @@ - - - - Qt::Vertical - - - - - Qt::NoFocus - - - - - - - - @ - - - 1 - - - 430000000 - - - @@ -521,21 +496,11 @@ - - - - - - - &Gas - - - gas - - + + - - + + 0 @@ -545,28 +510,34 @@ - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - &Send + + + + + + + @ + + + 1 + + + 430000000 - - + + - - 0 + + 1 0 - - + + (Create Contract) @@ -583,16 +554,49 @@ - - + + + + &Execute + + + + + + + + + + 430000000 + + + 0 + + + + + + + &Amount + + + value + + + + + - - 1 + + 0 0 - - (Create Contract) + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -820,6 +824,87 @@ + + + QDockWidget::DockWidgetFeatureMask + + + Debugger + + + 8 + + + + false + + + + + + Qt::Horizontal + + + + + 0 + 0 + + + + + + + 1 + 0 + + + + Qt::Vertical + + + + QAbstractItemView::NoSelection + + + + + true + + + Qt::NoTextInteraction + + + + + true + + + Qt::NoTextInteraction + + + + + + + + + + 0 + 0 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + &Quit @@ -875,6 +960,36 @@ &Preview + + + true + + + &Debug EVM Execution + + + + + false + + + &Single Step + + + F10 + + + + + false + + + &Continue + + + F5 + + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 14b227e27..f55115a04 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "BuildInfo.h" #include "MainWin.h" #include "ui_Main.h" @@ -31,6 +32,7 @@ using eth::PeerInfo; using eth::RLP; using eth::Secret; using eth::Transaction; +using eth::Executive; // functions using eth::toHex; @@ -702,8 +704,8 @@ void Main::on_contracts_currentItemChanged() auto h = h160((byte const*)hba.data(), h160::ConstructFromPointer); stringstream s; - auto mem = state().contractStorage(h); - for (auto const& i: mem) + auto storage = state().contractStorage(h); + for (auto const& i: storage) s << "@" << showbase << hex << i.first << "    " << showbase << hex << i.second << "
"; s << "

Body Code

" << disassemble(state().contractCode(h)); ui->contractInfo->appendHtml(QString::fromStdString(s.str())); @@ -929,11 +931,42 @@ void Main::on_send_clicked() { m_client->unlock(); Secret s = i.secret(); - if (isCreation()) - m_client->transact(s, value(), m_data, m_init, ui->gas->value(), gasPrice()); + if (ui->enableDebug->isChecked()) + { + m_client->lock(); + m_executiveState = state(); + m_client->unlock(); + m_currentExecution = unique_ptr(new Executive(m_executiveState)); + Transaction t; + t.nonce = m_executiveState.transactionsFrom(toAddress(s)); + t.value = value(); + t.gasPrice = gasPrice(); + t.gas = ui->gas->value(); + t.data = m_data; + if (isCreation()) + { + t.receiveAddress = Address(); + t.init = m_init; + } + else + { + t.receiveAddress = fromString(ui->destination->text()); + t.data = m_data; + } + t.sign(s); + auto r = t.rlp(); + m_currentExecution->setup(&r); + initDebugger(); + updateDebugger(); + } else - m_client->transact(s, value(), fromString(ui->destination->text()), m_data, ui->gas->value(), gasPrice()); - refresh(); + { + if (isCreation()) + m_client->transact(s, value(), m_data, m_init, ui->gas->value(), gasPrice()); + else + m_client->transact(s, value(), fromString(ui->destination->text()), m_data, ui->gas->value(), gasPrice()); + refresh(); + } return; } m_client->unlock(); @@ -946,6 +979,102 @@ void Main::on_create_triggered() m_keysChanged = true; } +void Main::on_enableDebug_triggered() +{ + ui->debugPanel->setEnabled(ui->enableDebug->isChecked()); + ui->send->setText(ui->enableDebug->isChecked() ? "D&ebug" : "&Execute"); +} + +void Main::on_debugStep_triggered() +{ + if (!m_currentExecution) + return; + if (m_currentExecution->go(1)) + debugFinished(); + else + updateDebugger(); +} + +void Main::on_debugContinue_triggered() +{ + if (!m_currentExecution) + return; + if (m_currentExecution->go()) + debugFinished(); + else + updateDebugger(); +} + +void Main::debugFinished() +{ + m_currentExecution.reset(); + ui->debugCode->clear(); + ui->debugStack->clear(); + ui->debugMemory->setHtml(""); + ui->debugStorage->setHtml(""); + ui->debugStateInfo->setText(""); + ui->send->setEnabled(true); + ui->enableDebug->setEnabled(true); + ui->debugStep->setEnabled(false); + ui->debugContinue->setEnabled(false); + ui->debugPanel->setEnabled(false); +} + +void Main::initDebugger() +{ + ui->send->setEnabled(false); + ui->enableDebug->setEnabled(false); + ui->debugStep->setEnabled(true); + ui->debugContinue->setEnabled(true); + ui->debugPanel->setEnabled(true); + ui->debugCode->setEnabled(false); + + QListWidget* dc = ui->debugCode; + dc->clear(); + if (m_currentExecution) + { + for (unsigned i = 0; i <= m_currentExecution->ext().code.size(); ++i) + { + byte b = i < m_currentExecution->ext().code.size() ? m_currentExecution->ext().code[i] : 0; + QString s = c_instructionInfo.at((Instruction)b).name; + m_pcWarp[i] = dc->count(); + ostringstream out; + out << hex << setw(4) << setfill('0') << i; + if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32) + { + unsigned bc = b - (byte)Instruction::PUSH1 + 1; + s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&m_currentExecution->ext().code[i + 1], bc))); + i += bc; + } + dc->addItem(QString::fromStdString(out.str()) + " " + s); + } + + } +} + +void Main::updateDebugger() +{ + QListWidget* ds = ui->debugStack; + ds->clear(); + if (m_currentExecution) + { + eth::VM const& vm = m_currentExecution->vm(); + for (auto i: vm.stack()) + ds->insertItem(0, QString::fromStdString(toHex(((h256)i).asArray()))); + ui->debugMemory->setHtml(QString::fromStdString(htmlDump(vm.memory(), 16))); + ui->debugCode->setCurrentRow(m_pcWarp[(unsigned)vm.curPC()]); + ostringstream ss; + ss << hex << "PC: 0x" << vm.curPC() << " | GAS: 0x" << vm.gas(); + ui->debugStateInfo->setText(QString::fromStdString(ss.str())); + + stringstream s; + auto storage = m_currentExecution->state().contractStorage(m_currentExecution->ext().myAddress); + for (auto const& i: storage) + s << "@" << showbase << hex << i.first << "    " << showbase << hex << i.second << "
"; + ui->debugStorage->setHtml(QString::fromStdString(s.str())); + } +} + // extra bits needed to link on VS #ifdef _MSC_VER diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index b40427b66..758b249bb 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace Ui { @@ -287,6 +288,9 @@ private slots: void on_preview_triggered() { refresh(true); } void on_quit_triggered() { close(); } void on_urlEdit_editingFinished(); + void on_debugStep_triggered(); + void on_debugContinue_triggered(); + void on_enableDebug_triggered(); void refresh(bool _override = false); void refreshNetwork(); @@ -297,6 +301,9 @@ signals: private: QString pretty(eth::Address _a) const; + void initDebugger(); + void updateDebugger(); + void debugFinished(); QString render(eth::Address _a) const; eth::Address fromString(QString const& _a) const; @@ -329,6 +336,10 @@ private: unsigned m_backupGas; + eth::State m_executiveState; + std::unique_ptr m_currentExecution; + QMap m_pcWarp; + QNetworkAccessManager m_webCtrl; QEthereum* m_ethereum; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 23ad0cbd1..513971565 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -601,6 +601,166 @@ bytes const& State::contractCode(Address _contract) const return m_cache[_contract].code(); } +Executive::~Executive() +{ + // TODO: Make safe. + delete m_ext; + delete m_vm; +} + +void Executive::setup(bytesConstRef _rlp) +{ + // Entry point for a user-executed transaction. + m_t = Transaction(_rlp); + + auto sender = m_t.sender(); + + // Avoid invalid transactions. + auto nonceReq = m_s.transactionsFrom(sender); + if (m_t.nonce != nonceReq) + { + clog(StateChat) << "Invalid Nonce."; + throw InvalidNonce(nonceReq, m_t.nonce); + } + + // Don't like transactions whose gas price is too low. NOTE: this won't stay here forever - it's just until we get a proper gas price discovery protocol going. + if (m_t.gasPrice < 10 * szabo) + { + clog(StateChat) << "Offered gas-price is too low."; + throw GasPriceTooLow(); + } + + // Check gas cost is enough. + u256 gasCost; + if (m_t.isCreation()) + gasCost = (m_t.init.size() + m_t.data.size()) * c_txDataGas + c_createGas; + else + gasCost = m_t.data.size() * c_txDataGas + c_callGas; + + if (m_t.gas < gasCost) + { + clog(StateChat) << "Not enough gas to pay for the transaction."; + throw OutOfGas(); + } + + m_startGas = m_t.gas; + + u256 cost = m_t.value + m_t.gas * m_t.gasPrice; + + // Avoid unaffordable transactions. + if (m_s.balance(sender) < cost) + { + clog(StateChat) << "Not enough cash."; + throw NotEnoughCash(); + } + + // Increment associated nonce for sender. + m_s.noteSending(sender); + + // Pay... + cnote << "Paying" << formatBalance(cost) << "from sender (includes" << m_t.gas << "gas at" << formatBalance(m_t.gasPrice) << ")"; + m_s.subBalance(sender, cost); + + if (m_t.isCreation()) + create(sender, m_t.value, m_t.gasPrice, m_t.gas - gasCost, &m_t.data, &m_t.init, sender); + else + call(m_t.receiveAddress, sender, m_t.value, m_t.gasPrice, bytesConstRef(&m_t.data), m_t.gas - gasCost, sender); +} + +void Executive::call(Address _receiveAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256 _gas, Address _originAddress) +{ + cnote << "Transferring" << formatBalance(_value) << "to receiver."; + m_s.addBalance(_receiveAddress, _value); + + if (m_s.isContractAddress(_receiveAddress)) + { + m_vm = new VM(_gas); + m_ext = new ExtVM(m_s, _receiveAddress, _senderAddress, _originAddress, _value, _gasPrice, _data, &m_s.contractCode(_receiveAddress)); + } + else + m_endGas = _gas; +} + +void Executive::create(Address _sender, u256 _endowment, u256 _gasPrice, u256 _gas, bytesConstRef _code, bytesConstRef _init, Address _origin) +{ + m_newAddress = right160(sha3(rlpList(_sender, m_s.transactionsFrom(_sender) - 1))); + while (m_s.isContractAddress(m_newAddress) || m_s.isNormalAddress(m_newAddress)) + m_newAddress = (u160)m_newAddress + 1; + + // Set up new account... + m_s.m_cache[m_newAddress] = AddressState(0, 0, _code); + + // Execute _init. + m_vm = new VM(_gas); + m_ext = new ExtVM(m_s, m_newAddress, _sender, _origin, _endowment, _gasPrice, bytesConstRef(), _init); +} + +bool Executive::go(uint64_t _steps) +{ + if (m_vm) + { + bool revert = false; + try + { + m_vm->go(*m_ext, _steps); + m_endGas = m_vm->gas(); + } + catch (StepsDone const&) + { + return false; + } + catch (OutOfGas const& /*_e*/) + { + clog(StateChat) << "Out of Gas! Reverting."; + revert = true; + } + catch (VMException const& _e) + { + clog(StateChat) << "VM Exception: " << _e.description(); + } + catch (Exception const& _e) + { + clog(StateChat) << "Exception in VM: " << _e.description(); + } + catch (std::exception const& _e) + { + clog(StateChat) << "std::exception in VM: " << _e.what(); + } + + // Write state out only in the case of a non-excepted transaction. + if (revert) + { + m_ext->revert(); + if (m_newAddress) + { + m_s.m_cache.erase(m_newAddress); + m_newAddress = Address(); + } + } + } + return true; +} + +u256 Executive::gas() const +{ + return m_vm->gas(); +} + +void Executive::finalize() +{ + cnote << "Refunding" << formatBalance(m_endGas * m_ext->gasPrice) << "to origin (=" << m_endGas << "*" << formatBalance(m_ext->gasPrice) << ")"; + m_s.addBalance(m_ext->origin, m_endGas * m_ext->gasPrice); + + u256 gasSpent = (m_startGas - m_endGas) * m_ext->gasPrice; +/* unsigned c_feesKept = 8; + u256 feesEarned = gasSpent - (gasSpent / c_feesKept); + cnote << "Transferring" << (100.0 - 100.0 / c_feesKept) << "% of" << formatBalance(gasSpent) << "=" << formatBalance(feesEarned) << "to miner (" << formatBalance(gasSpent - feesEarned) << "is burnt)."; +*/ + u256 feesEarned = gasSpent; + cnote << "Transferring" << formatBalance(gasSpent) << "to miner."; + m_s.addBalance(m_s.m_currentBlock.coinbaseAddress, feesEarned); +} + void State::execute(bytesConstRef _rlp) { // Entry point for a user-executed transaction. @@ -616,7 +776,7 @@ void State::execute(bytesConstRef _rlp) throw InvalidNonce(nonceReq, t.nonce); } - // Don't like transactions whose gas price is too low. NOTE: this won't stay here forever - it's just until we get a proper gas proce discovery protocol going. + // Don't like transactions whose gas price is too low. NOTE: this won't stay here forever - it's just until we get a proper gas price discovery protocol going. if (t.gasPrice < 10 * szabo) { clog(StateChat) << "Offered gas-price is too low."; @@ -671,6 +831,7 @@ void State::execute(bytesConstRef _rlp) cnote << "Transferring" << formatBalance(gasSpent) << "to miner."; addBalance(m_currentBlock.coinbaseAddress, feesEarned); + // !!!!!!!!!!!!!!!!!!!!! If moving to use Executive, this still needs to be done - Executive won't do it. // Add to the user-originated transactions that we've executed. m_transactions.push_back(t); m_transactionSet.insert(t.sha3()); diff --git a/libethereum/State.h b/libethereum/State.h index 6a5485df2..82a34cdf8 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -50,7 +50,43 @@ static const bytes EmptyBytes; struct StateChat: public LogChannel { static const char* name() { return "=S="; } static const int verbosity = 4; }; +class VM; class ExtVM; +class State; + +class Executive +{ +public: + Executive(State& _s): m_s(_s) {} + ~Executive(); + + void setup(bytesConstRef _transaction); + void create(Address _txSender, u256 _endowment, u256 _gasPrice, u256 _gas, bytesConstRef _code, bytesConstRef _init, Address _originAddress); + void call(Address _myAddress, Address _txSender, u256 _txValue, u256 _gasPrice, bytesConstRef _txData, u256 _gas, Address _originAddress); + bool go(uint64_t _steps = (unsigned)-1); + void finalize(); + + u256 gas() const; + + bytesConstRef out() const { return m_out; } + h160 newAddress() const { return m_newAddress; } + + VM const& vm() const { return *m_vm; } + State const& state() const { return m_s; } + ExtVM const& ext() const { return *m_ext; } + +private: + State& m_s; + ExtVM* m_ext = nullptr; // TODO: make safe. + VM* m_vm = nullptr; + bytesConstRef m_out; + Address m_newAddress; + + Transaction m_t; + + u256 m_startGas; + u256 m_endGas; +}; /** * @brief Model of the current state of the ledger. @@ -61,10 +97,11 @@ class State { template friend class UnitTest; friend class ExtVM; + friend class Executive; public: /// Construct state object. - State(Address _coinbaseAddress, Overlay const& _db); + State(Address _coinbaseAddress = Address(), Overlay const& _db = Overlay()); /// Copy state object. State(State const& _s); diff --git a/libethereum/VM.cpp b/libethereum/VM.cpp index 6f69c64b3..5383f4906 100644 --- a/libethereum/VM.cpp +++ b/libethereum/VM.cpp @@ -28,7 +28,4 @@ void VM::reset(u256 _gas) { m_gas = _gas; m_curPC = 0; - m_nextPC = 1; - m_stepCount = 0; - m_runFee = 0; } diff --git a/libethereum/VM.h b/libethereum/VM.h index 99b0a4740..60584ab4e 100644 --- a/libethereum/VM.h +++ b/libethereum/VM.h @@ -64,17 +64,17 @@ public: void require(u256 _n) { if (m_stack.size() < _n) throw StackTooSmall(_n, m_stack.size()); } void requireMem(unsigned _n) { if (m_temp.size() < _n) { m_temp.resize(_n); } } - u256 runFee() const { return m_runFee; } u256 gas() const { return m_gas; } + u256 curPC() const { return m_curPC; } + + bytes const& memory() const { return m_temp; } + u256s const& stack() const { return m_stack; } private: u256 m_gas = 0; u256 m_curPC = 0; - u256 m_nextPC = 1; - uint64_t m_stepCount = 0; bytes m_temp; - std::vector m_stack; - u256 m_runFee = 0; + u256s m_stack; }; } @@ -82,10 +82,9 @@ private: // INLINE: template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps) { - for (bool stopped = false; !stopped && _steps--; m_curPC = m_nextPC, m_nextPC = m_curPC + 1) + u256 nextPC = m_curPC + 1; + for (bool stopped = false; !stopped && _steps--; m_curPC = nextPC, nextPC = m_curPC + 1) { - m_stepCount++; - // INSTRUCTION... Instruction inst = (Instruction)_ext.getCode(m_curPC); @@ -310,7 +309,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps) case Instruction::CALLDATALOAD: { require(1); - if ((unsigned)m_stack.back() + 32 < _ext.data.size()) + if ((unsigned)m_stack.back() + 31 < _ext.data.size()) m_stack.back() = (u256)*(h256 const*)(_ext.data.data() + (unsigned)m_stack.back()); else { @@ -379,10 +378,10 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps) case Instruction::PUSH32: { int i = (int)inst - (int)Instruction::PUSH1 + 1; - m_nextPC = m_curPC + 1; + nextPC = m_curPC + 1; m_stack.push_back(0); - for (; i--; m_nextPC++) - m_stack.back() = (m_stack.back() << 8) | _ext.getCode(m_nextPC); + for (; i--; nextPC++) + m_stack.back() = (m_stack.back() << 8) | _ext.getCode(nextPC); break; } case Instruction::POP: @@ -456,13 +455,13 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps) break; case Instruction::JUMP: require(1); - m_nextPC = m_stack.back(); + nextPC = m_stack.back(); m_stack.pop_back(); break; case Instruction::JUMPI: require(2); if (m_stack[m_stack.size() - 2]) - m_nextPC = m_stack.back(); + nextPC = m_stack.back(); m_stack.pop_back(); m_stack.pop_back(); break; @@ -557,7 +556,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps) throw BadInstruction(); } } - if (_steps == (unsigned)-1) + if (_steps == (uint64_t)-1) throw StepsDone(); return bytesConstRef(); }