diff --git a/README.md b/README.md
index 790ee541e1..186c979bc9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
Ethereum
========
-[](https://travis-ci.org/ethereum/go-ethereum)
+Master [](http://cpt-obvious.ethercasts.com:8010/builders/go-ethereum-master-docker/builds/-1) Develop [](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 }