Browse Source

Merge pull request #2822 from ethereum/transactasyncgas

Async Transact dialog for gas determination.
cl-refactor
Gav Wood 9 years ago
parent
commit
1a5039f476
  1. 185
      alethzero/Transact.cpp
  2. 14
      alethzero/Transact.h
  3. 321
      alethzero/Transact.ui

185
alethzero/Transact.cpp

@ -104,7 +104,12 @@ bool Transact::isCreation() const
u256 Transact::fee() const
{
return ui->gas->value() * gasPrice();
return gas() * gasPrice();
}
u256 Transact::gas() const
{
return ui->gas->value() == -1 ? m_upperBound : ui->gas->value();
}
u256 Transact::value() const
@ -197,10 +202,10 @@ void Transact::on_copyUnsigned_clicked()
if (isCreation())
// If execution is a contract creation, add Natspec to
// a local Natspec LEVELDB
t = Transaction(value(), gasPrice(), ui->gas->value(), m_data, nonce);
t = Transaction(value(), gasPrice(), gas(), m_data, nonce);
else
// TODO: cache like m_data.
t = Transaction(value(), gasPrice(), ui->gas->value(), toAccount().first, m_data, nonce);
t = Transaction(value(), gasPrice(), gas(), toAccount().first, m_data, nonce);
qApp->clipboard()->setText(QString::fromStdString(toHex(t.rlp())));
}
@ -334,6 +339,90 @@ pair<Address, bytes> Transact::toAccount()
return p;
}
void Transact::timerEvent(QTimerEvent*)
{
Address from = fromAccount();
Address to = toAccount().first;
if (m_upperBound != m_lowerBound)
{
qint64 mid = (m_lowerBound + m_upperBound) / 2;
ExecutionResult er;
if (isCreation())
er = ethereum()->create(from, value(), m_data, mid, gasPrice(), PendingBlock, FudgeFactor::Lenient);
else
er = ethereum()->call(from, value(), to, m_data, mid, gasPrice(), PendingBlock, FudgeFactor::Lenient);
if (er.excepted == TransactionException::OutOfGas || er.excepted == TransactionException::OutOfGasBase || er.excepted == TransactionException::OutOfGasIntrinsic || er.codeDeposit == CodeDeposit::Failed)
m_lowerBound = m_lowerBound == mid ? m_upperBound : mid;
else
{
m_lastGood = er;
m_upperBound = m_upperBound == mid ? m_lowerBound : mid;
}
updateBounds();
if (m_lowerBound == m_upperBound)
finaliseBounds();
}
}
void Transact::updateBounds()
{
ui->minGas->setValue(m_lowerBound);
ui->maxGas->setValue(m_upperBound);
double oran = m_startUpperBound - m_startLowerBound;
double nran = m_upperBound - m_lowerBound;
int x = int(log2(oran / nran) * 100.0 / log2(oran * 2));
ui->progressGas->setValue(x);
ui->progressGas->setVisible(true);
ui->gas->setSpecialValueText(QString("Auto (%1 gas)").arg(m_upperBound));
}
void Transact::finaliseBounds()
{
quint64 baseGas = (quint64)Transaction::gasRequired(m_data, 0);
ui->progressGas->setVisible(false);
quint64 executionGas = m_upperBound - baseGas;
QString htmlInfo = QString("<div class=\"info\"><span class=\"icon\">INFO</span> Gas required: %1 total = %2 base, %3 exec [%4 refunded later]</div>").arg(m_upperBound).arg(baseGas).arg(executionGas).arg((qint64)m_lastGood.gasRefunded);
auto bail = [&](QString he) {
ui->send->setEnabled(false);
ui->code->setHtml(he + htmlInfo + m_dataInfo);
};
auto s = fromAccount();
auto b = ethereum()->balanceAt(s, PendingBlock);
if (b < value() + baseGas * gasPrice())
{
// Not enough - bail.
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Account doesn't contain enough for paying even the basic amount of gas required.</div>");
return;
}
if (m_upperBound > m_ethereum->gasLimitRemaining())
{
// Not enough - bail.
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Gas remaining in block isn't enough to allow the gas required.</div>");
return;
}
if (m_lastGood.excepted != TransactionException::None)
{
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> " + QString::fromStdString(toString(m_lastGood.excepted)) + "</div>");
return;
}
if (m_lastGood.codeDeposit == CodeDeposit::Failed)
{
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Code deposit failed due to insufficient gas; " + QString::fromStdString(toString(m_lastGood.gasForDeposit)) + " GAS &lt; " + QString::fromStdString(toString(m_lastGood.depositSize)) + " bytes * " + QString::fromStdString(toString(c_createDataGas)) + "GAS/byte</div>");
return;
}
updateFee();
ui->code->setHtml(htmlInfo + m_dataInfo);
ui->send->setEnabled(true);
killTimer(m_gasCalcTimer);
}
GasRequirements Transact::determineGasRequirements()
{
// Determine the minimum amount of gas we need to play...
@ -343,12 +432,11 @@ GasRequirements Transact::determineGasRequirements()
Address to = toAccount().first;
ExecutionResult lastGood;
bool haveUpperBound = false;
qint64 lowerBound = baseGas;
qint64 upperBound = (qint64)ethereum()->gasLimitRemaining();
for (unsigned i = 0; i < 30 && ((haveUpperBound && upperBound - lowerBound > 16) || !haveUpperBound); ++i) // get to with 100.
m_startLowerBound = baseGas;
m_startUpperBound = (qint64)ethereum()->gasLimitRemaining();
for (unsigned i = 0; i < 30; ++i)
{
qint64 mid = haveUpperBound ? (lowerBound + upperBound) / 2 : upperBound;
qint64 mid = m_startUpperBound;
ExecutionResult er;
if (isCreation())
er = ethereum()->create(from, value(), m_data, mid, gasPrice(), PendingBlock, FudgeFactor::Lenient);
@ -356,23 +444,21 @@ GasRequirements Transact::determineGasRequirements()
er = ethereum()->call(from, value(), to, m_data, mid, gasPrice(), PendingBlock, FudgeFactor::Lenient);
if (er.excepted == TransactionException::OutOfGas || er.excepted == TransactionException::OutOfGasBase || er.excepted == TransactionException::OutOfGasIntrinsic || er.codeDeposit == CodeDeposit::Failed)
{
lowerBound = mid;
if (!haveUpperBound)
upperBound *= 2;
m_startLowerBound = mid;
m_startUpperBound *= 2;
}
else
{
lastGood = er;
if (haveUpperBound)
upperBound = mid;
else
haveUpperBound = true;
// Begin async binary chop for gas calculation..
m_lastGood = lastGood;
m_lowerBound = m_startLowerBound;
m_upperBound = m_startUpperBound;
killTimer(m_gasCalcTimer);
m_gasCalcTimer = startTimer(0);
return GasRequirements{m_upperBound, baseGas, m_upperBound - baseGas, (qint64)lastGood.gasRefunded, lastGood};
}
}
// Dry-run execution to determine gas requirement and any execution errors
// (qint64)(er.gasUsed + er.gasRefunded + c_callStipend);
return GasRequirements{upperBound, baseGas, upperBound - baseGas, (qint64)lastGood.gasRefunded, lastGood};
return GasRequirements();
}
void Transact::rejigData()
@ -386,15 +472,12 @@ void Transact::rejigData()
if (!s)
return;
auto b = ethereum()->balanceAt(s, PendingBlock);
m_allGood = true;
QString htmlInfo;
auto bail = [&](QString he) {
m_allGood = false;
// ui->send->setEnabled(false);
ui->code->setHtml(he + htmlInfo);
ui->send->setEnabled(false);
m_dataInfo = he + htmlInfo;
ui->code->setHtml(m_dataInfo);
};
// Determine m_info.
@ -422,49 +505,15 @@ void Transact::rejigData()
htmlInfo += "<h4>Hex</h4>" + QString(ETH_HTML_DIV(ETH_HTML_MONO)) + QString::fromStdString(toHex(m_data)) + "</div>";
auto gasReq = determineGasRequirements();
htmlInfo = QString("<div class=\"info\"><span class=\"icon\">INFO</span> Gas required: %1 total = %2 base, %3 exec [%4 refunded later]</div>").arg(gasReq.neededGas).arg(gasReq.baseGas).arg(gasReq.executionGas).arg(gasReq.refundedGas) + htmlInfo;
if (b < value() + gasReq.baseGas * gasPrice())
{
// Not enough - bail.
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Account doesn't contain enough for paying even the basic amount of gas required.</div>");
return;
}
if (gasReq.neededGas > m_ethereum->gasLimitRemaining())
{
// Not enough - bail.
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Gas remaining in block isn't enough to allow the gas required.</div>");
return;
}
if (gasReq.er.excepted != TransactionException::None)
{
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> " + QString::fromStdString(toString(gasReq.er.excepted)) + "</div>");
return;
}
if (gasReq.er.codeDeposit == CodeDeposit::Failed)
{
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Code deposit failed due to insufficient gas; " + QString::fromStdString(toString(gasReq.er.gasForDeposit)) + " GAS &lt; " + QString::fromStdString(toString(gasReq.er.depositSize)) + " bytes * " + QString::fromStdString(toString(c_createDataGas)) + "GAS/byte</div>");
return;
}
// Add Natspec information
if (!isCreation())
htmlInfo = "<div class=\"info\"><span class=\"icon\">INFO</span> " + QString::fromStdString(natspecNotice(toAccount().first, m_data)).toHtmlEscaped() + "</div>" + htmlInfo;
// Update gas
if (ui->gas->value() == ui->gas->minimum())
{
ui->gas->setMinimum(gasReq.neededGas);
ui->gas->setValue(gasReq.neededGas);
}
else
ui->gas->setMinimum(gasReq.neededGas);
updateFee();
determineGasRequirements();
ui->code->setHtml(htmlInfo);
// ui->send->setEnabled(m_allGood);
m_dataInfo = htmlInfo;
ui->code->setHtml(m_dataInfo);
ui->send->setEnabled(true);
}
Secret Transact::findSecret(u256 _totalReq) const
@ -526,7 +575,7 @@ void Transact::on_send_clicked()
{
// If execution is a contract creation, add Natspec to
// a local Natspec LEVELDB
ethereum()->submitTransaction(s, value(), m_data, ui->gas->value(), gasPrice(), nonce);
ethereum()->submitTransaction(s, value(), m_data, gas(), gasPrice(), nonce);
#if ETH_SOLIDITY
string src = ui->data->toPlainText().toStdString();
if (sourceIsSolidity(src))
@ -545,7 +594,7 @@ void Transact::on_send_clicked()
}
else
// TODO: cache like m_data.
ethereum()->submitTransaction(s, value(), toAccount().first, m_data, ui->gas->value(), gasPrice(), nonce);
ethereum()->submitTransaction(s, value(), toAccount().first, m_data, gas(), gasPrice(), nonce);
close();
}
@ -564,8 +613,8 @@ void Transact::on_debug_clicked()
{
Block postState(ethereum()->postState());
Transaction t = isCreation() ?
Transaction(value(), gasPrice(), ui->gas->value(), m_data, postState.transactionsFrom(from)) :
Transaction(value(), gasPrice(), ui->gas->value(), toAccount().first, m_data, postState.transactionsFrom(from));
Transaction(value(), gasPrice(), gas(), m_data, postState.transactionsFrom(from)) :
Transaction(value(), gasPrice(), gas(), toAccount().first, m_data, postState.transactionsFrom(from));
t.forceSender(from);
Debugger dw(m_main, this);
Executive e(postState, ethereum()->blockChain(), 0);

14
alethzero/Transact.h

@ -79,6 +79,8 @@ private:
dev::eth::Client* ethereum() const { return m_ethereum; }
void rejigData();
void updateNonce();
void updateBounds();
void finaliseBounds();
dev::Address fromAccount();
std::pair<dev::Address, bytes> toAccount();
@ -86,6 +88,7 @@ private:
void updateFee();
bool isCreation() const;
dev::u256 fee() const;
dev::u256 gas() const;
dev::u256 total() const;
dev::u256 value() const;
dev::u256 gasPrice() const;
@ -95,6 +98,8 @@ private:
std::string natspecNotice(dev::Address _to, dev::bytes const& _data);
dev::Secret findSecret(dev::u256 _totalReq) const;
void timerEvent(QTimerEvent*) override;
Ui::Transact* ui = nullptr;
unsigned m_backupGas = 0;
@ -104,9 +109,14 @@ private:
dev::eth::Client* m_ethereum = nullptr;
MainFace* m_main = nullptr;
NatSpecFace* m_natSpecDB = nullptr;
bool m_allGood = false;
bool m_determiningGas = false;
QString m_dataInfo;
qint64 m_startLowerBound = 0;
qint64 m_startUpperBound = 0;
qint64 m_lowerBound = 0;
qint64 m_upperBound = 0;
eth::ExecutionResult m_lastGood;
int m_gasCalcTimer = 0;
};
}

321
alethzero/Transact.ui

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>543</width>
<width>604</width>
<height>695</height>
</rect>
</property>
@ -14,82 +14,62 @@
<string>Transact</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="10" column="2">
<widget class="QPushButton" name="debug">
<property name="text">
<string>&amp;Debug</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="gas">
<property name="specialValueText">
<string>Automatic</string>
</property>
<property name="suffix">
<string> gas</string>
</property>
<property name="minimum">
<number>1</number>
<number>-1</number>
</property>
<property name="maximum">
<number>430000000</number>
</property>
<property name="value">
<number>10000</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QPushButton" name="cancel">
<property name="text">
<string>&amp;Cancel</string>
</property>
<property name="shortcut">
<string>Esc</string>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QPushButton" name="debug">
<property name="text">
<string>&amp;Debug</string>
<number>-1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;To</string>
</property>
<property name="buddy">
<cstring>destination</cstring>
</property>
</widget>
<item row="3" column="3">
<widget class="QComboBox" name="valueUnits"/>
</item>
<item row="4" column="3">
<widget class="QComboBox" name="gasPriceUnits"/>
</item>
<item row="7" column="0" colspan="4">
<widget class="QLabel" name="total">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="2" column="1" colspan="3">
<widget class="QLineEdit" name="calculatedName">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label5_2">
<item row="7" column="1">
<widget class="QCheckBox" name="optimize">
<property name="text">
<string>&amp;Amount</string>
<string>&amp;Optimise</string>
</property>
<property name="buddy">
<cstring>value</cstring>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="3">
<item row="10" column="3">
<widget class="QPushButton" name="send">
<property name="text">
<string>&amp;Execute</string>
@ -99,52 +79,41 @@
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QComboBox" name="destination">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>(Create Contract)</string>
<item row="7" column="3">
<widget class="QSpinBox" name="nonce">
<property name="enabled">
<bool>false</bool>
</property>
</item>
</widget>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="gasPrice">
<property name="prefix">
<string>@ </string>
<item row="8" column="0" colspan="4">
<widget class="QSplitter" name="splitter_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="minimum">
<number>0</number>
<widget class="QPlainTextEdit" name="data">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="maximum">
<number>430000000</number>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QTextEdit" name="code">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="text">
<string>D&amp;ata</string>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
<property name="lineWidth">
<number>0</number>
</property>
<property name="buddy">
<cstring>data</cstring>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QComboBox" name="valueUnits"/>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QSpinBox" name="value">
@ -159,55 +128,116 @@
</property>
</widget>
</item>
<item row="2" column="1" colspan="3">
<widget class="QLineEdit" name="calculatedName">
<property name="enabled">
<bool>false</bool>
<item row="10" column="1">
<widget class="QPushButton" name="copyUnsigned">
<property name="text">
<string>Copy &amp;Unsigned</string>
</property>
<property name="readOnly">
<bool>true</bool>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="from"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Gas</string>
</property>
<property name="placeholderText">
<string/>
<property name="buddy">
<cstring>gas</cstring>
</property>
</widget>
</item>
<item row="6" column="0" colspan="4">
<widget class="QSplitter" name="splitter_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="1" column="0">
<widget class="QLabel" name="label5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<widget class="QPlainTextEdit" name="data">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<property name="text">
<string>&amp;To</string>
</property>
<property name="lineWidth">
<number>0</number>
<property name="buddy">
<cstring>destination</cstring>
</property>
</widget>
<widget class="QTextEdit" name="code">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</item>
<item row="10" column="0">
<widget class="QPushButton" name="cancel">
<property name="text">
<string>&amp;Cancel</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<property name="shortcut">
<string>Esc</string>
</property>
<property name="lineWidth">
<number>0</number>
</widget>
</item>
<item row="7" column="2">
<widget class="QCheckBox" name="autoNonce">
<property name="text">
<string>Auto Nonce</string>
</property>
<property name="readOnly">
<property name="checked">
<bool>true</bool>
</property>
<property name="autoRepeat">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QComboBox" name="destination">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>(Create Contract)</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<item row="9" column="0" colspan="4">
<widget class="QLabel" name="total">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Gas</string>
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label5_2">
<property name="text">
<string>&amp;Amount</string>
</property>
<property name="buddy">
<cstring>gas</cstring>
<cstring>value</cstring>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>D&amp;ata</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>data</cstring>
</property>
</widget>
</item>
@ -221,43 +251,64 @@
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="from"/>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="optimize">
<property name="text">
<string>&amp;Optimise</string>
<item row="4" column="2">
<widget class="QSpinBox" name="gasPrice">
<property name="prefix">
<string>@ </string>
</property>
<property name="checked">
<bool>true</bool>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>430000000</number>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QSpinBox" name="nonce">
<property name="enabled">
<bool>false</bool>
<widget class="QSpinBox" name="maxGas">
<property name="suffix">
<string> gas</string>
</property>
<property name="prefix">
<string>max </string>
</property>
<property name="maximum">
<number>450000000</number>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QCheckBox" name="autoNonce">
<property name="text">
<string>Auto Nonce</string>
</property>
<property name="checked">
<widget class="QSpinBox" name="minGas">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="autoRepeat">
<bool>false</bool>
<property name="suffix">
<string> gas</string>
</property>
<property name="prefix">
<string>min </string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>450000000</number>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QPushButton" name="copyUnsigned">
<property name="text">
<string>Copy &amp;Unsigned</string>
<item row="5" column="1">
<widget class="QProgressBar" name="progressGas">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>24</number>
</property>
<property name="format">
<string>Calculating gas...</string>
</property>
</widget>
</item>

Loading…
Cancel
Save