diff --git a/README.md b/README.md index 790ee541e1..186c979bc9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ Ethereum ======== -[![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) +Master [![Build +Status](http://cpt-obvious.ethercasts.com:8010/buildstatusimage?builder=go-ethereum-master-docker)](http://cpt-obvious.ethercasts.com:8010/builders/go-ethereum-master-docker/builds/-1) Develop [![Build +Status](http://cpt-obvious.ethercasts.com:8010/buildstatusimage?builder=go-ethereum-develop-docker)](http://cpt-obvious.ethercasts.com:8010/builders/go-ethereum-develop-docker/builds/-1) Ethereum Go Client © 2014 Jeffrey Wilcke. diff --git a/ethereal/assets/ext/home.html b/ethereal/assets/ext/home.html new file mode 100644 index 0000000000..86a659d65f --- /dev/null +++ b/ethereal/assets/ext/home.html @@ -0,0 +1,21 @@ + + + +Ethereum + + + + + +

... Ethereum ...

+ + + + diff --git a/ethereal/assets/ext/messaging.js b/ethereal/assets/ext/messaging.js new file mode 100644 index 0000000000..e7bc630202 --- /dev/null +++ b/ethereal/assets/ext/messaging.js @@ -0,0 +1,117 @@ +function handleMessage(message) { + console.log("[onMessageReceived]: ", message.data) + // TODO move to messaging.js + var data = JSON.parse(message.data) + + try { + switch(data.call) { + case "getCoinBase": + postData(data._seed, eth.getCoinBase()) + + break + case "getIsListening": + postData(data._seed, eth.getIsListening()) + + break + case "getIsMining": + postData(data._seed, eth.getIsMining()) + + break + case "getPeerCount": + postData(data._seed, eth.getPeerCount()) + + break + + case "getTxCountAt": + require(1) + postData(data._seed, eth.getTxCountAt(data.args[0])) + + break + case "getBlockByNumber": + var block = eth.getBlock(data.args[0]) + postData(data._seed, block) + + break + case "getBlockByHash": + var block = eth.getBlock(data.args[0]) + postData(data._seed, block) + + break + case "transact": + require(5) + + var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5]) + postData(data._seed, tx) + + break + case "create": + postData(data._seed, null) + + break + case "getStorage": + require(2); + + var stateObject = eth.getStateObject(data.args[0]) + var storage = stateObject.getStorage(data.args[1]) + postData(data._seed, storage) + + break + case "getStateKeyVals": + require(1); + var stateObject = eth.getStateObject(data.args[0]).stateKeyVal(true) + postData(data._seed,stateObject) + + break + case "getTransactionsFor": + require(1); + var txs = eth.getTransactionsFor(data.args[0], true) + postData(data._seed, txs) + + break + case "getBalance": + require(1); + + postData(data._seed, eth.getStateObject(data.args[0]).value()); + + break + case "getKey": + var key = eth.getKey().privateKey; + + postData(data._seed, key) + break + case "watch": + require(1) + eth.watch(data.args[0], data.args[1]); + break + case "disconnect": + require(1) + postData(data._seed, null) + break; + case "set": + console.log("'Set' has been depcrecated") + /* + for(var key in data.args) { + if(webview.hasOwnProperty(key)) { + window[key] = data.args[key]; + } + } + */ + break; + case "getSecretToAddress": + require(1) + postData(data._seed, eth.secretToAddress(data.args[0])) + break; + case "debug": + console.log(data.args[0]); + break; + } + } catch(e) { + console.log(data.call + ": " + e) + + postData(data._seed, null); + } +} + +function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) +} diff --git a/ethereal/assets/qml/views/chain.qml b/ethereal/assets/qml/views/chain.qml new file mode 100644 index 0000000000..2b968d56cf --- /dev/null +++ b/ethereal/assets/qml/views/chain.qml @@ -0,0 +1,187 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var title: "Network" + property var iconFile: "../net.png" + + objectName: "chainView" + visible: false + anchors.fill: parent + + TableView { + id: blockTable + width: parent.width + anchors.top: parent.top + anchors.bottom: parent.bottom + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + TableViewColumn{ role: "txAmount" ; title: "Tx amount" ; width: 100 } + + model: blockModel + + onDoubleClicked: { + popup.visible = true + popup.setDetails(blockModel.get(row)) + } + } + + function addBlock(block, initial) { + var txs = JSON.parse(block.transactions); + var amount = 0 + if(initial == undefined){ + initial = false + } + + if(txs != null){ + amount = txs.length + } + + if(initial){ + blockModel.append({number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) + } else { + blockModel.insert(0, {number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) + } + } + + Window { + id: popup + visible: false + //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint + property var block + width: root.width + height: 300 + Component{ + id: blockDetailsDelegate + Rectangle { + color: "#252525" + width: popup.width + height: 150 + Column { + anchors.leftMargin: 10 + anchors.topMargin: 5 + anchors.top: parent.top + anchors.left: parent.left + Text { text: '

Block details

'; color: "#F2F2F2"} + Text { text: 'Block number: ' + number; color: "#F2F2F2"} + Text { text: 'Hash: ' + hash; color: "#F2F2F2"} + Text { text: 'Coinbase: <' + name + '> ' + coinbase; color: "#F2F2F2"} + Text { text: 'Block found at: ' + prettyTime; color: "#F2F2F2"} + Text { text: 'Gas used: ' + gasUsed + " / " + gasLimit; color: "#F2F2F2"} + } + } + } + ListView { + model: singleBlock + delegate: blockDetailsDelegate + anchors.top: parent.top + height: 100 + anchors.leftMargin: 20 + id: listViewThing + Layout.maximumHeight: 40 + } + TableView { + id: txView + anchors.top: listViewThing.bottom + anchors.topMargin: 50 + width: parent.width + + TableViewColumn{width: 90; role: "value" ; title: "Value" } + TableViewColumn{width: 200; role: "hash" ; title: "Hash" } + TableViewColumn{width: 200; role: "sender" ; title: "Sender" } + TableViewColumn{width: 200;role: "address" ; title: "Receiver" } + TableViewColumn{width: 60; role: "gas" ; title: "Gas" } + TableViewColumn{width: 60; role: "gasPrice" ; title: "Gas Price" } + TableViewColumn{width: 60; role: "isContract" ; title: "Contract" } + + model: transactionModel + onClicked: { + var tx = transactionModel.get(row) + if(tx.data) { + popup.showContractData(tx) + }else{ + popup.height = 440 + } + } + } + + function showContractData(tx) { + txDetailsDebugButton.tx = tx + if(tx.createsContract) { + contractData.text = tx.data + contractLabel.text = "

Transaction created contract " + tx.address + "

" + }else{ + contractLabel.text = "

Transaction ran contract " + tx.address + "

" + contractData.text = tx.rawData + } + popup.height = 540 + } + + Rectangle { + id: txDetails + width: popup.width + height: 300 + anchors.left: listViewThing.left + anchors.top: txView.bottom + Label { + text: "

Contract data

" + anchors.top: parent.top + anchors.left: parent.left + id: contractLabel + anchors.leftMargin: 10 + } + Button { + property var tx + id: txDetailsDebugButton + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.top: parent.top + anchors.topMargin: 10 + text: "Debug contract" + onClicked: { + if(tx.createsContract){ + eth.startDbWithCode(tx.rawData) + }else { + eth.startDbWithContractAndData(tx.address, tx.rawData) + } + } + } + TextArea { + id: contractData + text: "Contract" + anchors.top: contractLabel.bottom + anchors.left: parent.left + anchors.bottom: popup.bottom + wrapMode: Text.Wrap + width: parent.width - 30 + height: 80 + anchors.leftMargin: 10 + } + } + property var transactionModel: ListModel { + id: transactionModel + } + property var singleBlock: ListModel { + id: singleBlock + } + function setDetails(block){ + singleBlock.set(0,block) + popup.height = 300 + transactionModel.clear() + if(block.txs != undefined){ + for(var i = 0; i < block.txs.count; ++i) { + transactionModel.insert(0, block.txs.get(i)) + } + if(block.txs.get(0).data){ + popup.showContractData(block.txs.get(0)) + } + } + txView.forceActiveFocus() + } + } +} diff --git a/ethereal/assets/qml/views/history.qml b/ethereal/assets/qml/views/history.qml new file mode 100644 index 0000000000..f50ae80041 --- /dev/null +++ b/ethereal/assets/qml/views/history.qml @@ -0,0 +1,50 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var iconFile: "../tx.png" + property var title: "Transactions" + + property var txModel: ListModel { + id: txModel + } + + id: historyView + anchors.fill: parent + objectName: "transactionView" + + TableView { + id: txTableView + anchors.fill: parent + TableViewColumn{ role: "inout" ; title: "" ; width: 40 } + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } + TableViewColumn{ role: "contract" ; title: "Contract" ; width: 100 } + + model: txModel + } + + function addTx(type, tx, inout) { + var isContract + if (tx.contract == true){ + isContract = "Yes" + }else{ + isContract = "No" + } + + + var address; + if(inout == "recv") { + address = tx.sender; + } else { + address = tx.address; + } + + txModel.insert(0, {inout: inout, hash: tx.hash, address: address, value: tx.value, contract: isContract}) + } +} diff --git a/ethereal/assets/qml/views/info.qml b/ethereal/assets/qml/views/info.qml new file mode 100644 index 0000000000..9e05e2f8e7 --- /dev/null +++ b/ethereal/assets/qml/views/info.qml @@ -0,0 +1,160 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var title: "Information" + property var iconFile: "../heart.png" + + objectName: "infoView" + visible: false + anchors.fill: parent + + color: "#00000000" + + Column { + spacing: 3 + anchors.fill: parent + anchors.topMargin: 5 + anchors.leftMargin: 5 + + Label { + id: addressLabel + text: "Address" + } + TextField { + text: pub.getKey().address + width: 500 + } + + Label { + text: "Client ID" + } + TextField { + text: gui.getCustomIdentifier() + width: 500 + placeholderText: "Anonymous" + onTextChanged: { + gui.setCustomIdentifier(text) + } + } + } + + property var addressModel: ListModel { + id: addressModel + } + TableView { + id: addressView + width: parent.width - 200 + height: 200 + anchors.bottom: logLayout.top + TableViewColumn{ role: "name"; title: "name" } + TableViewColumn{ role: "address"; title: "address"; width: 300} + + model: addressModel + } + + Rectangle { + anchors.top: addressView.top + anchors.left: addressView.right + anchors.leftMargin: 20 + + TextField { + placeholderText: "Name to register" + id: nameToReg + width: 150 + } + + Button { + anchors.top: nameToReg.bottom + text: "Register" + MouseArea{ + anchors.fill: parent + onClicked: { + gui.registerName(nameToReg.text) + nameToReg.text = "" + } + } + } + } + + property var logModel: ListModel { + id: logModel + } + RowLayout { + id: logLayout + width: parent.width + height: 200 + anchors.bottom: parent.bottom + TableView { + id: logView + headerVisible: false + anchors { + right: logLevelSlider.left + left: parent.left + bottom: parent.bottom + top: parent.top + } + + TableViewColumn{ role: "description" ; title: "log" } + + model: logModel + } + + Slider { + id: logLevelSlider + value: gui.getLogLevelInt() + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + + rightMargin: 5 + leftMargin: 5 + topMargin: 5 + bottomMargin: 5 + } + + orientation: Qt.Vertical + maximumValue: 5 + stepSize: 1 + + onValueChanged: { + gui.setLogLevel(value) + } + } + } + + function addDebugMessage(message){ + debuggerLog.append({value: message}) + } + + function addAddress(address) { + addressModel.append({name: address.name, address: address.address}) + } + + function clearAddress() { + addressModel.clear() + } + + function addLog(str) { + // Remove first item once we've reached max log items + if(logModel.count > 250) { + logModel.remove(0) + } + + if(str.len != 0) { + if(logView.flickableItem.atYEnd) { + logModel.append({description: str}) + logView.positionViewAtRow(logView.rowCount - 1, ListView.Contain) + } else { + logModel.append({description: str}) + } + } + + } +} diff --git a/ethereal/assets/qml/views/pending_tx.qml b/ethereal/assets/qml/views/pending_tx.qml new file mode 100644 index 0000000000..18572e3e2a --- /dev/null +++ b/ethereal/assets/qml/views/pending_tx.qml @@ -0,0 +1,44 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var title: "Pending Transactions" + property var iconFile: "../tx.png" + + objectName: "pendingTxView" + anchors.fill: parent + visible: false + id: pendingTxView + + property var pendingTxModel: ListModel { + id: pendingTxModel + } + + TableView { + id: pendingTxTableView + anchors.fill: parent + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "from" ; title: "sender" ; width: 230 } + TableViewColumn{ role: "to" ; title: "Reciever" ; width: 230 } + TableViewColumn{ role: "contract" ; title: "Contract" ; width: 100 } + + model: pendingTxModel + } + + function addTx(type, tx, inout) { + var isContract + if (tx.contract == true){ + isContract = "Yes" + }else{ + isContract = "No" + } + + + pendingTxModel.insert(0, {hash: tx.hash, to: tx.address, from: tx.sender, value: tx.value, contract: isContract}) + } +} diff --git a/ethereal/assets/qml/views/transaction.qml b/ethereal/assets/qml/views/transaction.qml new file mode 100644 index 0000000000..80e1670f81 --- /dev/null +++ b/ethereal/assets/qml/views/transaction.qml @@ -0,0 +1,214 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var iconFile: "../new.png" + property var title: "New transaction" + + objectName: "newTxView" + visible: false + anchors.fill: parent + color: "#00000000" + + Column { + id: mainContractColumn + anchors.fill: parent + + + states: [ + State{ + name: "ERROR" + + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: codeView; visible:true} + }, + State { + name: "DONE" + + PropertyChanges { target: txValue; visible:false} + PropertyChanges { target: txGas; visible:false} + PropertyChanges { target: txGasPrice; visible:false} + PropertyChanges { target: codeView; visible:false} + PropertyChanges { target: txButton; visible:false} + PropertyChanges { target: txDataLabel; visible:false} + PropertyChanges { target: atLabel; visible:false} + PropertyChanges { target: txFuelRecipient; visible:false} + PropertyChanges { target: valueDenom; visible:false} + PropertyChanges { target: gasDenom; visible:false} + + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: txOutput; visible:true} + PropertyChanges { target: newTxButton; visible:true} + }, + State { + name: "SETUP" + + PropertyChanges { target: txValue; visible:true; text: ""} + PropertyChanges { target: txGas; visible:true;} + PropertyChanges { target: txGasPrice; visible:true;} + PropertyChanges { target: codeView; visible:true; text: ""} + PropertyChanges { target: txButton; visible:true} + PropertyChanges { target: txDataLabel; visible:true} + PropertyChanges { target: valueDenom; visible:true} + PropertyChanges { target: gasDenom; visible:true} + + PropertyChanges { target: txResult; visible:false} + PropertyChanges { target: txOutput; visible:false} + PropertyChanges { target: newTxButton; visible:false} + } + ] + width: 400 + spacing: 5 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 5 + anchors.topMargin: 5 + + ListModel { + id: denomModel + ListElement { text: "Wei" ; zeros: "" } + ListElement { text: "Ada" ; zeros: "000" } + ListElement { text: "Babbage" ; zeros: "000000" } + ListElement { text: "Shannon" ; zeros: "000000000" } + ListElement { text: "Szabo" ; zeros: "000000000000" } + ListElement { text: "Finney" ; zeros: "000000000000000" } + ListElement { text: "Ether" ; zeros: "000000000000000000" } + ListElement { text: "Einstein" ;zeros: "000000000000000000000" } + ListElement { text: "Douglas" ; zeros: "000000000000000000000000000000000000000000" } + } + + + TextField { + id: txFuelRecipient + placeholderText: "Address / Name or empty for contract" + //validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } + width: 400 + } + + RowLayout { + TextField { + id: txValue + width: 222 + placeholderText: "Amount" + validator: RegExpValidator { regExp: /\d*/ } + onTextChanged: { + contractFormReady() + } + } + + ComboBox { + id: valueDenom + currentIndex: 6 + model: denomModel + } + } + + RowLayout { + TextField { + id: txGas + width: 50 + validator: RegExpValidator { regExp: /\d*/ } + placeholderText: "Gas" + text: "500" + } + Label { + id: atLabel + text: "@" + } + + TextField { + id: txGasPrice + width: 200 + placeholderText: "Gas price" + text: "10" + validator: RegExpValidator { regExp: /\d*/ } + } + + ComboBox { + id: gasDenom + currentIndex: 4 + model: denomModel + } + } + + Label { + id: txDataLabel + text: "Data" + } + + TextArea { + id: codeView + height: 300 + anchors.topMargin: 5 + width: 400 + onTextChanged: { + contractFormReady() + } + } + + + Button { + id: txButton + /* enabled: false */ + states: [ + State { + name: "READY" + PropertyChanges { target: txButton; /*enabled: true*/} + }, + State { + name: "NOTREADY" + PropertyChanges { target: txButton; /*enabled:false*/} + } + ] + text: "Send" + onClicked: { + var value = txValue.text + denomModel.get(valueDenom.currentIndex).zeros; + var gasPrice = txGasPrice.text + denomModel.get(gasDenom.currentIndex).zeros; + var res = gui.create(txFuelRecipient.text, value, txGas.text, gasPrice, codeView.text) + if(res[1]) { + txResult.text = "Your contract could not be sent over the network:\n" + txResult.text += res[1].error() + txResult.text += "" + mainContractColumn.state = "ERROR" + } else { + txResult.text = "Your transaction has been submitted:\n" + txOutput.text = res[0].address + mainContractColumn.state = "DONE" + } + } + } + Text { + id: txResult + visible: false + } + TextField { + id: txOutput + visible: false + width: 530 + } + Button { + id: newTxButton + visible: false + text: "Create a new transaction" + onClicked: { + this.visible = false + txResult.text = "" + txOutput.text = "" + mainContractColumn.state = "SETUP" + } + } + } + + function contractFormReady(){ + if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { + txButton.state = "READY" + }else{ + txButton.state = "NOTREADY" + } + } +} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index eef49824f1..3fc9a024c6 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -6,7 +6,6 @@ import QtQuick.Window 2.1; import QtQuick.Controls.Styles 1.1 import Ethereum 1.0 - ApplicationWindow { id: root @@ -18,13 +17,78 @@ ApplicationWindow { title: "Ethereal" + // Takes care of loading all default plugins + Component.onCompleted: { + var historyView = addPlugin("./views/history.qml", {title: "History"}) + var newTxView = addPlugin("./views/transaction.qml", {title: "New Transaction"}) + var chainView = addPlugin("./views/chain.qml", {title: "Block chain"}) + var infoView = addPlugin("./views/info.qml", {title: "Info"}) + var pendingTxView = addPlugin("./views/pending_tx.qml", {title: "Pending", canClose: true}) + + // Call the ready handler + gui.done() + } + + function addPlugin(path, options) { + var component = Qt.createComponent(path); + if(component.status != Component.Ready) { + if(component.status == Component.Error) { + console.debug("Error:"+ component.errorString()); + } + return + } + + return mainSplit.addComponent(component, options) + } + MenuBar { Menu { title: "File" MenuItem { text: "Import App" shortcut: "Ctrl+o" - onTriggered: openAppDialog.open() + onTriggered: { + generalFileDialog.callback = importApp; + generalFileDialog.open() + } + } + + MenuItem { + text: "Browser" + onTriggered: eth.openBrowser() + } + + MenuItem { + text: "Add plugin" + onTriggered: { + generalFileDialog.callback = function(path) { + addPlugin(path, {canClose: true}) + } + generalFileDialog.open() + } + } + + MenuSeparator {} + + MenuItem { + text: "Import key" + shortcut: "Ctrl+i" + onTriggered: { + generalFileDialog.callback = function(path) { + ui.importKey(path) + } + generalFileDialog.open() + } + } + + MenuItem { + text: "Export keys" + shortcut: "Ctrl+e" + onTriggered: { + generalFileDialog.callback = function(path) { + } + generalFileDialog.open() + } } } @@ -35,6 +99,23 @@ ApplicationWindow { shortcut: "Ctrl+d" onTriggered: ui.startDebugger() } + + MenuItem { + text: "Import Tx" + onTriggered: { + txImportDialog.visible = true + } + } + + MenuItem { + text: "Run JS file" + onTriggered: { + generalFileDialog.callback = function(path) { + eth.evalJavascriptFile(path) + } + generalFileDialog.open() + } + } } Menu { @@ -67,572 +148,325 @@ ApplicationWindow { } + statusBar: StatusBar { + height: 32 + RowLayout { + Button { + id: miningButton + text: "Start Mining" + onClicked: { + gui.toggleMining() + } + } + + Button { + id: importAppButton + text: "Browser" + onClicked: { + eth.openBrowser() + } + } + + RowLayout { + Label { + id: walletValueLabel + + font.pixelSize: 10 + styleColor: "#797979" + } + } + } + + Label { + y: 6 + id: lastBlockLabel + objectName: "lastBlockLabel" + visible: true + text: "" + font.pixelSize: 10 + anchors.right: peerGroup.left + anchors.rightMargin: 5 + } + + ProgressBar { + id: syncProgressIndicator + visible: false + objectName: "syncProgressIndicator" + y: 3 + width: 140 + indeterminate: true + anchors.right: peerGroup.left + anchors.rightMargin: 5 + } + + RowLayout { + id: peerGroup + y: 7 + anchors.right: parent.right + MouseArea { + onDoubleClicked: peerWindow.visible = true + anchors.fill: parent + } + + Label { + id: peerLabel + font.pixelSize: 8 + text: "0 / 0" + } + Image { + id: peerImage + width: 10; height: 10 + source: "../network.png" + } + } + } + property var blockModel: ListModel { id: blockModel } - function setView(view) { - networkView.visible = false - historyView.visible = false - newTxView.visible = false - infoView.visible = false - view.visible = true - //root.title = "Ethereal - " = view.title - } - SplitView { + property var views: []; + + id: mainSplit anchors.fill: parent resizing: false + function setView(view) { + for(var i = 0; i < views.length; i++) { + views[i].visible = false + } + + view.visible = true + } + + function addComponent(component, options) { + var view = mainView.createView(component, options) + if(!view.hasOwnProperty("iconFile")) { + console.log("Could not load plugin. Property 'iconFile' not found on view."); + return; + } + + menu.createMenuItem(view.iconFile, view, options); + mainSplit.views.push(view); + + return view + } + + /********************* + * Main menu. + ********************/ Rectangle { id: menu - Layout.minimumWidth: 80 - Layout.maximumWidth: 80 - anchors.bottom: parent.bottom + Layout.minimumWidth: 180 + Layout.maximumWidth: 180 anchors.top: parent.top - //color: "#D9DDE7" color: "#252525" - ColumnLayout { - y: 50 - anchors.left: parent.left - anchors.right: parent.right - height: 200 + Component { + id: menuItemTemplate Image { - source: "../tx.png" + property var view; anchors.horizontalCenter: parent.horizontalCenter MouseArea { anchors.fill: parent onClicked: { - setView(historyView) - } - } - } - Image { - source: "../new.png" - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(newTxView) - } - } - } - Image { - source: "../net.png" - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(networkView) - } - } - } - - Image { - source: "../heart.png" - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(infoView) + mainSplit.setView(view) } } } } + + /* + Component { + id: menuItemTemplate + Rectangle { + property var view; + property var source; + property alias title: title.text + height: 25 + + id: tab + + anchors { + left: parent.left + right: parent.right + } + + Label { + id: title + y: parent.height / 2 - this.height / 2 + x: 5 + font.pixelSize: 10 + } + + MouseArea { + anchors.fill: parent + onClicked: { + mainSplit.setView(view) + } + } + + Image { + id: closeButton + y: parent.height / 2 - this.height / 2 + visible: false + + source: "../close.png" + anchors { + right: parent.right + rightMargin: 5 + } + + MouseArea { + anchors.fill: parent + onClicked: { + console.log("should close") + } + } + } + } + } + */ + + + function createMenuItem(icon, view, options) { + if(options === undefined) { + options = {}; + } + + var comp = menuItemTemplate.createObject(menuColumn) + comp.view = view + comp.source = icon + /* + comp.title = options.title + if(options.canClose) { + //comp.closeButton.visible = options.canClose + } + */ + } + + ColumnLayout { + id: menuColumn + y: 50 + anchors.left: parent.left + anchors.right: parent.right + spacing: 10 + } } + /********************* + * Main view + ********************/ Rectangle { id: mainView color: "#00000000" + anchors.right: parent.right anchors.left: menu.right anchors.bottom: parent.bottom anchors.top: parent.top - property var txModel: ListModel { - id: txModel + function createView(component) { + var view = component.createObject(mainView) + + return view; } - - Rectangle { - id: historyView - anchors.fill: parent - - property var title: "Transactions" - TableView { - id: txTableView - anchors.fill: parent - TableViewColumn{ role: "inout" ; title: "" ; width: 40 } - TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } - TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } - TableViewColumn{ role: "contract" ; title: "Contract" ; width: 100 } - - model: txModel - } - } - - Rectangle { - id: newTxView - property var title: "New transaction" - visible: false - anchors.fill: parent - color: "#00000000" - /* - TabView{ - anchors.fill: parent - anchors.rightMargin: 5 - anchors.leftMargin: 5 - anchors.topMargin: 5 - anchors.bottomMargin: 5 - id: newTransactionTab - Component.onCompleted:{ - addTab("Simple send", newTransaction) - addTab("Contracts", newContract) - } - } - */ - Component.onCompleted: { - newContract.createObject(newTxView) - } - } - - Rectangle { - id: networkView - property var title: "Network" - visible: false - anchors.fill: parent - - TableView { - id: blockTable - width: parent.width - anchors.top: parent.top - anchors.bottom: parent.bottom - TableViewColumn{ role: "number" ; title: "#" ; width: 100 } - TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } - TableViewColumn{ role: "txAmount" ; title: "Tx amount" ; width: 100 } - - model: blockModel - - onDoubleClicked: { - popup.visible = true - popup.setDetails(blockModel.get(row)) - } - } - - } - - Rectangle { - id: infoView - property var title: "Information" - visible: false - color: "#00000000" - anchors.fill: parent - - Column { - spacing: 3 - anchors.fill: parent - anchors.topMargin: 5 - anchors.leftMargin: 5 - - Label { - id: addressLabel - text: "Address" - } - TextField { - text: pub.getKey().address - width: 500 - } - - Label { - text: "Client ID" - } - TextField { - text: eth.getCustomIdentifier() - width: 500 - placeholderText: "Anonymous" - onTextChanged: { - eth.setCustomIdentifier(text) - } - } - } - - property var addressModel: ListModel { - id: addressModel - } - TableView { - id: addressView - width: parent.width - 200 - height: 200 - anchors.bottom: logLayout.top - TableViewColumn{ role: "name"; title: "name" } - TableViewColumn{ role: "address"; title: "address"; width: 300} - - model: addressModel - } - - Rectangle { - anchors.top: addressView.top - anchors.left: addressView.right - anchors.leftMargin: 20 - - TextField { - placeholderText: "Name to register" - id: nameToReg - width: 150 - } - - Button { - anchors.top: nameToReg.bottom - text: "Register" - MouseArea{ - anchors.fill: parent - onClicked: { - eth.registerName(nameToReg.text) - nameToReg.text = "" - } - } - } - } - - - property var logModel: ListModel { - id: logModel - } - RowLayout { - id: logLayout - width: parent.width - height: 200 - anchors.bottom: parent.bottom - TableView { - id: logView - headerVisible: false - anchors { - right: logLevelSlider.left - left: parent.left - bottom: parent.bottom - top: parent.top - } - - TableViewColumn{ role: "description" ; title: "log" } - - model: logModel - } - - Slider { - id: logLevelSlider - value: eth.getLogLevelInt() - anchors { - right: parent.right - top: parent.top - bottom: parent.bottom - - rightMargin: 5 - leftMargin: 5 - topMargin: 5 - bottomMargin: 5 - } - - orientation: Qt.Vertical - maximumValue: 5 - stepSize: 1 - - onValueChanged: { - eth.setLogLevel(value) - } - } - } - } - - /* - signal addPlugin(string name) - Component { - id: pluginWindow - Rectangle { - anchors.fill: parent - Label { - id: pluginTitle - anchors.centerIn: parent - text: "Hello world" - } - Component.onCompleted: setView(this) - } - } - - onAddPlugin: { - var pluginWin = pluginWindow.createObject(mainView) - console.log(pluginWin) - pluginWin.pluginTitle.text = "Test" - } - */ } + + } + + /****************** + * Dialogs + *****************/ FileDialog { - id: openAppDialog - title: "Open QML Application" + id: generalFileDialog + property var callback; onAccepted: { - //ui.open(openAppDialog.fileUrl.toString()) - //ui.openHtml(Qt.resolvedUrl(ui.assetPath("test.html"))) - var path = openAppDialog.fileUrl.toString() - console.log(path) - var ext = path.split('.').pop() - console.log(ext) - if(ext == "html" || ext == "htm") { - ui.openHtml(path) - }else if(ext == "qml"){ - ui.openQml(path) - } + var path = this.fileUrl.toString() + callback.call(this, path) } } - statusBar: StatusBar { - height: 30 - RowLayout { - Button { - id: miningButton - onClicked: { - eth.toggleMining() - } - text: "Start Mining" - } - Button { - property var enabled: true - id: debuggerWindow - onClicked: { - ui.startDebugger() - } - text: "Debugger" - } - - Button { - id: importAppButton - anchors.left: debuggerWindow.right - anchors.leftMargin: 5 - onClicked: openAppDialog.open() - text: "Import App" - } - - Label { - anchors.left: importAppButton.right - anchors.leftMargin: 5 - id: walletValueLabel - } + /****************** + * Wallet functions + *****************/ + function importApp(path) { + var ext = path.split('.').pop() + if(ext == "html" || ext == "htm") { + ui.openHtml(path) + }else if(ext == "qml"){ + ui.openQml(path) } - - Label { - y: 6 - id: lastBlockLabel - objectName: "lastBlockLabel" - visible: true - text: "" - font.pixelSize: 10 - anchors.right: peerGroup.left - anchors.rightMargin: 5 - } - - ProgressBar { - id: syncProgressIndicator - visible: false - objectName: "syncProgressIndicator" - y: 3 - width: 140 - indeterminate: true - anchors.right: peerGroup.left - anchors.rightMargin: 5 - } - - RowLayout { - id: peerGroup - y: 7 - anchors.right: parent.right - MouseArea { - onDoubleClicked: peerWindow.visible = true - anchors.fill: parent - } - - Label { - id: peerLabel - font.pixelSize: 8 - text: "0 / 0" - } - Image { - id: peerImage - width: 10; height: 10 - source: "../network.png" - } - } } + function setWalletValue(value) { + walletValueLabel.text = value + } + + function loadPlugin(name) { + console.log("Loading plugin" + name) + mainView.addPlugin(name) + } + + function setPeers(text) { + peerLabel.text = text + } + + function addPeer(peer) { + // We could just append the whole peer object but it cries if you try to alter them + peerModel.append({ip: peer.ip, port: peer.port, lastResponse:timeAgo(peer.lastSend), latency: peer.latency, version: peer.version}) + } + + function resetPeers(){ + peerModel.clear() + } + + function timeAgo(unixTs){ + var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000 + return (lapsed + " seconds ago") + } + + function convertToPretty(unixTs){ + var a = new Date(unixTs*1000); + var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; + var year = a.getFullYear(); + var month = months[a.getMonth()]; + var date = a.getDate(); + var hour = a.getHours(); + var min = a.getMinutes(); + var sec = a.getSeconds(); + var time = date+' '+month+' '+year+' '+hour+':'+min+':'+sec ; + return time; + } + + /********************** + * Windows + *********************/ Window { - id: popup - visible: false - //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint - property var block - width: root.width - height: 300 - Component{ - id: blockDetailsDelegate - Rectangle { - color: "#252525" - width: popup.width - height: 150 - Column { - anchors.leftMargin: 10 - anchors.topMargin: 5 - anchors.top: parent.top - anchors.left: parent.left - Text { text: '

Block details

'; color: "#F2F2F2"} - Text { text: 'Block number: ' + number; color: "#F2F2F2"} - Text { text: 'Hash: ' + hash; color: "#F2F2F2"} - Text { text: 'Coinbase: <' + name + '> ' + coinbase; color: "#F2F2F2"} - Text { text: 'Block found at: ' + prettyTime; color: "#F2F2F2"} - Text { text: 'Gas used: ' + gasUsed + " / " + gasLimit; color: "#F2F2F2"} - } - } - } - ListView { - model: singleBlock - delegate: blockDetailsDelegate - anchors.top: parent.top - height: 100 - anchors.leftMargin: 20 - id: listViewThing - Layout.maximumHeight: 40 - } - TableView { - id: txView - anchors.top: listViewThing.bottom - anchors.topMargin: 50 - width: parent.width - - TableViewColumn{width: 90; role: "value" ; title: "Value" } - TableViewColumn{width: 200; role: "hash" ; title: "Hash" } - TableViewColumn{width: 200; role: "sender" ; title: "Sender" } - TableViewColumn{width: 200;role: "address" ; title: "Receiver" } - TableViewColumn{width: 60; role: "gas" ; title: "Gas" } - TableViewColumn{width: 60; role: "gasPrice" ; title: "Gas Price" } - TableViewColumn{width: 60; role: "isContract" ; title: "Contract" } - - model: transactionModel - onClicked: { - var tx = transactionModel.get(row) - if(tx.data) { - popup.showContractData(tx) - }else{ - popup.height = 440 - } - } - } - - function showContractData(tx) { - txDetailsDebugButton.tx = tx - if(tx.createsContract) { - contractData.text = tx.data - contractLabel.text = "

Transaction created contract " + tx.address + "

" - }else{ - contractLabel.text = "

Transaction ran contract " + tx.address + "

" - contractData.text = tx.rawData - } - popup.height = 540 - } - + id: peerWindow + //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint + height: 200 + width: 700 Rectangle { - id: txDetails - width: popup.width - height: 300 - anchors.left: listViewThing.left - anchors.top: txView.bottom - Label { - text: "

Contract data

" - anchors.top: parent.top - anchors.left: parent.left - id: contractLabel - anchors.leftMargin: 10 + anchors.fill: parent + property var peerModel: ListModel { + id: peerModel } - Button { - property var tx - id: txDetailsDebugButton - anchors.right: parent.right - anchors.rightMargin: 10 - anchors.top: parent.top - anchors.topMargin: 10 - text: "Debug contract" - onClicked: { - if(tx.createsContract){ - ui.startDbWithCode(tx.rawData) - }else { - ui.startDbWithContractAndData(tx.address, tx.rawData) - } - } + TableView { + anchors.fill: parent + id: peerTable + model: peerModel + TableViewColumn{width: 100; role: "ip" ; title: "IP" } + TableViewColumn{width: 60; role: "port" ; title: "Port" } + TableViewColumn{width: 140; role: "lastResponse"; title: "Last event" } + TableViewColumn{width: 100; role: "latency"; title: "Latency" } + TableViewColumn{width: 260; role: "version" ; title: "Version" } } - TextArea { - id: contractData - text: "Contract" - anchors.top: contractLabel.bottom - anchors.left: parent.left - anchors.bottom: popup.bottom - wrapMode: Text.Wrap - width: parent.width - 30 - height: 80 - anchors.leftMargin: 10 - } - } - property var transactionModel: ListModel { - id: transactionModel - } - property var singleBlock: ListModel { - id: singleBlock - } - function setDetails(block){ - singleBlock.set(0,block) - popup.height = 300 - transactionModel.clear() - if(block.txs != undefined){ - for(var i = 0; i < block.txs.count; ++i) { - transactionModel.insert(0, block.txs.get(i)) - } - if(block.txs.get(0).data){ - popup.showContractData(block.txs.get(0)) - } - } - txView.forceActiveFocus() - } - } - - Window { - id: addPeerWin - //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint - visible: false - minimumWidth: 230 - maximumWidth: 230 - maximumHeight: 50 - minimumHeight: 50 - - TextField { - id: addrField - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - placeholderText: "address:port" - onAccepted: { - ui.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Button { - anchors.left: addrField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Add" - onClicked: { - ui.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Component.onCompleted: { - addrField.focus = true } } @@ -660,444 +494,71 @@ ApplicationWindow { anchors.left: aboutIcon.right anchors.leftMargin: 10 font.pointSize: 12 - text: "

Ethereal


Development

Jeffrey Wilcke
Maran Hidskes
Viktor Trón
" + text: "

Ethereal - Adrastea


Development

Jeffrey Wilcke
Maran Hidskes
Viktor Trón
" } } - function addDebugMessage(message){ - debuggerLog.append({value: message}) - } - - function addAddress(address) { - addressModel.append({name: address.name, address: address.address}) - } - function clearAddress() { - addressModel.clear() - } - - function loadPlugin(name) { - console.log("Loading plugin" + name) - mainView.addPlugin(name) - } - - function setWalletValue(value) { - walletValueLabel.text = value - } - - function addTx(tx, inout) { - var isContract - if (tx.contract == true){ - isContract = "Yes" - }else{ - isContract = "No" - } - - var address; - if(inout == "recv") { - address = tx.sender; - } else { - address = tx.address; - } - txModel.insert(0, {inout: inout, hash: tx.hash, address: address, value: tx.value, contract: isContract}) - } - - function addBlock(block, initial) { - var txs = JSON.parse(block.transactions); - var amount = 0 - if(initial == undefined){ - initial = false - } - - if(txs != null){ - amount = txs.length - } - - if(initial){ - blockModel.append({number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) - }else{ - blockModel.insert(0, {number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) - } - } - - function addLog(str) { - // Remove first item once we've reached max log items - if(logModel.count > 250) { - logModel.remove(0) - } - - if(str.len != 0) { - if(logView.flickableItem.atYEnd) { - logModel.append({description: str}) - logView.positionViewAtRow(logView.rowCount - 1, ListView.Contain) - } else { - logModel.append({description: str}) - } - } - - } - - function setPeers(text) { - peerLabel.text = text - } - - function addPeer(peer) { - // We could just append the whole peer object but it cries if you try to alter them - peerModel.append({ip: peer.ip, port: peer.port, lastResponse:timeAgo(peer.lastSend), latency: peer.latency, version: peer.version}) - } - - function resetPeers(){ - peerModel.clear() - } - - function timeAgo(unixTs){ - var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000 - return (lapsed + " seconds ago") - } - function convertToPretty(unixTs){ - var a = new Date(unixTs*1000); - var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; - var year = a.getFullYear(); - var month = months[a.getMonth()]; - var date = a.getDate(); - var hour = a.getHours(); - var min = a.getMinutes(); - var sec = a.getSeconds(); - var time = date+' '+month+' '+year+' '+hour+':'+min+':'+sec ; - return time; - } - // ****************************************** - // Windows - // ****************************************** Window { - id: peerWindow - //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint - height: 200 - width: 700 - Rectangle { - anchors.fill: parent - property var peerModel: ListModel { - id: peerModel + id: txImportDialog + minimumWidth: 270 + maximumWidth: 270 + maximumHeight: 50 + minimumHeight: 50 + TextField { + id: txImportField + width: 170 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + onAccepted: { } - TableView { - anchors.fill: parent - id: peerTable - model: peerModel - TableViewColumn{width: 100; role: "ip" ; title: "IP" } - TableViewColumn{width: 60; role: "port" ; title: "Port" } - TableViewColumn{width: 140; role: "lastResponse"; title: "Last event" } - TableViewColumn{width: 100; role: "latency"; title: "Latency" } - TableViewColumn{width: 260; role: "version" ; title: "Version" } + } + Button { + anchors.left: txImportField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Import" + onClicked: { + eth.importTx(txImportField.text) + txImportField.visible = false } } + Component.onCompleted: { + addrField.focus = true + } } - // ******************************************* - // Components - // ******************************************* + Window { + id: addPeerWin + visible: false + minimumWidth: 230 + maximumWidth: 230 + maximumHeight: 50 + minimumHeight: 50 - // New Contract component - Component { - id: newContract - Column { - id: mainContractColumn - anchors.fill: parent - function contractFormReady(){ - if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { - txButton.state = "READY" - }else{ - txButton.state = "NOTREADY" - } - } - states: [ - State{ - name: "ERROR" - PropertyChanges { target: txResult; visible:true} - PropertyChanges { target: codeView; visible:true} - }, - State { - name: "DONE" - PropertyChanges { target: txValue; visible:false} - PropertyChanges { target: txGas; visible:false} - PropertyChanges { target: txGasPrice; visible:false} - PropertyChanges { target: codeView; visible:false} - PropertyChanges { target: txButton; visible:false} - PropertyChanges { target: txDataLabel; visible:false} - PropertyChanges { target: atLabel; visible:false} - PropertyChanges { target: txFuelRecipient; visible:false} - - PropertyChanges { target: txResult; visible:true} - PropertyChanges { target: txOutput; visible:true} - PropertyChanges { target: newTxButton; visible:true} - }, - State { - name: "SETUP" - PropertyChanges { target: txValue; visible:true; text: ""} - PropertyChanges { target: txGas; visible:true; text: ""} - PropertyChanges { target: txGasPrice; visible:true; text: ""} - PropertyChanges { target: codeView; visible:true; text: ""} - PropertyChanges { target: txButton; visible:true} - PropertyChanges { target: txDataLabel; visible:true} - - PropertyChanges { target: txResult; visible:false} - PropertyChanges { target: txOutput; visible:false} - PropertyChanges { target: newTxButton; visible:false} - } - ] - width: 400 - spacing: 5 + TextField { + id: addrField + anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: 5 - anchors.topMargin: 5 - - ListModel { - id: denomModel - ListElement { text: "Wei" ; zeros: "" } - ListElement { text: "Ada" ; zeros: "000" } - ListElement { text: "Babbage" ; zeros: "000000" } - ListElement { text: "Shannon" ; zeros: "000000000" } - ListElement { text: "Szabo" ; zeros: "000000000000" } - ListElement { text: "Finney" ; zeros: "000000000000000" } - ListElement { text: "Ether" ; zeros: "000000000000000000" } - ListElement { text: "Einstein" ;zeros: "000000000000000000000" } - ListElement { text: "Douglas" ; zeros: "000000000000000000000000000000000000000000" } - } - - - TextField { - id: txFuelRecipient - placeholderText: "Address / Name or empty for contract" - //validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } - width: 400 - } - - RowLayout { - TextField { - id: txValue - width: 222 - placeholderText: "Amount" - validator: RegExpValidator { regExp: /\d*/ } - onTextChanged: { - contractFormReady() - } - } - - ComboBox { - id: valueDenom - currentIndex: 6 - model: denomModel - } - } - - RowLayout { - TextField { - id: txGas - width: 50 - validator: RegExpValidator { regExp: /\d*/ } - placeholderText: "Gas" - text: "500" - /* - onTextChanged: { - contractFormReady() - } - */ - } - Label { - id: atLabel - text: "@" - } - - TextField { - id: txGasPrice - width: 200 - placeholderText: "Gas price" - text: "10" - validator: RegExpValidator { regExp: /\d*/ } - /* - onTextChanged: { - contractFormReady() - } - */ - } - - ComboBox { - id: gasDenom - currentIndex: 4 - model: denomModel - } - } - - Label { - id: txDataLabel - text: "Data" - } - - TextArea { - id: codeView - height: 300 - anchors.topMargin: 5 - width: 400 - onTextChanged: { - contractFormReady() - } - } - - - Button { - id: txButton - /* enabled: false */ - states: [ - State { - name: "READY" - PropertyChanges { target: txButton; /*enabled: true*/} - }, - State { - name: "NOTREADY" - PropertyChanges { target: txButton; /*enabled:false*/} - } - ] - text: "Send" - onClicked: { - var value = txValue.text + denomModel.get(valueDenom.currentIndex).zeros; - var gasPrice = txGasPrice.text + denomModel.get(gasDenom.currentIndex).zeros; - var res = eth.create(txFuelRecipient.text, value, txGas.text, gasPrice, codeView.text) - if(res[1]) { - txResult.text = "Your contract could not be send over the network:\n" - txResult.text += res[1].error() - txResult.text += "" - mainContractColumn.state = "ERROR" - } else { - txResult.text = "Your transaction has been submitted:\n" - txOutput.text = res[0].address - mainContractColumn.state = "DONE" - } - } - } - Text { - id: txResult - visible: false - } - TextField { - id: txOutput - visible: false - width: 530 - } - Button { - id: newTxButton - visible: false - text: "Create a new transaction" - onClicked: { - this.visible = false - txResult.text = "" - txOutput.text = "" - mainContractColumn.state = "SETUP" - } + anchors.leftMargin: 10 + placeholderText: "address:port" + onAccepted: { + eth.connectToPeer(addrField.text) + addPeerWin.visible = false } } - } - // New Transaction component - Component { - id: newTransaction - Column { - id: simpleSendColumn - states: [ - State{ - name: "ERROR" - }, - State { - name: "DONE" - PropertyChanges { target: txSimpleValue; visible:false} - PropertyChanges { target: txSimpleRecipient; visible:false} - PropertyChanges { target:newSimpleTxButton; visible:false} - - PropertyChanges { target: txSimpleResult; visible:true} - PropertyChanges { target: txSimpleOutput; visible:true} - PropertyChanges { target:newSimpleTxButton; visible:true} - }, - State { - name: "SETUP" - PropertyChanges { target: txSimpleValue; visible:true; text: ""} - PropertyChanges { target: txSimpleRecipient; visible:true; text: ""} - PropertyChanges { target: txSimpleButton; visible:true} - PropertyChanges { target:newSimpleTxButton; visible:false} - } - ] - spacing: 5 + Button { + anchors.left: addrField.right + anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 5 - anchors.topMargin: 5 - anchors.top: parent.top - anchors.left: parent.left - - function checkFormState(){ - if(txSimpleRecipient.text.length == 40 && txSimpleValue.text.length > 0) { - txSimpleButton.state = "READY" - }else{ - txSimpleButton.state = "NOTREADY" - } - } - - TextField { - id: txSimpleRecipient - placeholderText: "Recipient address" - Layout.fillWidth: true - //validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } - width: 530 - onTextChanged: { checkFormState() } - } - TextField { - id: txSimpleValue - width: 200 - placeholderText: "Amount" - anchors.rightMargin: 5 - validator: RegExpValidator { regExp: /\d*/ } - onTextChanged: { checkFormState() } - } - Button { - id: txSimpleButton - /*enabled: false*/ - states: [ - State { - name: "READY" - PropertyChanges { target: txSimpleButton; /*enabled: true*/} - }, - State { - name: "NOTREADY" - PropertyChanges { target: txSimpleButton; /*enabled: false*/} - } - ] - text: "Send" - onClicked: { - //this.enabled = false - var res = eth.transact(txSimpleRecipient.text, txSimpleValue.text, "500", "1000000", "") - if(res[1]) { - txSimpleResult.text = "There has been an error broadcasting your transaction:" + res[1].error() - } else { - txSimpleResult.text = "Your transaction has been broadcasted over the network.\nYour transaction id is:" - txSimpleOutput.text = res[0].hash - this.visible = false - simpleSendColumn.state = "DONE" - } - } - } - Text { - id: txSimpleResult - visible: false - - } - TextField { - id: txSimpleOutput - visible: false - width: 530 - } - Button { - id: newSimpleTxButton - visible: false - text: "Create an other transaction" - onClicked: { - this.visible = false - simpleSendColumn.state = "SETUP" - } + text: "Add" + onClicked: { + eth.connectToPeer(addrField.text) + addPeerWin.visible = false } } + Component.onCompleted: { + addrField.focus = true + } } } diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 5e4c035d8d..a848adf45d 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -21,19 +21,58 @@ ApplicationWindow { id: root anchors.fill: parent state: "inspectorShown" + TextField { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + id: uriNav + //text: webview.url + + Keys.onReturnPressed: { + var uri = this.text; + if(!/.*\:\/\/.*/.test(uri)) { + uri = "http://" + uri; + } + + var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/ + + if(reg.test(uri)) { + this.text.replace(reg, function(match, pre, domain, path) { + uri = pre; + + var lookup = eth.lookupDomain(domain.substring(0, domain.length - 4)); + var ip = []; + for(var i = 0, l = lookup.length; i < l; i++) { + ip.push(lookup.charCodeAt(i)) + } + + if(ip.length != 0) { + uri += lookup; + } else { + uri += domain; + } + + uri += path; + }); + } + + console.log("connecting to ...", uri) + + webview.url = uri; + } + } WebView { objectName: "webView" id: webview - anchors.fill: parent - /* - anchors { - left: parent.left - right: parent.right - bottom: sizeGrip.top - top: parent.top - } - */ + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + top: uriNav.bottom + } onTitleChanged: { window.title = title } experimental.preferences.javascriptEnabled: true experimental.preferences.navigatorQtObjectEnabled: true @@ -46,50 +85,50 @@ ApplicationWindow { try { switch(data.call) { - case "getCoinBase": - postData(data._seed, eth.getCoinBase()) + case "getCoinBase": + postData(data._seed, eth.getCoinBase()) - break - case "getIsListening": - postData(data._seed, eth.getIsListening()) + break + case "getIsListening": + postData(data._seed, eth.getIsListening()) - break - case "getIsMining": - postData(data._seed, eth.getIsMining()) + break + case "getIsMining": + postData(data._seed, eth.getIsMining()) - break - case "getPeerCount": - postData(data._seed, eth.getPeerCount()) + break + case "getPeerCount": + postData(data._seed, eth.getPeerCount()) - break + break - case "getTxCountAt": - require(1) - postData(data._seed, eth.getTxCountAt(data.args[0])) + case "getTxCountAt": + require(1) + postData(data._seed, eth.getTxCountAt(data.args[0])) - break - case "getBlockByNumber": + break + case "getBlockByNumber": var block = eth.getBlock(data.args[0]) postData(data._seed, block) break - case "getBlockByHash": + case "getBlockByHash": var block = eth.getBlock(data.args[0]) postData(data._seed, block) break - case "transact": + case "transact": require(5) var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5]) postData(data._seed, tx) break - case "create": + case "create": postData(data._seed, null) break - case "getStorage": + case "getStorage": require(2); var stateObject = eth.getStateObject(data.args[0]) @@ -97,52 +136,52 @@ ApplicationWindow { postData(data._seed, storage) break - case "getStateKeyVals": - require(1); - var stateObject = eth.getStateObject(data.args[0]).stateKeyVal(true) - postData(data._seed,stateObject) + case "getStateKeyVals": + require(1); + var stateObject = eth.getStateObject(data.args[0]).stateKeyVal(true) + postData(data._seed,stateObject) break - case "getTransactionsFor": - require(1); - var txs = eth.getTransactionsFor(data.args[0], true) - postData(data._seed, txs) + case "getTransactionsFor": + require(1); + var txs = eth.getTransactionsFor(data.args[0], true) + postData(data._seed, txs) - break - case "getBalance": + break + case "getBalance": require(1); postData(data._seed, eth.getStateObject(data.args[0]).value()); break - case "getKey": + case "getKey": var key = eth.getKey().privateKey; postData(data._seed, key) break - case "watch": + case "watch": require(1) eth.watch(data.args[0], data.args[1]); break - case "disconnect": + case "disconnect": require(1) postData(data._seed, null) break; - case "set": - console.log("'Set' has been depcrecated") - /* - for(var key in data.args) { - if(webview.hasOwnProperty(key)) { - window[key] = data.args[key]; - } - } - */ + case "set": + console.log("'Set' has been depcrecated") + /* + for(var key in data.args) { + if(webview.hasOwnProperty(key)) { + window[key] = data.args[key]; + } + } + */ break; - case "getSecretToAddress": + case "getSecretToAddress": require(1) postData(data._seed, eth.secretToAddress(data.args[0])) break; - case "debug": + case "debug": console.log(data.args[0]); break; } @@ -191,12 +230,12 @@ ApplicationWindow { inspector.visible = false }else{ inspector.visible = true - inspector.url = webview.experimental.remoteInspectorUrl + inspector.url = webview.experimental.remoteInspectorUrl } } onDoubleClicked: { - console.log('refreshing') - webView.reload() + console.log('refreshing') + webView.reload() } anchors.fill: parent } diff --git a/ethereal/debugger.go b/ethereal/debugger.go index 0963874057..1cf5e0b664 100644 --- a/ethereal/debugger.go +++ b/ethereal/debugger.go @@ -2,15 +2,16 @@ package main import ( "fmt" + "math/big" + "strconv" + "strings" + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethvm" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" - "math/big" - "strconv" - "strings" ) type DebuggerWindow struct { @@ -134,26 +135,13 @@ func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, data state := self.lib.eth.StateManager().TransState() account := self.lib.eth.StateManager().TransState().GetAccount(keyPair.Address()) contract := ethstate.NewStateObject([]byte{0}) - contract.Amount = value + contract.Balance = value self.SetAsm(script) - callerClosure := ethvm.NewClosure(account, contract, script, gas, gasPrice) - block := self.lib.eth.BlockChain().CurrentBlock - /* - vm := ethchain.NewVm(state, self.lib.eth.StateManager(), ethchain.RuntimeVars{ - Block: block, - Origin: account.Address(), - BlockNumber: block.Number, - PrevHash: block.PrevHash, - Coinbase: block.Coinbase, - Time: block.Time, - Diff: block.Difficulty, - Value: ethutil.Big(valueStr), - }) - */ + callerClosure := ethvm.NewClosure(account, contract, script, gas, gasPrice) env := utils.NewEnv(state, block, account.Address(), value) vm := ethvm.New(env) vm.Verbose = true diff --git a/ethereal/ext_app.go b/ethereal/ext_app.go index ac3e090f90..697630504f 100644 --- a/ethereal/ext_app.go +++ b/ethereal/ext_app.go @@ -1,9 +1,9 @@ package main import ( - "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethreact" "github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethutil" "github.com/go-qml/qml" @@ -25,8 +25,8 @@ type AppContainer interface { type ExtApplication struct { *ethpub.PEthereum - blockChan chan ethutil.React - changeChan chan ethutil.React + blockChan chan ethreact.Event + changeChan chan ethreact.Event quitChan chan bool watcherQuitChan chan bool @@ -38,8 +38,8 @@ type ExtApplication struct { func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication { app := &ExtApplication{ ethpub.NewPEthereum(lib.eth), - make(chan ethutil.React, 1), - make(chan ethutil.React, 1), + make(chan ethreact.Event, 100), + make(chan ethreact.Event, 100), make(chan bool), make(chan bool), container, @@ -58,8 +58,7 @@ func (app *ExtApplication) run() { err := app.container.Create() if err != nil { - fmt.Println(err) - + logger.Errorln(err) return } diff --git a/ethereal/gui.go b/ethereal/gui.go index df01cddda0..a4e3efb19a 100644 --- a/ethereal/gui.go +++ b/ethereal/gui.go @@ -3,20 +3,23 @@ package main import ( "bytes" "fmt" + "math/big" + "strconv" + "strings" + "time" + "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethlog" "github.com/ethereum/eth-go/ethminer" + "github.com/ethereum/eth-go/ethpipe" "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethreact" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethwire" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" - "math/big" - "strconv" - "strings" - "time" ) var logger = ethlog.NewLogger("GUI") @@ -27,6 +30,7 @@ type Gui struct { // QML Engine engine *qml.Engine component *qml.Common + qmlDone bool // The ethereum interface eth *eth.Ethereum @@ -74,12 +78,12 @@ func (gui *Gui) Start(assetPath string) { // Create a new QML engine gui.engine = qml.NewEngine() context := gui.engine.Context() + gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath) // Expose the eth library and the ui library to QML - context.SetVar("eth", gui) + context.SetVar("gui", gui) context.SetVar("pub", gui.pub) - gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath) - context.SetVar("ui", gui.uiLib) + context.SetVar("eth", gui.uiLib) // Load the main QML interface data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) @@ -102,11 +106,13 @@ func (gui *Gui) Start(assetPath string) { logger.Infoln("Starting GUI") gui.open = true win.Show() + // only add the gui logger after window is shown otherwise slider wont be shown if addlog { ethlog.AddLogSystem(gui) } win.Wait() + // need to silence gui logger after window closed otherwise logsystem hangs (but do not save loglevel) gui.logLevel = ethlog.Silence gui.open = false @@ -118,6 +124,9 @@ func (gui *Gui) Stop() { gui.open = false gui.win.Hide() } + + gui.uiLib.jsEngine.Stop() + logger.Infoln("Stopped") } @@ -141,18 +150,19 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { return nil, err } - win := gui.createWindow(component) + gui.win = gui.createWindow(component) - go func() { - gui.setInitialBlockChain() - gui.loadAddressBook() - gui.readPreviousTransactions() - gui.setPeerInfo() - }() + gui.update() - go gui.update() + return gui.win, nil +} - return win, nil +// The done handler will be called by QML when all views have been loaded +func (gui *Gui) Done() { + gui.qmlDone = true +} + +func (gui *Gui) ImportKey(filePath string) { } func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) { @@ -218,33 +228,69 @@ type address struct { } func (gui *Gui) loadAddressBook() { - gui.win.Root().Call("clearAddress") + view := gui.getObjectByName("infoView") + view.Call("clearAddress") nameReg := ethpub.EthereumConfig(gui.eth.StateManager()).NameReg() if nameReg != nil { nameReg.EachStorage(func(name string, value *ethutil.Value) { if name[0] != 0 { value.Decode() - gui.win.Root().Call("addAddress", struct{ Name, Address string }{name, ethutil.Bytes2Hex(value.Bytes())}) + + view.Call("addAddress", struct{ Name, Address string }{name, ethutil.Bytes2Hex(value.Bytes())}) } }) } } +func (gui *Gui) insertTransaction(window string, tx *ethchain.Transaction) { + nameReg := ethpipe.New(gui.eth).World().Config().Get("NameReg") + addr := gui.address() + + var inout string + if bytes.Compare(tx.Sender(), addr) == 0 { + inout = "send" + } else { + inout = "recv" + } + + var ( + ptx = ethpub.NewPTx(tx) + send = nameReg.Storage(tx.Sender()) + rec = nameReg.Storage(tx.Recipient) + s, r string + ) + + if tx.CreatesContract() { + rec = nameReg.Storage(tx.CreationAddress()) + } + + if send.Len() != 0 { + s = strings.Trim(send.Str(), "\x00") + } else { + s = ethutil.Bytes2Hex(tx.Sender()) + } + if rec.Len() != 0 { + r = strings.Trim(rec.Str(), "\x00") + } else { + if tx.CreatesContract() { + r = ethutil.Bytes2Hex(tx.CreationAddress()) + } else { + r = ethutil.Bytes2Hex(tx.Recipient) + } + } + ptx.Sender = s + ptx.Address = r + + gui.getObjectByName("transactionView").Call("addTx", window, ptx, inout) +} + func (gui *Gui) readPreviousTransactions() { it := gui.txDb.Db().NewIterator(nil, nil) - addr := gui.address() for it.Next() { tx := ethchain.NewTransactionFromBytes(it.Value()) - var inout string - if bytes.Compare(tx.Sender(), addr) == 0 { - inout = "send" - } else { - inout = "recv" - } - - gui.win.Root().Call("addTx", ethpub.NewPTx(tx), inout) + gui.insertTransaction("post", tx) } it.Release() @@ -255,7 +301,7 @@ func (gui *Gui) processBlock(block *ethchain.Block, initial bool) { b := ethpub.NewPBlock(block) b.Name = name - gui.win.Root().Call("addBlock", b, initial) + gui.getObjectByName("chainView").Call("addBlock", b, initial) } func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) { @@ -280,17 +326,111 @@ func (self *Gui) getObjectByName(objectName string) qml.Object { // Simple go routine function that updates the list of peers in the GUI func (gui *Gui) update() { - reactor := gui.eth.Reactor() + // We have to wait for qml to be done loading all the windows. + for !gui.qmlDone { + time.Sleep(500 * time.Millisecond) + } + + go func() { + go gui.setInitialBlockChain() + gui.loadAddressBook() + gui.setPeerInfo() + gui.readPreviousTransactions() + }() var ( - blockChan = make(chan ethutil.React, 1) - txChan = make(chan ethutil.React, 1) - objectChan = make(chan ethutil.React, 1) - peerChan = make(chan ethutil.React, 1) - chainSyncChan = make(chan ethutil.React, 1) - miningChan = make(chan ethutil.React, 1) + blockChan = make(chan ethreact.Event, 100) + txChan = make(chan ethreact.Event, 100) + objectChan = make(chan ethreact.Event, 100) + peerChan = make(chan ethreact.Event, 100) + chainSyncChan = make(chan ethreact.Event, 100) + miningChan = make(chan ethreact.Event, 100) ) + peerUpdateTicker := time.NewTicker(5 * time.Second) + generalUpdateTicker := time.NewTicker(1 * time.Second) + + state := gui.eth.StateManager().TransState() + + unconfirmedFunds := new(big.Int) + gui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(state.GetAccount(gui.address()).Balance))) + gui.getObjectByName("syncProgressIndicator").Set("visible", !gui.eth.IsUpToDate()) + + lastBlockLabel := gui.getObjectByName("lastBlockLabel") + + go func() { + for { + select { + case b := <-blockChan: + block := b.Resource.(*ethchain.Block) + gui.processBlock(block, false) + if bytes.Compare(block.Coinbase, gui.address()) == 0 { + gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.address()).Balance, nil) + } + case txMsg := <-txChan: + tx := txMsg.Resource.(*ethchain.Transaction) + + if txMsg.Name == "newTx:pre" { + object := state.GetAccount(gui.address()) + + if bytes.Compare(tx.Sender(), gui.address()) == 0 { + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) + } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { + unconfirmedFunds.Add(unconfirmedFunds, tx.Value) + } + + gui.setWalletValue(object.Balance, unconfirmedFunds) + + gui.insertTransaction("pre", tx) + } else { + object := state.GetAccount(gui.address()) + if bytes.Compare(tx.Sender(), gui.address()) == 0 { + object.SubAmount(tx.Value) + + gui.getObjectByName("transactionView").Call("addTx", "post", ethpub.NewPTx(tx), "send") + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { + object.AddAmount(tx.Value) + + gui.getObjectByName("transactionView").Call("addTx", "post", ethpub.NewPTx(tx), "recv") + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } + + gui.setWalletValue(object.Balance, nil) + + state.UpdateStateObject(object) + } + case msg := <-chainSyncChan: + sync := msg.Resource.(bool) + gui.win.Root().ObjectByName("syncProgressIndicator").Set("visible", sync) + + case <-objectChan: + gui.loadAddressBook() + case <-peerChan: + gui.setPeerInfo() + case <-peerUpdateTicker.C: + gui.setPeerInfo() + case msg := <-miningChan: + if msg.Name == "miner:start" { + gui.miner = msg.Resource.(*ethminer.Miner) + } else { + gui.miner = nil + } + case <-generalUpdateTicker.C: + statusText := "#" + gui.eth.BlockChain().CurrentBlock.Number.String() + if gui.miner != nil { + pow := gui.miner.GetPow() + if pow.GetHashrate() != 0 { + statusText = "Mining @ " + strconv.FormatInt(pow.GetHashrate(), 10) + "Khash - " + statusText + } + } + lastBlockLabel.Set("text", statusText) + } + } + }() + + reactor := gui.eth.Reactor() + reactor.Subscribe("newBlock", blockChan) reactor.Subscribe("newTx:pre", txChan) reactor.Subscribe("newTx:post", txChan) @@ -303,86 +443,6 @@ func (gui *Gui) update() { reactor.Subscribe("object:"+string(nameReg.Address()), objectChan) } reactor.Subscribe("peerList", peerChan) - - peerUpdateTicker := time.NewTicker(5 * time.Second) - generalUpdateTicker := time.NewTicker(1 * time.Second) - - state := gui.eth.StateManager().TransState() - - unconfirmedFunds := new(big.Int) - gui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(state.GetAccount(gui.address()).Amount))) - gui.getObjectByName("syncProgressIndicator").Set("visible", !gui.eth.IsUpToDate()) - - lastBlockLabel := gui.getObjectByName("lastBlockLabel") - - for { - select { - case b := <-blockChan: - block := b.Resource.(*ethchain.Block) - gui.processBlock(block, false) - if bytes.Compare(block.Coinbase, gui.address()) == 0 { - gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.address()).Amount, nil) - } - - case txMsg := <-txChan: - tx := txMsg.Resource.(*ethchain.Transaction) - - if txMsg.Event == "newTx:pre" { - object := state.GetAccount(gui.address()) - - if bytes.Compare(tx.Sender(), gui.address()) == 0 { - gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "send") - gui.txDb.Put(tx.Hash(), tx.RlpEncode()) - - unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) - } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { - gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "recv") - gui.txDb.Put(tx.Hash(), tx.RlpEncode()) - - unconfirmedFunds.Add(unconfirmedFunds, tx.Value) - } - - gui.setWalletValue(object.Amount, unconfirmedFunds) - } else { - object := state.GetAccount(gui.address()) - if bytes.Compare(tx.Sender(), gui.address()) == 0 { - object.SubAmount(tx.Value) - } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { - object.AddAmount(tx.Value) - } - - gui.setWalletValue(object.Amount, nil) - - state.UpdateStateObject(object) - } - case msg := <-chainSyncChan: - sync := msg.Resource.(bool) - gui.win.Root().ObjectByName("syncProgressIndicator").Set("visible", sync) - - case <-objectChan: - gui.loadAddressBook() - case <-peerChan: - gui.setPeerInfo() - case <-peerUpdateTicker.C: - gui.setPeerInfo() - case msg := <-miningChan: - if msg.Event == "miner:start" { - gui.miner = msg.Resource.(*ethminer.Miner) - } else { - gui.miner = nil - } - - case <-generalUpdateTicker.C: - statusText := "#" + gui.eth.BlockChain().CurrentBlock.Number.String() - if gui.miner != nil { - pow := gui.miner.GetPow() - if pow.GetHashrate() != 0 { - statusText = "Mining @ " + strconv.FormatInt(pow.GetHashrate(), 10) + "Khash - " + statusText - } - } - lastBlockLabel.Set("text", statusText) - } - } } func (gui *Gui) setPeerInfo() { @@ -453,7 +513,9 @@ func (gui *Gui) Printf(format string, v ...interface{}) { func (gui *Gui) printLog(s string) { str := strings.TrimRight(s, "\n") lines := strings.Split(str, "\n") + + view := gui.getObjectByName("infoView") for _, line := range lines { - gui.win.Root().Call("addLog", line) + view.Call("addLog", line) } } diff --git a/ethereal/html_container.go b/ethereal/html_container.go index b00d3f78e0..40a9f55842 100644 --- a/ethereal/html_container.go +++ b/ethereal/html_container.go @@ -2,17 +2,18 @@ package main import ( "errors" + "io/ioutil" + "net/url" + "os" + "path" + "path/filepath" + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethpub" "github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethutil" "github.com/go-qml/qml" "github.com/howeyc/fsnotify" - "io/ioutil" - "net/url" - "os" - "path" - "path/filepath" ) type HtmlApplication struct { @@ -41,7 +42,7 @@ func (app *HtmlApplication) Create() error { return errors.New("Ethereum package not yet supported") // TODO - ethutil.OpenPackage(app.path) + //ethutil.OpenPackage(app.path) } win := component.CreateWindow(nil) diff --git a/ethereal/main.go b/ethereal/main.go index 0f99be8865..04a04536d2 100644 --- a/ethereal/main.go +++ b/ethereal/main.go @@ -1,16 +1,17 @@ package main import ( + "os" + "runtime" + "github.com/ethereum/eth-go/ethlog" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" - "os" - "runtime" ) const ( ClientIdentifier = "Ethereal" - Version = "0.6.0" + Version = "0.6.1" ) func main() { diff --git a/ethereal/qml_container.go b/ethereal/qml_container.go index 1b420ee219..53ff13c2f3 100644 --- a/ethereal/qml_container.go +++ b/ethereal/qml_container.go @@ -25,7 +25,7 @@ func (app *QmlApplication) Create() error { path := string(app.path) // For some reason for windows we get /c:/path/to/something, windows doesn't like the first slash but is fine with the others so we are removing it - if string(app.path[0]) == "/" && runtime.GOOS == "windows" { + if app.path[0] == '/' && runtime.GOOS == "windows" { path = app.path[1:] } diff --git a/ethereal/ui_lib.go b/ethereal/ui_lib.go index 6a62fa1df2..b7cabf3a8e 100644 --- a/ethereal/ui_lib.go +++ b/ethereal/ui_lib.go @@ -1,10 +1,13 @@ package main import ( - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethutil" - "github.com/go-qml/qml" "path" + + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/javascript" + "github.com/go-qml/qml" ) type memAddr struct { @@ -22,10 +25,21 @@ type UiLib struct { win *qml.Window Db *Debugger DbWindow *DebuggerWindow + + jsEngine *javascript.JSRE } func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { - return &UiLib{engine: engine, eth: eth, assetPath: assetPath} + return &UiLib{engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(eth)} +} + +func (self *UiLib) ImportTx(rlpTx string) { + tx := ethchain.NewTransactionFromBytes(ethutil.Hex2Bytes(rlpTx)) + self.eth.TxPool().QueueTransaction(tx) +} + +func (self *UiLib) EvalJavascriptFile(path string) { + self.jsEngine.LoadExtFile(path[7:]) } func (ui *UiLib) OpenQml(path string) { @@ -42,6 +56,10 @@ func (ui *UiLib) OpenHtml(path string) { go app.run() } +func (ui *UiLib) OpenBrowser() { + ui.OpenHtml("file://" + ui.AssetPath("ext/home.html")) +} + func (ui *UiLib) Muted(content string) { component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml")) if err != nil { @@ -94,7 +112,6 @@ func (self *UiLib) StartDbWithCode(code string) { func (self *UiLib) StartDebugger() { dbWindow := NewDebuggerWindow(self) - //self.DbWindow = dbWindow dbWindow.Show() } diff --git a/ethereum/cmd.go b/ethereum/cmd.go index ff2b8409cf..5ddc916190 100644 --- a/ethereum/cmd.go +++ b/ethereum/cmd.go @@ -1,11 +1,13 @@ package main import ( - "github.com/ethereum/eth-go" - "github.com/ethereum/go-ethereum/ethereum/repl" - "github.com/ethereum/go-ethereum/utils" "io/ioutil" "os" + + "github.com/ethereum/eth-go" + "github.com/ethereum/go-ethereum/ethereum/repl" + "github.com/ethereum/go-ethereum/javascript" + "github.com/ethereum/go-ethereum/utils" ) func InitJsConsole(ethereum *eth.Ethereum) { @@ -25,7 +27,7 @@ func ExecJsFile(ethereum *eth.Ethereum, InputFile string) { if err != nil { logger.Fatalln(err) } - re := ethrepl.NewJSRE(ethereum) + re := javascript.NewJSRE(ethereum) utils.RegisterInterrupt(func(os.Signal) { re.Stop() }) diff --git a/ethereum/flags.go b/ethereum/flags.go index 4f59ddf060..5ed208411b 100644 --- a/ethereum/flags.go +++ b/ethereum/flags.go @@ -3,10 +3,11 @@ package main import ( "flag" "fmt" - "github.com/ethereum/eth-go/ethlog" "os" "os/user" "path" + + "github.com/ethereum/eth-go/ethlog" ) var Identifier string @@ -31,6 +32,9 @@ var LogFile string var ConfigFile string var DebugFile string var LogLevel int +var Dump bool +var DumpHash string +var DumpNumber int // flags specific to cli client var StartMining bool @@ -71,6 +75,10 @@ func Init() { flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0") flag.StringVar(&DiffType, "diff", "all", "sets the level of diff output [vm, all]. Has no effect if difftool=false") + flag.BoolVar(&Dump, "dump", false, "output the ethereum state in JSON format. Sub args [number, hash]") + flag.StringVar(&DumpHash, "hash", "", "specify arg in hex") + flag.IntVar(&DumpNumber, "number", -1, "specify arg in number") + flag.BoolVar(&StartMining, "mine", false, "start dagger mining") flag.BoolVar(&StartJsConsole, "js", false, "launches javascript console") diff --git a/ethereum/main.go b/ethereum/main.go index 2179910748..17838997c4 100644 --- a/ethereum/main.go +++ b/ethereum/main.go @@ -1,15 +1,19 @@ package main import ( + "fmt" + "os" + "runtime" + + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethlog" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" - "runtime" ) const ( ClientIdentifier = "Ethereum(G)" - Version = "0.6.0" + Version = "0.6.1" ) var logger = ethlog.NewLogger("CLI") @@ -23,7 +27,7 @@ func main() { Init() // parsing command line // If the difftool option is selected ignore all other log output - if DiffTool { + if DiffTool || Dump { LogLevel = 0 } @@ -46,6 +50,32 @@ func main() { ethereum := utils.NewEthereum(db, clientIdentity, keyManager, UseUPnP, OutboundPort, MaxPeer) + if Dump { + var block *ethchain.Block + + if len(DumpHash) == 0 && DumpNumber == -1 { + block = ethereum.BlockChain().CurrentBlock + } else if len(DumpHash) > 0 { + block = ethereum.BlockChain().GetBlock(ethutil.Hex2Bytes(DumpHash)) + } else { + block = ethereum.BlockChain().GetBlockByNumber(uint64(DumpNumber)) + } + + if block == nil { + fmt.Fprintln(os.Stderr, "block not found") + + // We want to output valid JSON + fmt.Println("{}") + + os.Exit(1) + } + + // Leave the Println. This needs clean output for piping + fmt.Println(block.State().Dump()) + + os.Exit(0) + } + if ShowGenesis { utils.ShowGenesis(ethereum) } diff --git a/ethereum/repl/repl.go b/ethereum/repl/repl.go index 92d4ad86a3..d08feb7b43 100644 --- a/ethereum/repl/repl.go +++ b/ethereum/repl/repl.go @@ -3,12 +3,14 @@ package ethrepl import ( "bufio" "fmt" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethlog" - "github.com/ethereum/eth-go/ethutil" "io" "os" "path" + + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethlog" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/javascript" ) var logger = ethlog.NewLogger("REPL") @@ -19,7 +21,7 @@ type Repl interface { } type JSRepl struct { - re *JSRE + re *javascript.JSRE prompt string @@ -34,7 +36,7 @@ func NewJSRepl(ethereum *eth.Ethereum) *JSRepl { panic(err) } - return &JSRepl{re: NewJSRE(ethereum), prompt: "> ", history: hist} + return &JSRepl{re: javascript.NewJSRE(ethereum), prompt: "> ", history: hist} } func (self *JSRepl) Start() { diff --git a/ethereum/repl/repl_darwin.go b/ethereum/repl/repl_darwin.go index 3a91b0d442..4c07280f70 100644 --- a/ethereum/repl/repl_darwin.go +++ b/ethereum/repl/repl_darwin.go @@ -115,8 +115,8 @@ L: } func (self *JSRepl) PrintValue(v interface{}) { - method, _ := self.re.vm.Get("prettyPrint") - v, err := self.re.vm.ToValue(v) + method, _ := self.re.Vm.Get("prettyPrint") + v, err := self.re.Vm.ToValue(v) if err == nil { method.Call(method, v) } diff --git a/ethereum/repl/javascript_runtime.go b/javascript/javascript_runtime.go similarity index 74% rename from ethereum/repl/javascript_runtime.go rename to javascript/javascript_runtime.go index 41b6216d49..158fc93cf4 100644 --- a/ethereum/repl/javascript_runtime.go +++ b/javascript/javascript_runtime.go @@ -1,30 +1,32 @@ -package ethrepl +package javascript import ( "fmt" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethlog" - "github.com/ethereum/eth-go/ethpub" - "github.com/ethereum/eth-go/ethstate" - "github.com/ethereum/eth-go/ethutil" - "github.com/ethereum/go-ethereum/utils" - "github.com/obscuren/otto" "io/ioutil" "os" "path" "path/filepath" + + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethlog" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethreact" + "github.com/ethereum/eth-go/ethstate" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" + "github.com/obscuren/otto" ) var jsrelogger = ethlog.NewLogger("JSRE") type JSRE struct { ethereum *eth.Ethereum - vm *otto.Otto + Vm *otto.Otto lib *ethpub.PEthereum - blockChan chan ethutil.React - changeChan chan ethutil.React + blockChan chan ethreact.Event + changeChan chan ethreact.Event quitChan chan bool objectCb map[string][]otto.Value @@ -33,9 +35,9 @@ type JSRE struct { func (jsre *JSRE) LoadExtFile(path string) { result, err := ioutil.ReadFile(path) if err == nil { - jsre.vm.Run(result) + jsre.Vm.Run(result) } else { - jsrelogger.Debugln("Could not load file:", path) + jsrelogger.Infoln("Could not load file:", path) } } @@ -49,14 +51,14 @@ func NewJSRE(ethereum *eth.Ethereum) *JSRE { ethereum, otto.New(), ethpub.NewPEthereum(ethereum), - make(chan ethutil.React, 1), - make(chan ethutil.React, 1), + make(chan ethreact.Event, 10), + make(chan ethreact.Event, 10), make(chan bool), make(map[string][]otto.Value), } // Init the JS lib - re.vm.Run(jsLib) + re.Vm.Run(jsLib) // Load extra javascript files re.LoadIntFile("string.js") @@ -65,7 +67,11 @@ func NewJSRE(ethereum *eth.Ethereum) *JSRE { // We have to make sure that, whoever calls this, calls "Stop" go re.mainLoop() - re.Bind("eth", &JSEthereum{re.lib, re.vm}) + // Subscribe to events + reactor := ethereum.Reactor() + reactor.Subscribe("newBlock", re.blockChan) + + re.Bind("eth", &JSEthereum{re.lib, re.Vm, ethereum}) re.initStdFuncs() @@ -75,11 +81,11 @@ func NewJSRE(ethereum *eth.Ethereum) *JSRE { } func (self *JSRE) Bind(name string, v interface{}) { - self.vm.Set(name, v) + self.Vm.Set(name, v) } func (self *JSRE) Run(code string) (otto.Value, error) { - return self.vm.Run(code) + return self.Vm.Run(code) } func (self *JSRE) Require(file string) error { @@ -109,10 +115,6 @@ func (self *JSRE) Stop() { } func (self *JSRE) mainLoop() { - // Subscribe to events - reactor := self.ethereum.Reactor() - reactor.Subscribe("newBlock", self.blockChan) - out: for { select { @@ -124,12 +126,12 @@ out: case object := <-self.changeChan: if stateObject, ok := object.Resource.(*ethstate.StateObject); ok { for _, cb := range self.objectCb[ethutil.Bytes2Hex(stateObject.Address())] { - val, _ := self.vm.ToValue(ethpub.NewPStateObject(stateObject)) + val, _ := self.Vm.ToValue(ethpub.NewPStateObject(stateObject)) cb.Call(cb, val) } } else if storageObject, ok := object.Resource.(*ethstate.StorageState); ok { for _, cb := range self.objectCb[ethutil.Bytes2Hex(storageObject.StateAddress)+ethutil.Bytes2Hex(storageObject.Address)] { - val, _ := self.vm.ToValue(ethpub.NewPStorageState(storageObject)) + val, _ := self.Vm.ToValue(ethpub.NewPStorageState(storageObject)) cb.Call(cb, val) } } @@ -138,7 +140,7 @@ out: } func (self *JSRE) initStdFuncs() { - t, _ := self.vm.Get("eth") + t, _ := self.Vm.Get("eth") eth := t.Object() eth.Set("watch", self.watch) eth.Set("addPeer", self.addPeer) @@ -146,19 +148,51 @@ func (self *JSRE) initStdFuncs() { eth.Set("stopMining", self.stopMining) eth.Set("startMining", self.startMining) eth.Set("execBlock", self.execBlock) + eth.Set("dump", self.dump) } /* * The following methods are natively implemented javascript functions */ +func (self *JSRE) dump(call otto.FunctionCall) otto.Value { + var state *ethstate.State + + if len(call.ArgumentList) > 0 { + var block *ethchain.Block + if call.Argument(0).IsNumber() { + num, _ := call.Argument(0).ToInteger() + block = self.ethereum.BlockChain().GetBlockByNumber(uint64(num)) + } else if call.Argument(0).IsString() { + hash, _ := call.Argument(0).ToString() + block = self.ethereum.BlockChain().GetBlock(ethutil.Hex2Bytes(hash)) + } else { + fmt.Println("invalid argument for dump. Either hex string or number") + } + + if block == nil { + fmt.Println("block not found") + + return otto.UndefinedValue() + } + + state = block.State() + } else { + state = self.ethereum.StateManager().CurrentState() + } + + v, _ := self.Vm.ToValue(state.Dump()) + + return v +} + func (self *JSRE) stopMining(call otto.FunctionCall) otto.Value { - v, _ := self.vm.ToValue(utils.StopMining(self.ethereum)) + v, _ := self.Vm.ToValue(utils.StopMining(self.ethereum)) return v } func (self *JSRE) startMining(call otto.FunctionCall) otto.Value { - v, _ := self.vm.ToValue(utils.StartMining(self.ethereum)) + v, _ := self.Vm.ToValue(utils.StartMining(self.ethereum)) return v } @@ -211,7 +245,7 @@ func (self *JSRE) require(call otto.FunctionCall) otto.Value { return otto.UndefinedValue() } - t, _ := self.vm.Get("exports") + t, _ := self.Vm.Get("exports") return t } diff --git a/ethereum/repl/js_lib.go b/javascript/js_lib.go similarity index 98% rename from ethereum/repl/js_lib.go rename to javascript/js_lib.go index c781c43d0c..a3e9b8a5b0 100644 --- a/ethereum/repl/js_lib.go +++ b/javascript/js_lib.go @@ -1,4 +1,4 @@ -package ethrepl +package javascript const jsLib = ` function pp(object) { diff --git a/ethereum/repl/types.go b/javascript/types.go similarity index 54% rename from ethereum/repl/types.go rename to javascript/types.go index 16a18e6e5d..f9d18b26ae 100644 --- a/ethereum/repl/types.go +++ b/javascript/types.go @@ -1,8 +1,12 @@ -package ethrepl +package javascript import ( "fmt" + + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethutil" "github.com/obscuren/otto" ) @@ -34,9 +38,37 @@ func (self *JSBlock) GetTransaction(hash string) otto.Value { return self.eth.toVal(self.PBlock.GetTransaction(hash)) } +type JSMessage struct { + To, From string + Input string + Output string + Path int + Origin string + Timestamp int32 + Coinbase string + Block string + Number int32 +} + +func NewJSMessage(message *ethstate.Message) JSMessage { + return JSMessage{ + To: ethutil.Bytes2Hex(message.To), + From: ethutil.Bytes2Hex(message.From), + Input: ethutil.Bytes2Hex(message.Input), + Output: ethutil.Bytes2Hex(message.Output), + Path: message.Path, + Origin: ethutil.Bytes2Hex(message.Origin), + Timestamp: int32(message.Timestamp), + Coinbase: ethutil.Bytes2Hex(message.Origin), + Block: ethutil.Bytes2Hex(message.Block), + Number: int32(message.Number.Int64()), + } +} + type JSEthereum struct { *ethpub.PEthereum - vm *otto.Otto + vm *otto.Otto + ethereum *eth.Ethereum } func (self *JSEthereum) GetBlock(hash string) otto.Value { @@ -93,3 +125,46 @@ func (self *JSEthereum) toVal(v interface{}) otto.Value { return result } + +func (self *JSEthereum) Messages(object map[string]interface{}) otto.Value { + filter := ethchain.NewFilter(self.ethereum) + + if object["earliest"] != nil { + earliest := object["earliest"] + if e, ok := earliest.(string); ok { + filter.SetEarliestBlock(ethutil.Hex2Bytes(e)) + } else { + filter.SetEarliestBlock(earliest) + } + } + if object["latest"] != nil { + latest := object["latest"] + if l, ok := latest.(string); ok { + filter.SetLatestBlock(ethutil.Hex2Bytes(l)) + } else { + filter.SetLatestBlock(latest) + } + } + if object["to"] != nil { + filter.SetTo(ethutil.Hex2Bytes(object["to"].(string))) + } + if object["from"] != nil { + filter.SetFrom(ethutil.Hex2Bytes(object["from"].(string))) + } + if object["max"] != nil { + filter.SetMax(object["max"].(int)) + } + if object["skip"] != nil { + filter.SetSkip(object["skip"].(int)) + } + + messages := filter.Find() + var msgs []JSMessage + for _, m := range messages { + msgs = append(msgs, NewJSMessage(m)) + } + + v, _ := self.vm.ToValue(msgs) + + return v +} diff --git a/utils/cmd.go b/utils/cmd.go index 5d0b3463c5..0eeb1049e1 100644 --- a/utils/cmd.go +++ b/utils/cmd.go @@ -127,6 +127,7 @@ func NewDatabase() ethutil.Database { } func NewClientIdentity(clientIdentifier, version, customIdentifier string) *ethwire.SimpleClientIdentity { + logger.Infoln("identity created") return ethwire.NewSimpleClientIdentity(clientIdentifier, version, customIdentifier) } @@ -243,21 +244,18 @@ func GetMiner() *ethminer.Miner { func StartMining(ethereum *eth.Ethereum) bool { if !ethereum.Mining { ethereum.Mining = true - addr := ethereum.KeyManager().Address() go func() { + logger.Infoln("Start mining") if miner == nil { miner = ethminer.NewDefaultMiner(addr, ethereum) } - // Give it some time to connect with peers time.Sleep(3 * time.Second) for !ethereum.IsUpToDate() { time.Sleep(5 * time.Second) } - - logger.Infoln("Miner started") miner.Start() }() RegisterInterrupt(func(os.Signal) { @@ -271,9 +269,7 @@ func StartMining(ethereum *eth.Ethereum) bool { func StopMining(ethereum *eth.Ethereum) bool { if ethereum.Mining && miner != nil { miner.Stop() - - logger.Infoln("Miner stopped") - + logger.Infoln("Stopped mining") ethereum.Mining = false return true diff --git a/utils/vm_env.go b/utils/vm_env.go index 2c40dd7b85..30568c421d 100644 --- a/utils/vm_env.go +++ b/utils/vm_env.go @@ -1,9 +1,10 @@ package utils import ( + "math/big" + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethstate" - "math/big" ) type VMEnv struct { @@ -29,5 +30,6 @@ func (self *VMEnv) PrevHash() []byte { return self.block.PrevHash } func (self *VMEnv) Coinbase() []byte { return self.block.Coinbase } func (self *VMEnv) Time() int64 { return self.block.Time } func (self *VMEnv) Difficulty() *big.Int { return self.block.Difficulty } +func (self *VMEnv) BlockHash() []byte { return self.block.Hash() } func (self *VMEnv) Value() *big.Int { return self.value } func (self *VMEnv) State() *ethstate.State { return self.state }