Browse Source

Merge pull request #1187 from yann300/accountManagement

Accounts management
cl-refactor
Arkadiy Paronyan 10 years ago
parent
commit
e8810789ee
  1. 38
      mix/ClientModel.cpp
  2. 11
      mix/ClientModel.h
  3. 22
      mix/MixClient.cpp
  4. 6
      mix/MixClient.h
  5. 2
      mix/qml/DeploymentDialog.qml
  6. 4
      mix/qml/Ether.qml
  7. 151
      mix/qml/StateDialog.qml
  8. 59
      mix/qml/StateListModel.qml
  9. 2
      mix/qml/StatusPane.qml
  10. 33
      mix/qml/TransactionDialog.qml

38
mix/ClientModel.cpp

@ -38,6 +38,7 @@
#include "QEther.h"
#include "Web3Server.h"
#include "ClientModel.h"
#include "MixClient.h"
using namespace dev;
using namespace dev::eth;
@ -87,7 +88,7 @@ ClientModel::ClientModel(AppContext* _context):
connect(this, &ClientModel::runComplete, this, &ClientModel::showDebugger, Qt::QueuedConnection);
m_client.reset(new MixClient(QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString()));
m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), std::vector<dev::KeyPair> { m_client->userAccount() }, m_client.get()));
m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), m_client->userAccounts(), m_client.get()));
connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection);
_context->appEngine()->rootContext()->setContextProperty("clientModel", this);
}
@ -136,6 +137,12 @@ void ClientModel::mine()
});
}
QString ClientModel::newAddress()
{
KeyPair a = KeyPair::create();
return QString::fromStdString(toHex(a.secret().ref()));
}
QVariantMap ClientModel::contractAddresses() const
{
QVariantMap res;
@ -144,16 +151,18 @@ QVariantMap ClientModel::contractAddresses() const
return res;
}
void ClientModel::debugDeployment()
{
executeSequence(std::vector<TransactionSettings>(), 10000000 * ether);
}
void ClientModel::setupState(QVariantMap _state)
{
u256 balance = (qvariant_cast<QEther*>(_state.value("balance")))->toU256Wei();
QVariantList balances = _state.value("accounts").toList();
QVariantList transactions = _state.value("transactions").toList();
std::map<Secret, u256> accounts;
for (auto const& b: balances)
{
QVariantMap address = b.toMap();
accounts.insert(std::make_pair(Secret(address.value("secret").toString().toStdString()), (qvariant_cast<QEther*>(address.value("balance")))->toU256Wei()));
}
std::vector<TransactionSettings> transactionSequence;
for (auto const& t: transactions)
{
@ -163,7 +172,7 @@ void ClientModel::setupState(QVariantMap _state)
u256 gas = boost::get<u256>(qvariant_cast<QBigInt*>(transaction.value("gas"))->internalValue());
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei();
u256 gasPrice = (qvariant_cast<QEther*>(transaction.value("gasPrice")))->toU256Wei();
QString sender = transaction.value("sender").toString();
bool isStdContract = (transaction.value("stdContract").toBool());
if (isStdContract)
{
@ -173,6 +182,7 @@ void ClientModel::setupState(QVariantMap _state)
transactionSettings.gasPrice = 10000000000000;
transactionSettings.gas = 125000;
transactionSettings.value = 0;
transactionSettings.sender = Secret(sender.toStdString());
transactionSequence.push_back(transactionSettings);
}
else
@ -180,7 +190,7 @@ void ClientModel::setupState(QVariantMap _state)
if (contractId.isEmpty() && m_context->codeModel()->hasContract()) //TODO: This is to support old project files, remove later
contractId = m_context->codeModel()->contracts().keys()[0];
QVariantList qParams = transaction.value("qType").toList();
TransactionSettings transactionSettings(contractId, functionId, value, gas, gasPrice);
TransactionSettings transactionSettings(contractId, functionId, value, gas, gasPrice, Secret(sender.toStdString()));
for (QVariant const& variant: qParams)
{
@ -194,10 +204,10 @@ void ClientModel::setupState(QVariantMap _state)
transactionSequence.push_back(transactionSettings);
}
}
executeSequence(transactionSequence, balance);
executeSequence(transactionSequence, accounts);
}
void ClientModel::executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance)
void ClientModel::executeSequence(std::vector<TransactionSettings> const& _sequence, std::map<Secret, u256> const& _balances)
{
if (m_running)
BOOST_THROW_EXCEPTION(ExecutionStateException());
@ -211,7 +221,7 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
{
try
{
m_client->resetState(_balance);
m_client->resetState(_balances);
onStateReset();
for (TransactionSettings const& transaction: _sequence)
{
@ -357,13 +367,13 @@ void ClientModel::showDebugError(QString const& _error)
Address ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction)
{
Address newAddress = m_client->transact(m_client->userAccount().secret(), _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice);
Address newAddress = m_client->transact(_ctrTransaction.sender, _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice);
return newAddress;
}
void ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr)
{
m_client->transact(m_client->userAccount().secret(), _tr.value, _contract, _data, _tr.gas, _tr.gasPrice);
m_client->transact(_tr.sender, _tr.value, _contract, _data, _tr.gas, _tr.gasPrice);
}
RecordLogEntry* ClientModel::lastBlock() const

11
mix/ClientModel.h

@ -46,8 +46,8 @@ class QVariableDefinition;
struct TransactionSettings
{
TransactionSettings() {}
TransactionSettings(QString const& _contractId, QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice):
contractId(_contractId), functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {}
TransactionSettings(QString const& _contractId, QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice, Secret _sender):
contractId(_contractId), functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice), sender(_sender) {}
TransactionSettings(QString const& _stdContractName, QString const& _stdContractUrl):
contractId(_stdContractName), stdContractUrl(_stdContractUrl) {}
@ -65,6 +65,8 @@ struct TransactionSettings
QList<QVariableDefinition*> parameterValues;
/// Standard contract url
QString stdContractUrl;
/// Sender
Secret sender;
};
@ -142,8 +144,6 @@ public:
Q_INVOKABLE void mine();
public slots:
/// Run the contract constructor and show debugger window.
void debugDeployment();
/// Setup state, run transaction sequence, show debugger for the last transaction
/// @param _state JS object with state configuration
void setupState(QVariantMap _state);
@ -151,6 +151,7 @@ public slots:
Q_INVOKABLE void debugRecord(unsigned _index);
/// Show the debugger for an empty record
Q_INVOKABLE void emptyRecord();
Q_INVOKABLE QString newAddress();
private slots:
/// Update UI with machine states result. Display a modal dialog.
@ -191,7 +192,7 @@ signals:
private:
RecordLogEntry* lastBlock() const;
QVariantMap contractAddresses() const;
void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance);
void executeSequence(std::vector<TransactionSettings> const& _sequence, std::map<Secret, u256> const& _balances);
dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings());
void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
void onNewTransaction();

22
mix/MixClient.cpp

@ -40,7 +40,7 @@ namespace dev
namespace mix
{
const Secret c_userAccountSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074");
const Secret c_defaultUserAccountSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074");
const u256 c_mixGenesisDifficulty = (u256) 1 << 4;
class MixBlockChain: public dev::eth::BlockChain
@ -62,16 +62,18 @@ public:
};
MixClient::MixClient(std::string const& _dbPath):
m_userAccount(c_userAccountSecret), m_dbPath(_dbPath), m_minigThreads(0)
m_dbPath(_dbPath), m_minigThreads(0)
{
resetState(10000000 * ether);
std::map<Secret, u256> account;
account.insert(std::make_pair(c_defaultUserAccountSecret, 1000000 * ether));
resetState(account);
}
MixClient::~MixClient()
{
}
void MixClient::resetState(u256 _balance)
void MixClient::resetState(std::map<Secret, u256> _accounts)
{
WriteGuard l(x_state);
Guard fl(m_filterLock);
@ -81,12 +83,20 @@ void MixClient::resetState(u256 _balance)
m_stateDB = OverlayDB();
TrieDB<Address, MemoryDB> accountState(&m_stateDB);
accountState.init();
std::map<Address, Account> genesisState = { std::make_pair(KeyPair(c_userAccountSecret).address(), Account(_balance, Account::NormalCreation)) };
std::map<Address, Account> genesisState;
for (auto account: _accounts)
{
KeyPair a = KeyPair(account.first);
m_userAccounts.push_back(a);
genesisState.insert(std::make_pair(a.address(), Account(account.second, Account::NormalCreation)));
}
dev::eth::commit(genesisState, static_cast<MemoryDB&>(m_stateDB), accountState);
h256 stateRoot = accountState.root();
m_bc.reset();
m_bc.reset(new MixBlockChain(m_dbPath, stateRoot));
m_state = eth::State(m_userAccount.address(), m_stateDB, BaseState::Empty);
m_state = eth::State(genesisState.begin()->first , m_stateDB, BaseState::Empty);
m_state.sync(bc());
m_startState = m_state;
m_executions.clear();

6
mix/MixClient.h

@ -42,8 +42,7 @@ public:
MixClient(std::string const& _dbPath);
virtual ~MixClient();
/// Reset state to the empty state with given balance.
void resetState(u256 _balance);
KeyPair const& userAccount() const { return m_userAccount; }
void resetState(std::map<Secret, u256> _accounts);
void mine();
ExecutionResult const& lastExecution() const;
ExecutionResults const& executions() const;
@ -91,6 +90,7 @@ public:
bool submitNonce(h256 const&) override { return false; }
/// @returns the last mined block information
eth::BlockInfo blockInfo() const;
std::vector<KeyPair> userAccounts() { return m_userAccounts; }
private:
void executeTransaction(dev::eth::Transaction const& _t, eth::State& _state, bool _call);
@ -99,7 +99,7 @@ private:
MixBlockChain& bc() { return *m_bc; }
MixBlockChain const& bc() const { return *m_bc; }
KeyPair m_userAccount;
std::vector<KeyPair> m_userAccounts;
eth::State m_state;
eth::State m_startState;
OverlayDB m_stateDB;

2
mix/qml/DeploymentDialog.qml

@ -267,7 +267,7 @@ Window {
MessageDialog {
id: errorDialog
standardButtons: StandardButton.Ok
icon: StandardIcon.Error
icon: StandardIcon.Critical
}
RowLayout

4
mix/qml/Ether.qml

@ -20,7 +20,7 @@ RowLayout {
function update()
{
if (value !== undefined)
if (value)
{
etherValueEdit.text = value.value;
selectUnit(value.unit);
@ -54,7 +54,7 @@ RowLayout {
id: units
onCurrentTextChanged:
{
if (value !== undefined)
if (value)
{
value.setUnit(currentText);
formattedValue.text = value.format();

151
mix/qml/StateDialog.qml

@ -1,5 +1,6 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import QtQuick.Controls.Styles 1.3
@ -12,24 +13,24 @@ Window {
id: modalStateDialog
modality: Qt.ApplicationModal
width: 520
width: 590
height: 480
title: qsTr("Edit State")
visible: false
color: StateDialogStyle.generic.backgroundColor
property alias stateTitle: titleField.text
property alias stateBalance: balanceField.value
property alias isDefault: defaultCheckBox.checked
property int stateIndex
property var stateTransactions: []
property var stateAccounts: []
signal accepted
function open(index, item, setDefault) {
stateIndex = index;
stateTitle = item.title;
balanceField.value = item.balance;
transactionsModel.clear();
stateTransactions = [];
var transactions = item.transactions;
for (var t = 0; t < transactions.length; t++) {
@ -37,6 +38,14 @@ Window {
stateTransactions.push(item.transactions[t]);
}
accountsModel.clear();
stateAccounts = [];
for (var k = 0; k < item.accounts.length; k++)
{
accountsModel.append(item.accounts[k]);
stateAccounts.push(item.accounts[k]);
}
modalStateDialog.setX((Screen.width - width) / 2);
modalStateDialog.setY((Screen.height - height) / 2);
@ -54,10 +63,11 @@ Window {
function getItem() {
var item = {
title: stateDialog.stateTitle,
balance: stateDialog.stateBalance,
transactions: []
transactions: [],
accounts: []
}
item.transactions = stateTransactions;
item.accounts = stateAccounts;
return item;
}
@ -90,15 +100,112 @@ Window {
RowLayout
{
Layout.fillWidth: true
DefaultLabel {
Rectangle
{
Layout.preferredWidth: 75
text: qsTr("Balance")
DefaultLabel {
id: accountsLabel
Layout.preferredWidth: 75
text: qsTr("Accounts")
}
Button
{
anchors.top: accountsLabel.bottom
anchors.topMargin: 10
iconSource: "qrc:/qml/img/plus.png"
action: newAccountAction
}
Action {
id: newAccountAction
tooltip: qsTr("Add new Account")
onTriggered:
{
var account = stateListModel.newAccount("1000000", QEther.Ether);
stateAccounts.push(account);
accountsModel.append(account);
}
}
}
Ether {
id: balanceField
edit: true
displayFormattedValue: true
MessageDialog
{
id: alertAlreadyUsed
text: qsTr("This account is in use. You cannot remove it. The first account is used to deploy config contract and cannot be removed.")
icon: StandardIcon.Warning
standardButtons: StandardButton.Ok
}
TableView
{
id: accountsView
Layout.fillWidth: true
model: accountsModel
headerVisible: false
TableViewColumn {
role: "name"
title: qsTr("Name")
width: 150
delegate: Item {
RowLayout
{
height: 25
width: parent.width
Button
{
iconSource: "qrc:/qml/img/delete_sign.png"
action: deleteAccountAction
}
Action {
id: deleteAccountAction
tooltip: qsTr("Delete Account")
onTriggered:
{
if (transactionsModel.isUsed(stateAccounts[styleData.row].secret))
alertAlreadyUsed.open();
else
{
stateAccounts.splice(styleData.row, 1);
accountsModel.remove(styleData.row);
}
}
}
DefaultTextField {
anchors.verticalCenter: parent.verticalCenter
onTextChanged: {
if (styleData.row > -1)
stateAccounts[styleData.row].name = text;
}
text: {
return styleData.value
}
}
}
}
}
TableViewColumn {
role: "balance"
title: qsTr("Balance")
width: 200
delegate: Item {
Ether {
id: balanceField
edit: true
displayFormattedValue: false
value: styleData.value
}
}
}
rowDelegate:
Rectangle {
color: styleData.alternate ? "transparent" : "#f0f0f0"
height: 30;
}
}
}
@ -196,10 +303,21 @@ Window {
}
}
ListModel {
id: accountsModel
function removeAccount(_i)
{
accountsModel.remove(_i);
stateAccounts.splice(_i, 1);
}
}
ListModel {
id: transactionsModel
function editTransaction(index) {
transactionDialog.stateAccounts = stateAccounts;
transactionDialog.open(index, transactionsModel.get(index));
}
@ -209,6 +327,7 @@ Window {
// https://bugreports.qt-project.org/browse/QTBUG-41327
// Second call to signal handler would just edit the item that was just created, no harm done
var item = TransactionHelper.defaultTransaction();
transactionDialog.stateAccounts = stateAccounts;
transactionDialog.open(transactionsModel.count, item);
}
@ -216,6 +335,16 @@ Window {
stateTransactions.splice(index, 1);
transactionsModel.remove(index);
}
function isUsed(secret)
{
for (var i in stateTransactions)
{
if (stateTransactions[i].sender === secret)
return true;
}
return false;
}
}
Component {

59
mix/qml/StateListModel.qml

@ -11,25 +11,39 @@ Item {
property alias model: stateListModel
property var stateList: []
property string defaultAccount: "cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074" //support for old project
function fromPlainStateItem(s) {
if (!s.accounts)
s.accounts = [stateListModel.newAccount("1000000", QEther.Ether, defaultAccount)]; //support for old project
return {
title: s.title,
balance: QEtherHelper.createEther(s.balance.value, s.balance.unit),
transactions: s.transactions.map(fromPlainTransactionItem)
transactions: s.transactions.map(fromPlainTransactionItem),
accounts: s.accounts.map(fromPlainAccountItem)
};
}
function fromPlainAccountItem(t)
{
return {
name: t.name,
secret: t.secret,
balance: QEtherHelper.createEther(t.balance.value, t.balance.unit)
};
}
function fromPlainTransactionItem(t) {
if (!t.sender)
t.sender = defaultAccount; //support for old project
var r = {
contractId: t.contractId,
functionId: t.functionId,
url: t.url,
value: QEtherHelper.createEther(t.value.value, t.value.unit),
gas: QEtherHelper.createBigInt(t.gas.value), //t.gas,//QEtherHelper.createEther(t.gas.value, t.gas.unit),
gas: QEtherHelper.createBigInt(t.gas.value),
gasPrice: QEtherHelper.createEther(t.gasPrice.value, t.gasPrice.unit),
stdContract: t.stdContract,
parameters: {}
parameters: {},
sender: t.sender
};
var qType = [];
for (var key in t.parameters)
@ -65,8 +79,8 @@ Item {
function toPlainStateItem(s) {
return {
title: s.title,
balance: { value: s.balance.value, unit: s.balance.unit },
transactions: s.transactions.map(toPlainTransactionItem)
transactions: s.transactions.map(toPlainTransactionItem),
accounts: s.accounts.map(toPlainAccountItem)
};
}
@ -80,6 +94,18 @@ Item {
return '';
}
function toPlainAccountItem(t)
{
return {
name: t.name,
secret: t.secret,
balance: {
value: t.balance.value,
unit: t.balance.unit
}
};
}
function toPlainTransactionItem(t) {
var r = {
contractId: t.contractId,
@ -152,7 +178,6 @@ Item {
ListModel {
id: stateListModel
property int defaultStateIndex: 0
signal defaultStateChanged;
signal stateListModelReady;
@ -167,15 +192,23 @@ Item {
};
}
function newAccount(_balance, _unit, _secret)
{
if (!_secret)
_secret = clientModel.newAddress();
var name = qsTr("Account") + "-" + _secret.substring(0, 4);
return { name: name, secret: _secret, balance: QEtherHelper.createEther(_balance, _unit) };
}
function createDefaultState() {
//var ether = QEtherHelper.createEther("100000000000000000000000000", QEther.Wei);
var ether = QEtherHelper.createEther("1000000", QEther.Ether);
var item = {
title: "",
balance: ether,
transactions: []
transactions: [],
accounts: []
};
item.accounts.push(newAccount("1000000", QEther.Ether, defaultAccount));
//add all stdc contracts
for (var i = 0; i < contractLibrary.model.count; i++) {
var contractTransaction = defaultTransactionItem();
@ -184,6 +217,7 @@ Item {
contractTransaction.contractId = contractItem.name;
contractTransaction.functionId = contractItem.name;
contractTransaction.stdContract = true;
contractTransaction.sender = item.accounts[0].secret; // default account is used to deploy std contract.
item.transactions.push(contractTransaction);
};
@ -192,6 +226,7 @@ Item {
var ctorTr = defaultTransactionItem();
ctorTr.functionId = c;
ctorTr.contractId = c;
ctorTr.sender = item.accounts[0].secret;
item.transactions.push(ctorTr);
}
return item;

2
mix/qml/StatusPane.qml

@ -164,8 +164,6 @@ Rectangle {
RowLayout
{
anchors.fill: parent
anchors.top: statusHeader.top
anchors.right: statusHeader.right
Rectangle
{
color: "transparent"

33
mix/qml/TransactionDialog.qml

@ -25,6 +25,7 @@ Window {
property var itemParams;
property bool useTransactionDefaultValue: false
property var qType;
property alias stateAccounts: senderComboBox.model
signal accepted;
@ -44,6 +45,8 @@ Window {
rowFunction.visible = true;
itemParams = item.parameters !== undefined ? item.parameters : {};
if (item.sender)
senderComboBox.select(item.sender);
contractsModel.clear();
var contractIndex = -1;
@ -190,6 +193,7 @@ Window {
item.functionId = transactionDialog.functionId;
}
item.sender = senderComboBox.model[senderComboBox.currentIndex].secret;
var orderedQType = [];
for (var p = 0; p < transactionDialog.transactionParams.count; p++) {
var parameter = transactionDialog.transactionParams.get(p);
@ -210,6 +214,35 @@ Window {
id: dialogContent
anchors.top: parent.top
spacing: 10
RowLayout
{
id: rowSender
Layout.fillWidth: true
height: 150
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Sender")
}
ComboBox {
function select(secret)
{
for (var i in model)
if (model[i].secret === secret)
{
currentIndex = i;
break;
}
}
id: senderComboBox
Layout.preferredWidth: 350
currentIndex: 0
textRole: "name"
editable: false
}
}
RowLayout
{
id: rowContract

Loading…
Cancel
Save