aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorobscuren <geffobscura@gmail.com>2014-08-21 21:43:45 +0800
committerobscuren <geffobscura@gmail.com>2014-08-21 21:43:45 +0800
commit1f59c37b894b1d446612d00be1c42dc4865094c3 (patch)
tree48e20ac6043a6f32f2892f4b6e18e31d8c733ae1
parentb3f25a6adeec5d07a168d608798fddfae44fef9c (diff)
parentbe9912fae218f12ed9087628be55c0898e161910 (diff)
downloadgo-tangerine-1f59c37b894b1d446612d00be1c42dc4865094c3.tar
go-tangerine-1f59c37b894b1d446612d00be1c42dc4865094c3.tar.gz
go-tangerine-1f59c37b894b1d446612d00be1c42dc4865094c3.tar.bz2
go-tangerine-1f59c37b894b1d446612d00be1c42dc4865094c3.tar.lz
go-tangerine-1f59c37b894b1d446612d00be1c42dc4865094c3.tar.xz
go-tangerine-1f59c37b894b1d446612d00be1c42dc4865094c3.tar.zst
go-tangerine-1f59c37b894b1d446612d00be1c42dc4865094c3.zip
Merge branch 'release/0.6.3'
-rw-r--r--LICENSE31
-rw-r--r--README.md6
-rw-r--r--ethereal/assets/back.pngbin0 -> 1004 bytes
-rw-r--r--ethereal/assets/bug.pngbin0 -> 1671 bytes
-rw-r--r--ethereal/assets/close.pngbin0 -> 905 bytes
-rw-r--r--ethereal/assets/ext/ethereum.js184
-rw-r--r--ethereal/assets/ext/filter.js31
-rw-r--r--ethereal/assets/ext/home.html22
-rw-r--r--ethereal/assets/ext/pre.js21
-rw-r--r--ethereal/assets/ext/test.html44
-rw-r--r--ethereal/assets/pick.pngbin0 -> 932 bytes
-rw-r--r--ethereal/assets/qml/views/chain.qml256
-rw-r--r--ethereal/assets/qml/views/history.qml52
-rw-r--r--ethereal/assets/qml/views/info.qml179
-rw-r--r--ethereal/assets/qml/views/javascript.qml45
-rw-r--r--ethereal/assets/qml/views/pending_tx.qml45
-rw-r--r--ethereal/assets/qml/views/transaction.qml215
-rw-r--r--ethereal/assets/qml/views/wallet.qml165
-rw-r--r--ethereal/assets/qml/wallet.qml1572
-rw-r--r--ethereal/assets/qml/webapp.qml292
-rw-r--r--ethereal/assets/wallet.pngbin0 -> 1114 bytes
-rw-r--r--ethereal/debugger.go35
-rw-r--r--ethereal/ext_app.go84
-rw-r--r--ethereal/gui.go413
-rw-r--r--ethereal/html_container.go38
-rw-r--r--ethereal/main.go45
-rw-r--r--ethereal/qml_container.go22
-rw-r--r--ethereal/ui_lib.go159
-rw-r--r--ethereum/cmd.go8
-rw-r--r--ethereum/flags.go10
-rw-r--r--ethereum/main.go36
-rw-r--r--ethereum/repl/repl.go12
-rw-r--r--ethereum/repl/repl_darwin.go4
-rw-r--r--ethereum/repl/types.go95
-rw-r--r--javascript/javascript_runtime.go (renamed from ethereum/repl/javascript_runtime.go)100
-rw-r--r--javascript/js_lib.go (renamed from ethereum/repl/js_lib.go)2
-rw-r--r--javascript/types.go138
-rw-r--r--utils/cmd.go48
-rw-r--r--utils/vm_env.go4
39 files changed, 2875 insertions, 1538 deletions
diff --git a/LICENSE b/LICENSE
index b77f7909a..78efdaabe 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,16 @@
-The MIT License (MIT)
+Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved.
-Copyright (c) 2013 Jeffrey Wilcke
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+You should have received a copy of the GNU General Public License
+along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+MA 02110-1301 USA
diff --git a/README.md b/README.md
index 790ee541e..da75e4d9e 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
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.
-Current state: Proof of Concept 0.6.0.
+Current state: Proof of Concept 0.6.3.
For the development package please see the [eth-go package](https://github.com/ethereum/eth-go).
diff --git a/ethereal/assets/back.png b/ethereal/assets/back.png
new file mode 100644
index 000000000..38fc84d6e
--- /dev/null
+++ b/ethereal/assets/back.png
Binary files differ
diff --git a/ethereal/assets/bug.png b/ethereal/assets/bug.png
new file mode 100644
index 000000000..f5e85dc99
--- /dev/null
+++ b/ethereal/assets/bug.png
Binary files differ
diff --git a/ethereal/assets/close.png b/ethereal/assets/close.png
new file mode 100644
index 000000000..88df442c5
--- /dev/null
+++ b/ethereal/assets/close.png
Binary files differ
diff --git a/ethereal/assets/ext/ethereum.js b/ethereal/assets/ext/ethereum.js
index de6fb0255..697a404a3 100644
--- a/ethereal/assets/ext/ethereum.js
+++ b/ethereal/assets/ext/ethereum.js
@@ -1,39 +1,124 @@
// Main Ethereum library
window.eth = {
prototype: Object(),
+ _callbacks: {},
+ _onCallbacks: {},
+
+ test: function() {
+ var t = undefined;
+ postData({call: "test"})
+ navigator.qt.onmessage = function(d) {console.log("onmessage called"); t = d; }
+ for(;;) {
+ if(t !== undefined) {
+ return t
+ }
+ }
+ },
+
+ mutan: function(code, cb) {
+ postData({call: "mutan", args: [code]}, cb)
+ },
+
+ toHex: function(str) {
+ var hex = "";
+ for(var i = 0; i < str.length; i++) {
+ var n = str.charCodeAt(i).toString(16);
+ hex += n.length < 2 ? '0' + n : n;
+ }
+
+ return hex;
+ },
+
+ toAscii: function(hex) {
+ // Find termination
+ var str = "";
+ var i = 0, l = hex.length;
+ for(; i < l; i+=2) {
+ var code = hex.charCodeAt(i)
+ if(code == 0) {
+ break;
+ }
+
+ str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+ }
+
+ return str;
+ },
+
+ fromAscii: function(str, pad) {
+ if(pad === undefined) {
+ pad = 32
+ }
+
+ var hex = this.toHex(str);
+
+ while(hex.length < pad*2)
+ hex += "00";
+
+ return hex
+ },
+
// Retrieve block
//
// Either supply a number or a string. Type is determent for the lookup method
// string - Retrieves the block by looking up the hash
// number - Retrieves the block by looking up the block number
- getBlock: function(numberOrHash, cb) {
- var func;
- if(typeof numberOrHash == "string") {
- func = "getBlockByHash";
- } else {
- func = "getBlockByNumber";
- }
- postData({call: func, args: [numberOrHash]}, cb);
- },
+ getBlock: function(numberOrHash, cb) {
+ var func;
+ if(typeof numberOrHash == "string") {
+ func = "getBlockByHash";
+ } else {
+ func = "getBlockByNumber";
+ }
+ postData({call: func, args: [numberOrHash]}, cb);
+ },
// Create transaction
//
// Transact between two state objects
- transact: function(sec, recipient, value, gas, gasPrice, data, cb) {
- postData({call: "transact", args: [sec, recipient, value, gas, gasPrice, data]}, cb);
+ transact: function(params, cb) {
+ if(params === undefined) {
+ params = {};
+ }
+
+ if(params.endowment !== undefined)
+ params.value = params.endowment;
+ if(params.code !== undefined)
+ params.data = params.code;
+
+ // Make sure everything is string
+ var fields = ["to", "from", "value", "gas", "gasPrice"];
+ for(var i = 0; i < fields.length; i++) {
+ if(params[fields[i]] === undefined) {
+ params[fields[i]] = "";
+ }
+ params[fields[i]] = params[fields[i]].toString();
+ }
+
+ var data;
+ if(typeof params.data === "object") {
+ data = "";
+ for(var i = 0; i < params.data.length; i++) {
+ data += params.data[i]
+ }
+ } else {
+ data = params.data;
+ }
+
+ postData({call: "transact", args: [params.from, params.to, params.value, params.gas, params.gasPrice, "0x"+data]}, cb);
},
- create: function(sec, value, gas, gasPrice, init, body, cb) {
- postData({call: "create", args: [sec, value, gas, gasPrice, init, body]}, cb);
+ getMessages: function(filter, cb) {
+ postData({call: "messages", args: [filter]}, cb);
},
getStorageAt: function(address, storageAddress, cb) {
postData({call: "getStorage", args: [address, storageAddress]}, cb);
},
- getStateKeyVals: function(address, cb){
- postData({call: "getStateKeyVals", args: [address]}, cb);
+ getEachStorageAt: function(address, cb){
+ postData({call: "getEachStorage", args: [address]}, cb);
},
getKey: function(cb) {
@@ -66,6 +151,7 @@ window.eth = {
postData({call: "getSecretToAddress", args: [sec]}, cb);
},
+ /*
watch: function(address, storageAddrOrCb, cb) {
var ev;
if(cb === undefined) {
@@ -95,6 +181,16 @@ window.eth = {
postData({call: "disconnect", args: [address, storageAddrOrCb]});
},
+ */
+
+ watch: function(options) {
+ var filter = new Filter(options);
+ filter.number = newWatchNum().toString()
+
+ postData({call: "watch", args: [options, filter.number]})
+
+ return filter;
+ },
set: function(props) {
postData({call: "set", args: props});
@@ -137,9 +233,63 @@ window.eth = {
}
}
},
+}
+
+
+var Filter = function(options) {
+ this.options = options;
+};
+Filter.prototype.changed = function(callback) {
+ // Register the watched:<number>. Qml will call the appropriate event if anything
+ // interesting happens in the land of Go.
+ eth.on("watched:"+this.number, callback)
+}
+Filter.prototype.getMessages = function(cb) {
+ return eth.getMessages(this.options, cb)
+}
+
+var watchNum = 0;
+function newWatchNum() {
+ return watchNum++;
+}
+
+function postData(data, cb) {
+ data._seed = Math.floor(Math.random() * 1000000)
+ if(cb) {
+ eth._callbacks[data._seed] = cb;
+ }
+ if(data.args === undefined) {
+ data.args = [];
+ }
+ navigator.qt.postMessage(JSON.stringify(data));
}
-window.eth._callbacks = {}
-window.eth._onCallbacks = {}
+
+navigator.qt.onmessage = function(ev) {
+ var data = JSON.parse(ev.data)
+
+ if(data._event !== undefined) {
+ eth.trigger(data._event, data.data);
+ } else {
+ if(data._seed) {
+ var cb = eth._callbacks[data._seed];
+ if(cb) {
+ cb.call(this, data.data)
+
+ // Remove the "trigger" callback
+ delete eth._callbacks[ev._seed];
+ }
+ }
+ }
+}
+
+eth.on("chain:changed", function() {
+})
+
+eth.on("messages", { /* filters */}, function(messages){
+})
+
+eth.on("pending:changed", function() {
+})
diff --git a/ethereal/assets/ext/filter.js b/ethereal/assets/ext/filter.js
new file mode 100644
index 000000000..5c1c03aad
--- /dev/null
+++ b/ethereal/assets/ext/filter.js
@@ -0,0 +1,31 @@
+var Filter = function(options) {
+ this.callbacks = {};
+ this.seed = Math.floor(Math.random() * 1000000);
+ this.options = options;
+
+ if(options == "chain") {
+ eth.registerFilterString(options, this.seed);
+ } else if(typeof options === "object") {
+ eth.registerFilter(options, this.seed);
+ }
+};
+
+Filter.prototype.changed = function(callback) {
+ var cbseed = Math.floor(Math.random() * 1000000);
+ eth.registerFilterCallback(this.seed, cbseed);
+
+ var self = this;
+ message.connect(function(messages, seed, callbackSeed) {
+ if(seed == self.seed && callbackSeed == cbseed) {
+ callback.call(self, messages);
+ }
+ });
+};
+
+Filter.prototype.uninstall = function() {
+ eth.uninstallFilter(this.seed)
+}
+
+Filter.prototype.messages = function() {
+ return JSON.parse(eth.messages(this.options))
+}
diff --git a/ethereal/assets/ext/home.html b/ethereal/assets/ext/home.html
new file mode 100644
index 000000000..a524e8403
--- /dev/null
+++ b/ethereal/assets/ext/home.html
@@ -0,0 +1,22 @@
+<!doctype>
+<html>
+<head>
+<title>Ethereum</title>
+
+<style type="text/css">
+h1 {
+ text-align: center;
+ font-family: Courier;
+ font-size: 50pt;
+}
+</style>
+</head>
+
+<body>
+<h1>... Ethereum ...</h1>
+<ul>
+ <li><a href="http://std.eth">std::Service</a></li>
+</ul>
+</body>
+</html>
+
diff --git a/ethereal/assets/ext/pre.js b/ethereal/assets/ext/pre.js
index ca520f152..3e8a534e9 100644
--- a/ethereal/assets/ext/pre.js
+++ b/ethereal/assets/ext/pre.js
@@ -1,18 +1,3 @@
-function debug(/**/) {
- var args = arguments;
- var msg = ""
- for(var i = 0; i < args.length; i++){
- if(typeof args[i] === "object") {
- msg += " " + JSON.stringify(args[i])
- } else {
- msg += " " + args[i]
- }
- }
-
- postData({call:"debug", args:[msg]})
- document.getElementById("debug").innerHTML += "<br>" + msg
-}
-
// Helper function for generating pseudo callbacks and sending data to the QML part of the application
function postData(data, cb) {
data._seed = Math.floor(Math.random() * 1000000)
@@ -50,9 +35,3 @@ navigator.qt.onmessage = function(ev) {
}
}
}
-
-window.onerror = function(message, file, lineNumber, column, errorObj) {
- debug(file, message, lineNumber+":"+column, errorObj);
-
- return false;
-}
diff --git a/ethereal/assets/ext/test.html b/ethereal/assets/ext/test.html
new file mode 100644
index 000000000..4bac7d36f
--- /dev/null
+++ b/ethereal/assets/ext/test.html
@@ -0,0 +1,44 @@
+<!doctype>
+<html>
+<head>
+<title>Tests</title>
+</head>
+
+<body>
+<button onclick="test();">Test me</button>
+
+<script type="text/javascript">
+function test() {
+ var filter = eth.watch({
+ latest: -1,
+ from: "e6716f9544a56c530d868e4bfbacb172315bdead",
+ altered: ["aabb", {id: "eeff", "at": "aabb"}],
+ });
+
+ filter.changed(function(messages) {
+ console.log("messages", messages)
+ })
+
+ filter.getMessages(function(messages) {
+ console.log("getMessages", messages)
+ });
+
+ eth.getEachStorageAt("9ef0f0d81e040012600b0c1abdef7c48f720f88a", function(entries) {
+ for(var i = 0; i < entries.length; i++) {
+ console.log(entries[i].key, " : ", entries[i].value)
+ }
+ })
+
+ eth.getBlock("f70097659f329a09642a27f11338d9269de64f1d4485786e36bfc410832148cd", function(block) {
+ console.log(block)
+ })
+
+ eth.mutan("var a = 10", function(code) {
+ console.log("code", code)
+ });
+}
+</script>
+
+</body>
+
+</html>
diff --git a/ethereal/assets/pick.png b/ethereal/assets/pick.png
new file mode 100644
index 000000000..2f5a261c2
--- /dev/null
+++ b/ethereal/assets/pick.png
Binary files differ
diff --git a/ethereal/assets/qml/views/chain.qml b/ethereal/assets/qml/views/chain.qml
new file mode 100644
index 000000000..9eaa49db1
--- /dev/null
+++ b/ethereal/assets/qml/views/chain.qml
@@ -0,0 +1,256 @@
+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 {
+ id: root
+ property var title: "Network"
+ property var iconSource: "../net.png"
+ property var secondary: "Hi"
+ property var menuItem
+
+ 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
+
+ itemDelegate: Item {
+ Text {
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: 10
+ verticalCenter: parent.verticalCenter
+ }
+ color: styleData.textColor
+ elide: styleData.elideMode
+ text: styleData.value
+ font.pixelSize: 11
+ MouseArea {
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ propagateComposedEvents: true
+ anchors.fill: parent
+ onClicked: {
+ blockTable.selection.clear()
+ blockTable.selection.select(styleData.row)
+
+ if(mouse.button == Qt.RightButton) {
+ contextMenu.row = styleData.row;
+ contextMenu.popup()
+ }
+ }
+
+ onDoubleClicked: {
+ popup.visible = true
+ popup.setDetails(blockModel.get(styleData.row))
+ }
+ }
+ }
+
+ }
+
+ Menu {
+ id: contextMenu
+ property var row;
+ MenuItem {
+ text: "Details"
+ onTriggered: {
+ popup.visible = true
+ popup.setDetails(blockModel.get(this.row))
+ }
+ }
+
+ MenuSeparator{}
+
+ MenuItem {
+ text: "Copy"
+ onTriggered: {
+ copyToClipboard(blockModel.get(this.row).hash)
+ }
+ }
+
+ MenuItem {
+ text: "Dump State"
+ onTriggered: {
+ generalFileDialog.show(false, function(path) {
+ var hash = blockModel.get(this.row).hash;
+
+ gui.dumpState(hash, path);
+ });
+ }
+ }
+ }
+ }
+
+
+
+ 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)})
+ }
+
+ //root.secondary.text = "#" + block.number;
+ }
+
+ 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: '<h3>Block details</h3>'; color: "#F2F2F2"}
+ Text { text: '<b>Block number:</b> ' + number; color: "#F2F2F2"}
+ Text { text: '<b>Hash:</b> ' + hash; color: "#F2F2F2"}
+ Text { text: '<b>Coinbase:</b> &lt;' + name + '&gt; ' + coinbase; color: "#F2F2F2"}
+ Text { text: '<b>Block found at:</b> ' + prettyTime; color: "#F2F2F2"}
+ Text { text: '<b>Gas used:</b> ' + 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 = "<h4> Transaction created contract " + tx.address + "</h4>"
+ }else{
+ contractLabel.text = "<h4> Transaction ran contract " + tx.address + "</h4>"
+ 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: "<h4>Contract data</h4>"
+ 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 000000000..9eee883e3
--- /dev/null
+++ b/ethereal/assets/qml/views/history.qml
@@ -0,0 +1,52 @@
+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 iconSource: "../tx.png"
+ property var title: "Transactions"
+ property var menuItem
+
+
+ id: historyView
+ visible: false
+ anchors.fill: parent
+ objectName: "transactionView"
+
+ property var txModel: ListModel {
+ id: txModel
+ }
+ 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(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 000000000..ca6ca077e
--- /dev/null
+++ b/ethereal/assets/qml/views/info.qml
@@ -0,0 +1,179 @@
+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 iconSource: "../heart.png"
+ property var menuItem
+
+ objectName: "infoView"
+ visible: false
+ anchors.fill: parent
+
+ color: "#00000000"
+
+ Column {
+ id: info
+ spacing: 3
+ anchors.fill: parent
+ anchors.topMargin: 5
+ anchors.leftMargin: 5
+
+ Label {
+ id: addressLabel
+ text: "Address"
+ }
+ TextField {
+ text: eth.key().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
+ height: 200
+ anchors.bottom: logLayout.top
+ TableViewColumn{ role: "name"; title: "name" }
+ TableViewColumn{ role: "address"; title: "address"; width: 300}
+
+ model: addressModel
+ itemDelegate: Item {
+ Text {
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: 10
+ verticalCenter: parent.verticalCenter
+ }
+ color: styleData.textColor
+ elide: styleData.elideMode
+ text: styleData.value
+ font.pixelSize: 11
+ MouseArea {
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ propagateComposedEvents: true
+ anchors.fill: parent
+ onClicked: {
+ addressView.selection.clear()
+ addressView.selection.select(styleData.row)
+
+ if(mouse.button == Qt.RightButton) {
+ contextMenu.row = styleData.row;
+ contextMenu.popup()
+ }
+ }
+ }
+ }
+
+ }
+
+ Menu {
+ id: contextMenu
+ property var row;
+
+ MenuItem {
+ text: "Copy"
+ onTriggered: {
+ copyToClipboard(addressModel.get(this.row).address)
+ }
+ }
+ }
+ }
+
+ 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/javascript.qml b/ethereal/assets/qml/views/javascript.qml
new file mode 100644
index 000000000..ea05c4148
--- /dev/null
+++ b/ethereal/assets/qml/views/javascript.qml
@@ -0,0 +1,45 @@
+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: "JavaScript"
+ property var iconSource: "../tx.png"
+ property var menuItem
+
+ objectName: "javascriptView"
+ visible: false
+ anchors.fill: parent
+
+ TextField {
+ id: input
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+ height: 20
+
+ Keys.onReturnPressed: {
+ var res = eth.evalJavascriptString(this.text);
+ this.text = "";
+
+ output.append(res)
+ }
+ }
+
+ TextArea {
+ id: output
+ text: "> JSRE Ready..."
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ bottom: input.top
+ }
+ }
+}
diff --git a/ethereal/assets/qml/views/pending_tx.qml b/ethereal/assets/qml/views/pending_tx.qml
new file mode 100644
index 000000000..abfa25790
--- /dev/null
+++ b/ethereal/assets/qml/views/pending_tx.qml
@@ -0,0 +1,45 @@
+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 iconSource: "../tx.png"
+ property var menuItem
+
+ 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(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 000000000..fb8ba8a6d
--- /dev/null
+++ b/ethereal/assets/qml/views/transaction.qml
@@ -0,0 +1,215 @@
+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 iconSource: "../new.png"
+ property var title: "New transaction"
+ property var menuItem
+
+ 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.transact(txFuelRecipient.text, value, txGas.text, gasPrice, codeView.text)
+ if(res[1]) {
+ txResult.text = "Your contract <b>could not</b> be sent over the network:\n<b>"
+ txResult.text += res[1].error()
+ txResult.text += "</b>"
+ 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/views/wallet.qml b/ethereal/assets/qml/views/wallet.qml
new file mode 100644
index 000000000..5e10a7022
--- /dev/null
+++ b/ethereal/assets/qml/views/wallet.qml
@@ -0,0 +1,165 @@
+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 {
+ id: root
+ property var title: "Wallet"
+ property var iconSource: "../wallet.png"
+ property var menuItem
+
+ objectName: "walletView"
+ anchors.fill: parent
+
+ function onReady() {
+ menuItem.secondaryTitle = eth.numberToHuman(eth.balanceAt(eth.key().address))
+ }
+
+ 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" }
+ }
+
+ ColumnLayout {
+ spacing: 10
+ y: 40
+ anchors.fill: parent
+
+ Text {
+ id: balance
+ text: "<b>Balance</b>: " + eth.numberToHuman(eth.balanceAt(eth.key().address))
+ font.pixelSize: 24
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ top: parent.top
+ topMargin: 20
+ }
+ }
+
+ Rectangle {
+ id: newTxPane
+ color: "#ececec"
+ border.color: "#cccccc"
+ border.width: 1
+ anchors {
+ top: balance.bottom
+ topMargin: 10
+ left: parent.left
+ leftMargin: 5
+ right: parent.right
+ rightMargin: 5
+ }
+ height: 100
+
+ RowLayout {
+ id: amountFields
+ spacing: 10
+ anchors {
+ top: parent.top
+ topMargin: 20
+ left: parent.left
+ leftMargin: 20
+ }
+
+ Text {
+ text: "Ξ "
+ }
+
+ // There's something off with the row layout where textfields won't listen to the width setting
+ Rectangle {
+ width: 50
+ height: 20
+ TextField {
+ id: txValue
+ width: parent.width
+ placeholderText: "0.00"
+ }
+ }
+
+ ComboBox {
+ id: valueDenom
+ currentIndex: 6
+ model: denomModel
+ }
+
+ }
+
+ RowLayout {
+ id: toFields
+ spacing: 10
+ anchors {
+ top: amountFields.bottom
+ topMargin: 5
+ left: parent.left
+ leftMargin: 20
+ }
+
+ Text {
+ text: "To"
+ }
+
+ Rectangle {
+ width: 200
+ height: 20
+ TextField {
+ id: txTo
+ width: parent.width
+ placeholderText: "Address or name"
+ }
+ }
+
+ Button {
+ text: "Send"
+ onClicked: {
+ var value = txValue.text + denomModel.get(valueDenom.currentIndex).zeros;
+ var gasPrice = "10000000000000"
+ var res = eth.transact({from: eth.key().privateKey, to: txTo.text, value: value, gas: "500", gasPrice: gasPrice})
+ console.log(res)
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: newTxPane.bottom
+ topMargin: 10
+ bottom: parent.bottom
+ }
+ TableView {
+ id: txTableView
+ anchors.fill : parent
+ TableViewColumn{ role: "num" ; title: "#" ; width: 30 }
+ TableViewColumn{ role: "from" ; title: "From" ; width: 280 }
+ TableViewColumn{ role: "to" ; title: "To" ; width: 280 }
+ TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 }
+
+ model: ListModel {
+ id: txModel
+ Component.onCompleted: {
+ var messages = JSON.parse(eth.messages({latest: -1, from: "e6716f9544a56c530d868e4bfbacb172315bdead"}))
+ for(var i = 0; i < messages.length; i++) {
+ var message = messages[i];
+ this.insert(0, {num: i, from: message.from, to: message.to, value: eth.numberToHuman(message.value)})
+ }
+ }
+ }
+ }
+ }
+
+ }
+}
diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml
index eef49824f..094349bab 100644
--- a/ethereal/assets/qml/wallet.qml
+++ b/ethereal/assets/qml/wallet.qml
@@ -6,17 +6,76 @@ import QtQuick.Window 2.1;
import QtQuick.Controls.Styles 1.1
import Ethereum 1.0
+import "../ext/filter.js" as Eth
ApplicationWindow {
id: root
property alias miningButtonText: miningButton.text
+
width: 900
height: 600
minimumHeight: 300
- title: "Ethereal"
+ title: "Ether browser"
+
+ // This signal is used by the filter API. The filter API connects using this signal handler from
+ // the different QML files and plugins.
+ signal message(var callback, int seed, int seedCallback);
+ function invokeFilterCallback(data, receiverSeed, callbackSeed) {
+ var messages = JSON.parse(data)
+ // Signal handler
+ message(messages, receiverSeed, callbackSeed);
+ }
+
+ TextField {
+ id: copyElementHax
+ visible: false
+ }
+
+ function copyToClipboard(text) {
+ copyElementHax.text = text
+ copyElementHax.selectAll()
+ copyElementHax.copy()
+ }
+
+ // Takes care of loading all default plugins
+ Component.onCompleted: {
+ var walletView = addPlugin("./views/wallet.qml", {noAdd: true, section: "ethereum", active: true})
+ var historyView = addPlugin("./views/history.qml", {noAdd: true, section: "legacy"})
+ var newTxView = addPlugin("./views/transaction.qml", {noAdd: true, section: "legacy"})
+ var chainView = addPlugin("./views/chain.qml", {noAdd: true, section: "legacy"})
+ var infoView = addPlugin("./views/info.qml", {noAdd: true, section: "legacy"})
+ var pendingTxView = addPlugin("./views/pending_tx.qml", {noAdd: true, section: "legacy"})
+ var pendingTxView = addPlugin("./views/javascript.qml", {noAdd: true, section: "legacy"})
+
+ // 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
+ }
+
+ var views = mainSplit.addComponent(component, options)
+ views.menuItem.path = path
+
+ mainSplit.views.push(views);
+
+ if(!options.noAdd) {
+ gui.addPlugin(path)
+ }
+
+ return views.view
+ }
MenuBar {
Menu {
@@ -24,7 +83,44 @@ ApplicationWindow {
MenuItem {
text: "Import App"
shortcut: "Ctrl+o"
- onTriggered: openAppDialog.open()
+ onTriggered: {
+ generalFileDialog.show(true, importApp)
+ }
+ }
+
+ MenuItem {
+ text: "Browser"
+ onTriggered: eth.openBrowser()
+ }
+
+ MenuItem {
+ text: "Add plugin"
+ onTriggered: {
+ generalFileDialog.show(true, function(path) {
+ addPlugin(path, {canClose: true, section: "apps"})
+ })
+ }
+ }
+
+ MenuSeparator {}
+
+ MenuItem {
+ text: "Import key"
+ shortcut: "Ctrl+i"
+ onTriggered: {
+ generalFileDialog.show(true, function(path) {
+ gui.importKey(path)
+ })
+ }
+ }
+
+ MenuItem {
+ text: "Export keys"
+ shortcut: "Ctrl+e"
+ onTriggered: {
+ generalFileDialog.show(false, function(path) {
+ })
+ }
}
}
@@ -33,7 +129,33 @@ ApplicationWindow {
MenuItem {
text: "Debugger"
shortcut: "Ctrl+d"
- onTriggered: ui.startDebugger()
+ onTriggered: eth.startDebugger()
+ }
+
+ MenuItem {
+ text: "Import Tx"
+ onTriggered: {
+ txImportDialog.visible = true
+ }
+ }
+
+ MenuItem {
+ text: "Run JS file"
+ onTriggered: {
+ generalFileDialog.show(true, function(path) {
+ eth.evalJavascriptFile(path)
+ })
+ }
+ }
+
+ MenuItem {
+ text: "Dump state"
+ onTriggered: {
+ generalFileDialog.show(false, function(path) {
+ // Empty hash for latest
+ gui.dumpState("", path)
+ })
+ }
}
}
@@ -67,1037 +189,541 @@ ApplicationWindow {
}
-
- 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 {
- anchors.fill: parent
- resizing: false
-
- Rectangle {
- id: menu
- Layout.minimumWidth: 80
- Layout.maximumWidth: 80
- anchors.bottom: parent.bottom
- anchors.top: parent.top
- //color: "#D9DDE7"
- color: "#252525"
-
- ColumnLayout {
- y: 50
- anchors.left: parent.left
- anchors.right: parent.right
- height: 200
- Image {
- source: "../tx.png"
- 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)
- }
- }
- }
- }
- }
-
- 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
- }
-
- 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"
- }
- */
- }
- }
-
- FileDialog {
- id: openAppDialog
- title: "Open QML Application"
- 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)
- }
- }
- }
-
statusBar: StatusBar {
- height: 30
+ height: 32
RowLayout {
Button {
id: miningButton
+ text: "Start Mining"
onClicked: {
- eth.toggleMining()
+ gui.toggleMining()
}
- text: "Start Mining"
}
Button {
- property var enabled: true
- id: debuggerWindow
+ id: importAppButton
+ text: "Browser"
onClicked: {
- ui.startDebugger()
+ eth.openBrowser()
}
- text: "Debugger"
}
- Button {
- id: importAppButton
- anchors.left: debuggerWindow.right
- anchors.leftMargin: 5
- onClicked: openAppDialog.open()
- text: "Import App"
- }
+ RowLayout {
+ Label {
+ id: walletValueLabel
- Label {
- anchors.left: importAppButton.right
- anchors.leftMargin: 5
- id: walletValueLabel
+ font.pixelSize: 10
+ styleColor: "#797979"
+ }
}
}
- Label {
- y: 6
- id: lastBlockLabel
- objectName: "lastBlockLabel"
- visible: true
- text: ""
+ 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"
- }
- }
- }
-
- 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: '<h3>Block details</h3>'; color: "#F2F2F2"}
- Text { text: '<b>Block number:</b> ' + number; color: "#F2F2F2"}
- Text { text: '<b>Hash:</b> ' + hash; color: "#F2F2F2"}
- Text { text: '<b>Coinbase:</b> &lt;' + name + '&gt; ' + coinbase; color: "#F2F2F2"}
- Text { text: '<b>Block found at:</b> ' + prettyTime; color: "#F2F2F2"}
- Text { text: '<b>Gas used:</b> ' + gasUsed + " / " + gasLimit; color: "#F2F2F2"}
- }
- }
- }
- ListView {
- model: singleBlock
- delegate: blockDetailsDelegate
- anchors.top: parent.top
- height: 100
- anchors.leftMargin: 20
- id: listViewThing
- Layout.maximumHeight: 40
+ anchors.right: peerGroup.left
+ anchors.rightMargin: 5
}
- 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
- }
- }
+
+ ProgressBar {
+ id: syncProgressIndicator
+ visible: false
+ objectName: "syncProgressIndicator"
+ y: 3
+ width: 140
+ indeterminate: true
+ anchors.right: peerGroup.left
+ anchors.rightMargin: 5
}
- function showContractData(tx) {
- txDetailsDebugButton.tx = tx
- if(tx.createsContract) {
- contractData.text = tx.data
- contractLabel.text = "<h4> Transaction created contract " + tx.address + "</h4>"
- }else{
- contractLabel.text = "<h4> Transaction ran contract " + tx.address + "</h4>"
- contractData.text = tx.rawData
+ RowLayout {
+ id: peerGroup
+ y: 7
+ anchors.right: parent.right
+ MouseArea {
+ onDoubleClicked: peerWindow.visible = true
+ anchors.fill: parent
}
- popup.height = 540
- }
- Rectangle {
- id: txDetails
- width: popup.width
- height: 300
- anchors.left: listViewThing.left
- anchors.top: txView.bottom
Label {
- text: "<h4>Contract data</h4>"
- anchors.top: parent.top
- anchors.left: parent.left
- id: contractLabel
- anchors.leftMargin: 10
+ id: peerLabel
+ font.pixelSize: 8
+ text: "0 / 0"
}
- 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)
- }
- }
- }
- 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))
- }
+ Image {
+ id: peerImage
+ width: 10; height: 10
+ source: "../network.png"
}
- 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
- }
+
+ property var blockModel: ListModel {
+ id: blockModel
}
- Window {
- id: aboutWin
- visible: false
- title: "About"
- minimumWidth: 350
- maximumWidth: 350
- maximumHeight: 200
- minimumHeight: 200
-
- Image {
- id: aboutIcon
- height: 150
- width: 150
- fillMode: Image.PreserveAspectFit
- smooth: true
- source: "../facet.png"
- x: 10
- y: 10
- }
+ SplitView {
+ property var views: [];
- Text {
- anchors.left: aboutIcon.right
- anchors.leftMargin: 10
- font.pointSize: 12
- text: "<h2>Ethereal</h2><br><h3>Development</h3>Jeffrey Wilcke<br>Maran Hidskes<br>Viktor Trón<br>"
+ id: mainSplit
+ anchors.fill: parent
+ resizing: false
+
+ function setView(view, menu) {
+ for(var i = 0; i < views.length; i++) {
+ views[i].view.visible = false
+
+ views[i].menuItem.border.color = "#00000000"
+ views[i].menuItem.color = "#00000000"
+ }
+ view.visible = true
+
+ menu.border.color = "#CCCCCC"
+ menu.color = "#FFFFFFFF"
}
- }
- function addDebugMessage(message){
- debuggerLog.append({value: message})
- }
+ function addComponent(component, options) {
+ var view = mainView.createView(component, options)
+ view.visible = false
+ view.anchors.fill = mainView
- function addAddress(address) {
- addressModel.append({name: address.name, address: address.address})
- }
- function clearAddress() {
- addressModel.clear()
- }
+ if( !view.hasOwnProperty("iconSource") ) {
+ console.log("Could not load plugin. Property 'iconSourc' not found on view.");
+ return;
+ }
- function loadPlugin(name) {
- console.log("Loading plugin" + name)
- mainView.addPlugin(name)
- }
+ var menuItem = menu.createMenuItem(view.iconSource, view, options);
+ if( view.hasOwnProperty("menuItem") ) {
+ view.menuItem = menuItem;
+ }
- function setWalletValue(value) {
- walletValueLabel.text = value
- }
+ if( view.hasOwnProperty("onReady") ) {
+ view.onReady.call(view)
+ }
- function addTx(tx, inout) {
- var isContract
- if (tx.contract == true){
- isContract = "Yes"
- }else{
- isContract = "No"
- }
+ if( options.active ) {
+ setView(view, menuItem)
+ }
- 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
+ return {view: view, menuItem: menuItem}
}
- if(txs != null){
- amount = txs.length
- }
+ /*********************
+ * Main menu.
+ ********************/
+ Rectangle {
+ id: menu
+ Layout.minimumWidth: 180
+ Layout.maximumWidth: 180
+ anchors.top: parent.top
+ color: "#ececec"
- 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)})
- }
- }
+ Component {
+ id: menuItemTemplate
+ Rectangle {
+ id: menuItem
+ property var view;
+ property var path;
+
+ property alias title: label.text
+ property alias icon: icon.source
+ property alias secondaryTitle: secondary.text
+
+ width: 180
+ height: 28
+ border.color: "#00000000"
+ border.width: 1
+ radius: 5
+ color: "#00000000"
+
+ anchors {
+ left: parent.left
+ leftMargin: 4
+ }
- function addLog(str) {
- // Remove first item once we've reached max log items
- if(logModel.count > 250) {
- logModel.remove(0)
- }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ mainSplit.setView(view, menuItem)
+ }
+ }
- if(str.len != 0) {
- if(logView.flickableItem.atYEnd) {
- logModel.append({description: str})
- logView.positionViewAtRow(logView.rowCount - 1, ListView.Contain)
- } else {
- logModel.append({description: str})
- }
- }
+ Image {
+ id: icon
+ height: 20
+ width: 20
+ anchors {
+ left: parent.left
+ verticalCenter: parent.verticalCenter
+ leftMargin: 3
+ }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ menuItem.closeApp()
+ }
+ }
+ }
- }
+ Text {
+ id: label
+ anchors {
+ left: icon.right
+ verticalCenter: parent.verticalCenter
+ leftMargin: 3
+ }
- function setPeers(text) {
- peerLabel.text = text
- }
+ color: "#0D0A01"
+ font.pixelSize: 12
+ }
- 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})
- }
+ Text {
+ id: secondary
+ anchors {
+ right: parent.right
+ rightMargin: 8
+ verticalCenter: parent.verticalCenter
+ }
+ color: "#AEADBE"
+ font.pixelSize: 12
+ }
- 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
- }
- 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" }
- }
- }
- }
+ function closeApp() {
+ if(this.view.hasOwnProperty("onDestroy")) {
+ this.view.onDestroy.call(this.view)
+ }
- // *******************************************
- // Components
- // *******************************************
-
- // 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
- 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" }
- }
+ this.view.destroy()
+ this.destroy()
+ gui.removePlugin(this.path)
+ }
+ }
+ }
+ function createMenuItem(icon, view, options) {
+ if(options === undefined) {
+ options = {};
+ }
- TextField {
- id: txFuelRecipient
- placeholderText: "Address / Name or empty for contract"
- //validator: RegExpValidator { regExp: /[a-f0-9]{40}/ }
- width: 400
- }
+ var section;
+ switch(options.section) {
+ case "ethereum":
+ section = menuDefault;
+ break;
+ case "legacy":
+ section = menuLegacy;
+ break;
+ default:
+ section = menuApps;
+ break;
+ }
- RowLayout {
- TextField {
- id: txValue
- width: 222
- placeholderText: "Amount"
- validator: RegExpValidator { regExp: /\d*/ }
- onTextChanged: {
- contractFormReady()
- }
- }
+ var comp = menuItemTemplate.createObject(section)
- ComboBox {
- id: valueDenom
- currentIndex: 6
- model: denomModel
- }
- }
+ comp.view = view
+ comp.title = view.title
+ comp.icon = view.iconSource
+ /*
+ if(view.secondary !== undefined) {
+ comp.secondary = view.secondary
+ }
+ */
- RowLayout {
- TextField {
- id: txGas
- width: 50
- validator: RegExpValidator { regExp: /\d*/ }
- placeholderText: "Gas"
- text: "500"
- /*
- onTextChanged: {
- contractFormReady()
- }
- */
- }
- Label {
- id: atLabel
- text: "@"
- }
+ return comp
- TextField {
- id: txGasPrice
- width: 200
- placeholderText: "Gas price"
- text: "10"
- validator: RegExpValidator { regExp: /\d*/ }
- /*
- onTextChanged: {
- contractFormReady()
- }
- */
- }
+ /*
+ if(options.canClose) {
+ //comp.closeButton.visible = options.canClose
+ }
+ */
+ }
- ComboBox {
- id: gasDenom
- currentIndex: 4
- model: denomModel
- }
- }
+ ColumnLayout {
+ id: menuColumn
+ y: 10
+ width: parent.width
+ anchors.left: parent.left
+ anchors.right: parent.right
+ spacing: 3
+
+ Text {
+ text: "ETHEREUM"
+ font.bold: true
+ anchors {
+ left: parent.left
+ leftMargin: 5
+ }
+ color: "#888888"
+ }
- Label {
- id: txDataLabel
- text: "Data"
- }
+ ColumnLayout {
+ id: menuDefault
+ spacing: 3
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ }
- TextArea {
- id: codeView
- height: 300
- anchors.topMargin: 5
- width: 400
- onTextChanged: {
- contractFormReady()
- }
- }
+ Text {
+ text: "APPS"
+ font.bold: true
+ anchors {
+ left: parent.left
+ leftMargin: 5
+ }
+ color: "#888888"
+ }
- 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 <b>could not</b> be send over the network:\n<b>"
- txResult.text += res[1].error()
- txResult.text += "</b>"
- 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"
- }
- }
- }
- }
- // 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
- 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"
- }
- }
+ ColumnLayout {
+ id: menuApps
+ spacing: 3
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ }
- 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
+ Text {
+ text: "DEBUG"
+ font.bold: true
+ anchors {
+ left: parent.left
+ leftMargin: 5
+ }
+ color: "#888888"
+ }
- }
- TextField {
- id: txSimpleOutput
- visible: false
- width: 530
- }
- Button {
- id: newSimpleTxButton
- visible: false
- text: "Create an other transaction"
- onClicked: {
- this.visible = false
- simpleSendColumn.state = "SETUP"
- }
- }
- }
- }
-}
+ ColumnLayout {
+ id: menuLegacy
+ spacing: 3
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ }
+ }
+ }
+
+ /*********************
+ * Main view
+ ********************/
+ Rectangle {
+ id: mainView
+ color: "#00000000"
+
+ anchors.right: parent.right
+ anchors.left: menu.right
+ anchors.bottom: parent.bottom
+ anchors.top: parent.top
+
+ function createView(component) {
+ var view = component.createObject(mainView)
+
+ return view;
+ }
+ }
+
+
+ }
+
+
+ /******************
+ * Dialogs
+ *****************/
+ FileDialog {
+ id: generalFileDialog
+ property var callback;
+ onAccepted: {
+ var path = this.fileUrl.toString();
+ callback.call(this, path);
+ }
+
+ function show(selectExisting, callback) {
+ generalFileDialog.callback = callback;
+ generalFileDialog.selectExisting = selectExisting;
+
+ this.open();
+ }
+ }
+
+
+ /******************
+ * Wallet functions
+ *****************/
+ function importApp(path) {
+ var ext = path.split('.').pop()
+ if(ext == "html" || ext == "htm") {
+ eth.openHtml(path)
+ }else if(ext == "qml"){
+ addPlugin(path, {canClose: true, section: "apps"})
+ }
+ }
+
+
+ function setWalletValue(value) {
+ walletValueLabel.text = value
+ }
+
+ function loadPlugin(name) {
+ console.log("Loading plugin" + name)
+ var view = 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: peerWindow
+ //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint
+ height: 200
+ width: 700
+ Rectangle {
+ anchors.fill: parent
+ property var peerModel: ListModel {
+ id: peerModel
+ }
+ 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" }
+ }
+ }
+ }
+
+ Window {
+ id: aboutWin
+ visible: false
+ title: "About"
+ minimumWidth: 350
+ maximumWidth: 350
+ maximumHeight: 200
+ minimumHeight: 200
+
+ Image {
+ id: aboutIcon
+ height: 150
+ width: 150
+ fillMode: Image.PreserveAspectFit
+ smooth: true
+ source: "../facet.png"
+ x: 10
+ y: 10
+ }
+
+ Text {
+ anchors.left: aboutIcon.right
+ anchors.leftMargin: 10
+ font.pointSize: 12
+ text: "<h2>Ethereal - Adrastea</h2><br><h3>Development</h3>Jeffrey Wilcke<br>Maran Hidskes<br>Viktor Trón<br>"
+ }
+ }
+
+ Window {
+ 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: {
+ }
+ }
+ 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
+ }
+ }
+
+ Window {
+ id: addPeerWin
+ 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: {
+ eth.connectToPeer(addrField.text)
+ addPeerWin.visible = false
+ }
+ }
+ Button {
+ anchors.left: addrField.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 5
+ 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 5e4c035d8..ca6860036 100644
--- a/ethereal/assets/qml/webapp.qml
+++ b/ethereal/assets/qml/webapp.qml
@@ -2,6 +2,7 @@ import QtQuick 2.0
import QtWebKit 3.0
import QtWebKit.experimental 1.0
import QtQuick.Controls 1.0;
+import QtQuick.Controls.Styles 1.0
import QtQuick.Layouts 1.0;
import QtQuick.Window 2.1;
import Ethereum 1.0
@@ -9,8 +10,8 @@ import Ethereum 1.0
ApplicationWindow {
id: window
title: "Ethereum"
- width: 900
- height: 600
+ width: 1000
+ height: 800
minimumHeight: 300
property alias url: webview.url
@@ -22,19 +23,113 @@ ApplicationWindow {
anchors.fill: parent
state: "inspectorShown"
+ RowLayout {
+ id: navBar
+ height: 40
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: 7
+ }
+
+ Button {
+ id: back
+ onClicked: {
+ webview.goBack()
+ }
+ style: ButtonStyle {
+ background: Image {
+ source: "../back.png"
+ width: 30
+ height: 30
+ }
+ }
+ }
+
+ TextField {
+ anchors {
+ left: back.right
+ right: toggleInspector.left
+ leftMargin: 5
+ rightMargin: 5
+ }
+ id: uriNav
+ y: parent.height / 2 - this.height / 2
+
+ Keys.onReturnPressed: {
+ webview.url = this.text;
+ }
+ }
+
+ Button {
+ id: toggleInspector
+ anchors {
+ right: parent.right
+ }
+ iconSource: "../bug.png"
+ onClicked: {
+ if(inspector.visible == true){
+ inspector.visible = false
+ }else{
+ inspector.visible = true
+ inspector.url = webview.experimental.remoteInspectorUrl
+ }
+ }
+ }
+ }
+
+
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: navBar.bottom
+ }
onTitleChanged: { window.title = title }
+
+ property var cleanPath: false
+ onNavigationRequested: {
+ if(!this.cleanPath) {
+ var uri = request.url.toString();
+ if(!/.*\:\/\/.*/.test(uri)) {
+ uri = "http://" + uri;
+ }
+
+ var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/
+
+ if(reg.test(uri)) {
+ uri.replace(reg, function(match, pre, domain, path) {
+ uri = pre;
+
+ var lookup = ui.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;
+ });
+ }
+
+ this.cleanPath = true;
+
+ webview.url = uri;
+ } else {
+ // Prevent inf loop.
+ this.cleanPath = false;
+ }
+ }
+
+
experimental.preferences.javascriptEnabled: true
experimental.preferences.navigatorQtObjectEnabled: true
experimental.preferences.developerExtrasEnabled: true
@@ -46,105 +141,126 @@ ApplicationWindow {
try {
switch(data.call) {
- case "getCoinBase":
- postData(data._seed, eth.getCoinBase())
+ case "getCoinBase":
+ postData(data._seed, eth.coinBase())
- break
- case "getIsListening":
- postData(data._seed, eth.getIsListening())
+ break
- break
- case "getIsMining":
- postData(data._seed, eth.getIsMining())
+ case "getIsListening":
+ postData(data._seed, eth.isListening())
- break
- case "getPeerCount":
- postData(data._seed, eth.getPeerCount())
+ break
+
+ case "getIsMining":
+ postData(data._seed, eth.isMining())
+
+ break
+
+ case "getPeerCount":
+ postData(data._seed, eth.peerCount())
+
+ break
- break
+ case "getTxCountAt":
+ require(1)
+ postData(data._seed, eth.txCountAt(data.args[0]))
- case "getTxCountAt":
- require(1)
- postData(data._seed, eth.getTxCountAt(data.args[0]))
+ break
- break
- case "getBlockByNumber":
- var block = eth.getBlock(data.args[0])
+ case "getBlockByNumber":
+ var block = eth.blockByNumber(data.args[0])
postData(data._seed, block)
break
- case "getBlockByHash":
- var block = eth.getBlock(data.args[0])
+
+ case "getBlockByHash":
+ var block = eth.blockByHash(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":
- postData(data._seed, null)
- break
- case "getStorage":
+ case "getStorage":
require(2);
- var stateObject = eth.getStateObject(data.args[0])
- var storage = stateObject.getStorage(data.args[1])
+ var stateObject = eth.stateObject(data.args[0])
+ var storage = stateObject.storageAt(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)
+
+ case "getEachStorage":
+ require(1);
+ var storage = JSON.parse(eth.eachStorage(data.args[0]))
+ postData(data._seed, storage)
+
+ break
+
+ case "getTransactionsFor":
+ require(1);
+ var txs = eth.transactionsFor(data.args[0], true)
+ postData(data._seed, txs)
break
- case "getTransactionsFor":
- require(1);
- var txs = eth.getTransactionsFor(data.args[0], true)
- postData(data._seed, txs)
- break
- case "getBalance":
+ case "getBalance":
require(1);
- postData(data._seed, eth.getStateObject(data.args[0]).value());
+ postData(data._seed, eth.stateObject(data.args[0]).value());
break
- case "getKey":
- var key = eth.getKey().privateKey;
+
+ case "getKey":
+ var key = eth.key().privateKey;
postData(data._seed, key)
break
- case "watch":
- require(1)
- eth.watch(data.args[0], data.args[1]);
- break
- case "disconnect":
+
+ /*
+ case "watch":
+ require(1)
+ eth.watch(data.args[0], data.args[1]);
+
+ break
+ */
+ case "watch":
+ require(2)
+ eth.watch(data.args[0], data.args[1])
+
+ 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":
+
+ case "getSecretToAddress":
require(1)
postData(data._seed, eth.secretToAddress(data.args[0]))
+
break;
- case "debug":
- console.log(data.args[0]);
- break;
+
+ case "messages":
+ require(1);
+
+ var messages = JSON.parse(eth.getMessages(data.args[0]))
+ postData(data._seed, messages)
+
+ break
+
+ case "mutan":
+ require(1)
+
+ var code = eth.compileMutan(data.args[0])
+ postData(data._seed, "0x"+code)
+
+ break;
}
} catch(e) {
console.log(data.call + ": " + e)
@@ -153,6 +269,11 @@ ApplicationWindow {
}
}
+ function post(seed, data) {
+ console.log("data", data)
+ postData(data._seed, data)
+ }
+
function require(args, num) {
if(args.length < num) {
throw("required argument count of "+num+" got "+args.length);
@@ -165,6 +286,11 @@ ApplicationWindow {
webview.experimental.postMessage(JSON.stringify({data: data, _event: event}))
}
+ function onWatchedCb(data, id) {
+ var messages = JSON.parse(data)
+ postEvent("watched:"+id, messages)
+ }
+
function onNewBlockCb(block) {
postEvent("block:new", block)
}
@@ -176,31 +302,7 @@ ApplicationWindow {
postEvent(ev, [storageObject.address, storageObject.value])
}
}
- Rectangle {
- id: toggleInspector
- color: "#bcbcbc"
- visible: true
- height: 12
- width: 12
- anchors {
- right: root.right
- }
- MouseArea {
- onClicked: {
- if(inspector.visible == true){
- inspector.visible = false
- }else{
- inspector.visible = true
- inspector.url = webview.experimental.remoteInspectorUrl
- }
- }
- onDoubleClicked: {
- console.log('refreshing')
- webView.reload()
- }
- anchors.fill: parent
- }
- }
+
Rectangle {
id: sizeGrip
diff --git a/ethereal/assets/wallet.png b/ethereal/assets/wallet.png
new file mode 100644
index 000000000..92c401e52
--- /dev/null
+++ b/ethereal/assets/wallet.png
Binary files differ
diff --git a/ethereal/debugger.go b/ethereal/debugger.go
index 096387405..7bc544377 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"
+ "gopkg.in/qml.v1"
)
type DebuggerWindow struct {
@@ -102,14 +103,7 @@ func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, data
}
}()
- data := ethutil.StringToByteFunc(dataStr, func(s string) (ret []byte) {
- slice := strings.Split(dataStr, "\n")
- for _, dataItem := range slice {
- d := ethutil.FormatData(dataItem)
- ret = append(ret, d...)
- }
- return
- })
+ data := utils.FormatTransactionData(dataStr)
var err error
script := ethutil.StringToByteFunc(scriptStr, func(s string) (ret []byte) {
@@ -134,26 +128,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(&ethstate.Message{}, 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 ac3e090f9..514084c97 100644
--- a/ethereal/ext_app.go
+++ b/ethereal/ext_app.go
@@ -1,12 +1,14 @@
package main
import (
- "fmt"
+ "encoding/json"
+
"github.com/ethereum/eth-go/ethchain"
- "github.com/ethereum/eth-go/ethpub"
+ "github.com/ethereum/eth-go/ethpipe"
+ "github.com/ethereum/eth-go/ethreact"
"github.com/ethereum/eth-go/ethstate"
- "github.com/ethereum/eth-go/ethutil"
- "github.com/go-qml/qml"
+ "github.com/ethereum/go-ethereum/javascript"
+ "gopkg.in/qml.v1"
)
type AppContainer interface {
@@ -17,34 +19,37 @@ type AppContainer interface {
Engine() *qml.Engine
NewBlock(*ethchain.Block)
- ObjectChanged(*ethstate.StateObject)
- StorageChanged(*ethstate.StorageState)
NewWatcher(chan bool)
+ Messages(ethstate.Messages, string)
+ Post(string, int)
}
type ExtApplication struct {
- *ethpub.PEthereum
+ *ethpipe.JSPipe
+ eth ethchain.EthManager
- blockChan chan ethutil.React
- changeChan chan ethutil.React
+ blockChan chan ethreact.Event
+ messageChan chan ethreact.Event
quitChan chan bool
watcherQuitChan chan bool
- container AppContainer
- lib *UiLib
- registeredEvents []string
+ filters map[string]*ethchain.Filter
+
+ container AppContainer
+ lib *UiLib
}
func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication {
app := &ExtApplication{
- ethpub.NewPEthereum(lib.eth),
- make(chan ethutil.React, 1),
- make(chan ethutil.React, 1),
+ ethpipe.NewJSPipe(lib.eth),
+ lib.eth,
+ make(chan ethreact.Event, 100),
+ make(chan ethreact.Event, 100),
make(chan bool),
make(chan bool),
+ make(map[string]*ethchain.Filter),
container,
lib,
- nil,
}
return app
@@ -58,8 +63,7 @@ func (app *ExtApplication) run() {
err := app.container.Create()
if err != nil {
- fmt.Println(err)
-
+ logger.Errorln(err)
return
}
@@ -69,6 +73,7 @@ func (app *ExtApplication) run() {
// Subscribe to events
reactor := app.lib.eth.Reactor()
reactor.Subscribe("newBlock", app.blockChan)
+ reactor.Subscribe("messages", app.messageChan)
app.container.NewWatcher(app.watcherQuitChan)
@@ -83,9 +88,6 @@ func (app *ExtApplication) stop() {
// Clean up
reactor := app.lib.eth.Reactor()
reactor.Unsubscribe("newBlock", app.blockChan)
- for _, event := range app.registeredEvents {
- reactor.Unsubscribe(event, app.changeChan)
- }
// Kill the main loop
app.quitChan <- true
@@ -93,7 +95,6 @@ func (app *ExtApplication) stop() {
close(app.blockChan)
close(app.quitChan)
- close(app.changeChan)
app.container.Destroy()
}
@@ -108,26 +109,37 @@ out:
if block, ok := block.Resource.(*ethchain.Block); ok {
app.container.NewBlock(block)
}
- case object := <-app.changeChan:
- if stateObject, ok := object.Resource.(*ethstate.StateObject); ok {
- app.container.ObjectChanged(stateObject)
- } else if storageObject, ok := object.Resource.(*ethstate.StorageState); ok {
- app.container.StorageChanged(storageObject)
+ case msg := <-app.messageChan:
+ if messages, ok := msg.Resource.(ethstate.Messages); ok {
+ for id, filter := range app.filters {
+ msgs := filter.FilterMessages(messages)
+ if len(msgs) > 0 {
+ app.container.Messages(msgs, id)
+ }
+ }
}
}
}
}
-func (app *ExtApplication) Watch(addr, storageAddr string) {
- var event string
- if len(storageAddr) == 0 {
- event = "object:" + string(ethutil.Hex2Bytes(addr))
- app.lib.eth.Reactor().Subscribe(event, app.changeChan)
- } else {
- event = "storage:" + string(ethutil.Hex2Bytes(addr)) + ":" + string(ethutil.Hex2Bytes(storageAddr))
- app.lib.eth.Reactor().Subscribe(event, app.changeChan)
+func (self *ExtApplication) Watch(filterOptions map[string]interface{}, identifier string) {
+ self.filters[identifier] = ethchain.NewFilterFromMap(filterOptions, self.eth)
+}
+
+func (self *ExtApplication) GetMessages(object map[string]interface{}) string {
+ filter := ethchain.NewFilterFromMap(object, self.eth)
+
+ messages := filter.Find()
+ var msgs []javascript.JSMessage
+ for _, m := range messages {
+ msgs = append(msgs, javascript.NewJSMessage(m))
+ }
+
+ b, err := json.Marshal(msgs)
+ if err != nil {
+ return "{\"error\":" + err.Error() + "}"
}
- app.registeredEvents = append(app.registeredEvents, event)
+ return string(b)
}
diff --git a/ethereal/gui.go b/ethereal/gui.go
index df01cddda..f450acde6 100644
--- a/ethereal/gui.go
+++ b/ethereal/gui.go
@@ -2,31 +2,41 @@ package main
import (
"bytes"
+ "encoding/json"
"fmt"
+ "math/big"
+ "os"
+ "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/ethpub"
+ "github.com/ethereum/eth-go/ethpipe"
+ "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"
+ "gopkg.in/qml.v1"
)
var logger = ethlog.NewLogger("GUI")
+type plugin struct {
+ Name string `json:"name"`
+ Path string `json:"path"`
+}
+
type Gui struct {
// The main application window
win *qml.Window
// QML Engine
engine *qml.Engine
component *qml.Common
+ qmlDone bool
// The ethereum interface
eth *eth.Ethereum
@@ -35,14 +45,17 @@ type Gui struct {
txDb *ethdb.LDBDatabase
- pub *ethpub.PEthereum
logLevel ethlog.LogLevel
open bool
+ pipe *ethpipe.JSPipe
+
Session string
clientIdentity *ethwire.SimpleClientIdentity
config *ethutil.ConfigManager
+ plugins map[string]plugin
+
miner *ethminer.Miner
}
@@ -53,9 +66,17 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, clientIden
panic(err)
}
- pub := ethpub.NewPEthereum(ethereum)
+ pipe := ethpipe.NewJSPipe(ethereum)
+ gui := &Gui{eth: ethereum, txDb: db, pipe: pipe, logLevel: ethlog.LogLevel(logLevel), Session: session, open: false, clientIdentity: clientIdentity, config: config, plugins: make(map[string]plugin)}
+ data, err := ethutil.ReadAllFile(ethutil.Config.ExecPath + "/plugins.json")
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println("plugins:", string(data))
+
+ json.Unmarshal([]byte(data), &gui.plugins)
- return &Gui{eth: ethereum, txDb: db, pub: pub, logLevel: ethlog.LogLevel(logLevel), Session: session, open: false, clientIdentity: clientIdentity, config: config}
+ return gui
}
func (gui *Gui) Start(assetPath string) {
@@ -64,22 +85,20 @@ func (gui *Gui) Start(assetPath string) {
// Register ethereum functions
qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{
- Init: func(p *ethpub.PBlock, obj qml.Object) { p.Number = 0; p.Hash = "" },
+ Init: func(p *ethpipe.JSBlock, obj qml.Object) { p.Number = 0; p.Hash = "" },
}, {
- Init: func(p *ethpub.PTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" },
+ Init: func(p *ethpipe.JSTransaction, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" },
}, {
- Init: func(p *ethpub.KeyVal, obj qml.Object) { p.Key = ""; p.Value = "" },
+ Init: func(p *ethpipe.KeyVal, obj qml.Object) { p.Key = ""; p.Value = "" },
}})
-
// 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("pub", gui.pub)
- gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath)
- context.SetVar("ui", gui.uiLib)
+ context.SetVar("gui", gui)
+ context.SetVar("eth", gui.uiLib)
// Load the main QML interface
data, _ := ethutil.Config.Db.Get([]byte("KeyRing"))
@@ -102,11 +121,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 +139,9 @@ func (gui *Gui) Stop() {
gui.open = false
gui.win.Hide()
}
+
+ gui.uiLib.jsEngine.Stop()
+
logger.Infoln("Stopped")
}
@@ -141,18 +165,54 @@ 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()
+
+ return gui.win, nil
+}
+
+func (self *Gui) DumpState(hash, path string) {
+ var stateDump []byte
- go gui.update()
+ if len(hash) == 0 {
+ stateDump = self.eth.StateManager().CurrentState().Dump()
+ } else {
+ var block *ethchain.Block
+ if hash[0] == '#' {
+ i, _ := strconv.Atoi(hash[1:])
+ block = self.eth.BlockChain().GetBlockByNumber(uint64(i))
+ } else {
+ block = self.eth.BlockChain().GetBlock(ethutil.Hex2Bytes(hash))
+ }
- return win, nil
+ if block == nil {
+ logger.Infof("block err: not found %s\n", hash)
+ return
+ }
+
+ stateDump = block.State().Dump()
+ }
+
+ file, err := os.OpenFile(path[7:], os.O_CREATE|os.O_RDWR, os.ModePerm)
+ if err != nil {
+ logger.Infoln("dump err: ", err)
+ return
+ }
+ defer file.Close()
+
+ logger.Infof("dumped state (%s) to %s\n", hash, path)
+
+ file.Write(stateDump)
+}
+
+// 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,44 +278,84 @@ 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()
+ nameReg := gui.pipe.World().Config().Get("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) readPreviousTransactions() {
- it := gui.txDb.Db().NewIterator(nil, nil)
+func (gui *Gui) insertTransaction(window string, tx *ethchain.Transaction) {
+ nameReg := ethpipe.New(gui.eth).World().Config().Get("NameReg")
addr := gui.address()
- for it.Next() {
- tx := ethchain.NewTransactionFromBytes(it.Value())
- var inout string
- if bytes.Compare(tx.Sender(), addr) == 0 {
- inout = "send"
+ var inout string
+ if bytes.Compare(tx.Sender(), addr) == 0 {
+ inout = "send"
+ } else {
+ inout = "recv"
+ }
+
+ var (
+ ptx = ethpipe.NewJSTx(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 {
- inout = "recv"
+ r = ethutil.Bytes2Hex(tx.Recipient)
}
+ }
+ ptx.Sender = s
+ ptx.Address = r
+
+ if window == "post" {
+ gui.getObjectByName("transactionView").Call("addTx", ptx, inout)
+ } else {
+ gui.getObjectByName("pendingTxView").Call("addTx", ptx, inout)
+ }
+}
+
+func (gui *Gui) readPreviousTransactions() {
+ it := gui.txDb.Db().NewIterator(nil, nil)
+ for it.Next() {
+ tx := ethchain.NewTransactionFromBytes(it.Value())
- gui.win.Root().Call("addTx", ethpub.NewPTx(tx), inout)
+ gui.insertTransaction("post", tx)
}
it.Release()
}
func (gui *Gui) processBlock(block *ethchain.Block, initial bool) {
- name := ethpub.FindNameInNameReg(gui.eth.StateManager(), block.Coinbase)
- b := ethpub.NewPBlock(block)
+ name := strings.Trim(gui.pipe.World().Config().Get("NameReg").Storage(block.Coinbase).Str(), "\x00")
+ b := ethpipe.NewJSBlock(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,29 +380,30 @@ 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()
-
- 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)
- )
+ // We have to wait for qml to be done loading all the windows.
+ for !gui.qmlDone {
+ time.Sleep(500 * time.Millisecond)
+ }
- reactor.Subscribe("newBlock", blockChan)
- reactor.Subscribe("newTx:pre", txChan)
- reactor.Subscribe("newTx:post", txChan)
- reactor.Subscribe("chainSync", chainSyncChan)
- reactor.Subscribe("miner:start", miningChan)
- reactor.Subscribe("miner:stop", miningChan)
+ go func() {
+ go gui.setInitialBlockChain()
+ gui.loadAddressBook()
+ gui.setPeerInfo()
+ gui.readPreviousTransactions()
+ }()
- nameReg := ethpub.EthereumConfig(gui.eth.StateManager()).NameReg()
- if nameReg != nil {
- reactor.Subscribe("object:"+string(nameReg.Address()), objectChan)
+ for _, plugin := range gui.plugins {
+ gui.win.Root().Call("addPlugin", plugin.Path, "")
}
- reactor.Subscribe("peerList", peerChan)
+
+ var (
+ 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)
@@ -310,86 +411,107 @@ func (gui *Gui) update() {
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.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")
- 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)
+ 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.Event == "newTx:pre" {
- object := state.GetAccount(gui.address())
+ if txMsg.Name == "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())
+ 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)
+ }
- 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())
+ gui.setWalletValue(object.Balance, unconfirmedFunds)
- unconfirmedFunds.Add(unconfirmedFunds, tx.Value)
- }
+ gui.insertTransaction("pre", tx)
+ } else {
+ object := state.GetAccount(gui.address())
+ if bytes.Compare(tx.Sender(), gui.address()) == 0 {
+ object.SubAmount(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.getObjectByName("transactionView").Call("addTx", ethpipe.NewJSTx(tx), "send")
+ gui.txDb.Put(tx.Hash(), tx.RlpEncode())
+ } else if bytes.Compare(tx.Recipient, gui.address()) == 0 {
+ object.AddAmount(tx.Value)
- gui.setWalletValue(object.Amount, nil)
+ gui.getObjectByName("transactionView").Call("addTx", ethpipe.NewJSTx(tx), "recv")
+ gui.txDb.Put(tx.Hash(), tx.RlpEncode())
+ }
- 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
- }
+ gui.setWalletValue(object.Balance, 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
+ 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)
}
- lastBlockLabel.Set("text", statusText)
}
- }
+ }()
+
+ reactor := gui.eth.Reactor()
+
+ reactor.Subscribe("newBlock", blockChan)
+ reactor.Subscribe("newTx:pre", txChan)
+ reactor.Subscribe("newTx:post", txChan)
+ reactor.Subscribe("chainSync", chainSyncChan)
+ reactor.Subscribe("miner:start", miningChan)
+ reactor.Subscribe("miner:stop", miningChan)
+
+ nameReg := gui.pipe.World().Config().Get("NameReg")
+ reactor.Subscribe("object:"+string(nameReg.Address()), objectChan)
+
+ reactor.Subscribe("peerList", peerChan)
+}
+
+func (gui *Gui) CopyToClipboard(data string) {
+ //clipboard.WriteAll("test")
+ fmt.Println("COPY currently BUGGED. Here are the contents:\n", data)
}
func (gui *Gui) setPeerInfo() {
gui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", gui.eth.PeerCount(), gui.eth.MaxPeers))
gui.win.Root().Call("resetPeers")
- for _, peer := range gui.pub.GetPeers() {
+ for _, peer := range gui.pipe.Peers() {
gui.win.Root().Call("addPeer", peer)
}
}
@@ -402,18 +524,19 @@ func (gui *Gui) address() []byte {
return gui.eth.KeyManager().Address()
}
-func (gui *Gui) RegisterName(name string) {
- name = fmt.Sprintf("\"register\"\n\"%s\"", name)
-
- gui.pub.Transact(gui.privateKey(), "NameReg", "", "10000", "10000000000000", name)
-}
-
-func (gui *Gui) Transact(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) {
- return gui.pub.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data)
-}
+func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (*ethpipe.JSReceipt, error) {
+ var data string
+ if len(recipient) == 0 {
+ code, err := ethutil.Compile(d, false)
+ if err != nil {
+ return nil, err
+ }
+ data = ethutil.Bytes2Hex(code)
+ } else {
+ data = ethutil.Bytes2Hex(utils.FormatTransactionData(d))
+ }
-func (gui *Gui) Create(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) {
- return gui.pub.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data)
+ return gui.pipe.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data)
}
func (gui *Gui) SetCustomIdentifier(customIdentifier string) {
@@ -435,6 +558,20 @@ func (gui *Gui) GetLogLevel() ethlog.LogLevel {
return gui.logLevel
}
+func (self *Gui) AddPlugin(pluginPath string) {
+ self.plugins[pluginPath] = plugin{Name: "SomeName", Path: pluginPath}
+
+ json, _ := json.MarshalIndent(self.plugins, "", " ")
+ ethutil.WriteFile(ethutil.Config.ExecPath+"/plugins.json", json)
+}
+
+func (self *Gui) RemovePlugin(pluginPath string) {
+ delete(self.plugins, pluginPath)
+
+ json, _ := json.MarshalIndent(self.plugins, "", " ")
+ ethutil.WriteFile(ethutil.Config.ExecPath+"/plugins.json", json)
+}
+
// this extra function needed to give int typecast value to gui widget
// that sets initial loglevel to default
func (gui *Gui) GetLogLevelInt() int {
@@ -451,9 +588,13 @@ func (gui *Gui) Printf(format string, v ...interface{}) {
// Print function that logs directly to the GUI
func (gui *Gui) printLog(s string) {
- str := strings.TrimRight(s, "\n")
- lines := strings.Split(str, "\n")
- for _, line := range lines {
- gui.win.Root().Call("addLog", line)
- }
+ /*
+ str := strings.TrimRight(s, "\n")
+ lines := strings.Split(str, "\n")
+
+ view := gui.getObjectByName("infoView")
+ for _, line := range lines {
+ view.Call("addLog", line)
+ }
+ */
}
diff --git a/ethereal/html_container.go b/ethereal/html_container.go
index b00d3f78e..69edea570 100644
--- a/ethereal/html_container.go
+++ b/ethereal/html_container.go
@@ -1,18 +1,22 @@
package main
import (
+ "encoding/json"
"errors"
- "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"
+ "fmt"
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"
+
+ "github.com/ethereum/eth-go/ethchain"
+ "github.com/ethereum/eth-go/ethpipe"
+ "github.com/ethereum/eth-go/ethstate"
+ "github.com/ethereum/eth-go/ethutil"
+ "github.com/ethereum/go-ethereum/javascript"
+ "github.com/howeyc/fsnotify"
+ "gopkg.in/qml.v1"
)
type HtmlApplication struct {
@@ -41,7 +45,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)
@@ -118,18 +122,26 @@ func (app *HtmlApplication) Window() *qml.Window {
}
func (app *HtmlApplication) NewBlock(block *ethchain.Block) {
- b := &ethpub.PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Bytes2Hex(block.Hash())}
+ b := &ethpipe.JSBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Bytes2Hex(block.Hash())}
app.webView.Call("onNewBlockCb", b)
}
-func (app *HtmlApplication) ObjectChanged(stateObject *ethstate.StateObject) {
- app.webView.Call("onObjectChangeCb", ethpub.NewPStateObject(stateObject))
-}
+func (self *HtmlApplication) Messages(messages ethstate.Messages, id string) {
+ var msgs []javascript.JSMessage
+ for _, m := range messages {
+ msgs = append(msgs, javascript.NewJSMessage(m))
+ }
-func (app *HtmlApplication) StorageChanged(storageObject *ethstate.StorageState) {
- app.webView.Call("onStorageChangeCb", ethpub.NewPStorageState(storageObject))
+ b, _ := json.Marshal(msgs)
+
+ self.webView.Call("onWatchedCb", string(b), id)
}
func (app *HtmlApplication) Destroy() {
app.engine.Destroy()
}
+
+func (app *HtmlApplication) Post(data string, seed int) {
+ fmt.Println("about to call 'post'")
+ app.webView.Call("post", seed, data)
+}
diff --git a/ethereal/main.go b/ethereal/main.go
index 0f99be886..4101efbca 100644
--- a/ethereal/main.go
+++ b/ethereal/main.go
@@ -1,30 +1,23 @@
package main
import (
- "github.com/ethereum/eth-go/ethlog"
- "github.com/ethereum/go-ethereum/utils"
- "github.com/go-qml/qml"
"os"
"runtime"
+
+ "github.com/ethereum/eth-go"
+ "github.com/ethereum/eth-go/ethlog"
+ "github.com/ethereum/go-ethereum/utils"
+ "gopkg.in/qml.v1"
)
const (
ClientIdentifier = "Ethereal"
- Version = "0.6.0"
+ Version = "0.6.3"
)
-func main() {
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- qml.Init(nil)
-
- var interrupted = false
- utils.RegisterInterrupt(func(os.Signal) {
- interrupted = true
- })
-
- utils.HandleInterrupt()
+var ethereum *eth.Ethereum
+func run() error {
// precedence: code-internal flag default < config file < environment variables < command line
Init() // parsing command line
@@ -43,7 +36,7 @@ func main() {
clientIdentity := utils.NewClientIdentity(ClientIdentifier, Version, Identifier)
- ethereum := utils.NewEthereum(db, clientIdentity, keyManager, UseUPnP, OutboundPort, MaxPeer)
+ ethereum = utils.NewEthereum(db, clientIdentity, keyManager, UseUPnP, OutboundPort, MaxPeer)
if ShowGenesis {
utils.ShowGenesis(ethereum)
@@ -61,6 +54,26 @@ func main() {
utils.StartEthereum(ethereum, UseSeed)
// gui blocks the main thread
gui.Start(AssetPath)
+
+ return nil
+}
+
+func main() {
+ runtime.GOMAXPROCS(runtime.NumCPU())
+
+ // This is a bit of a cheat, but ey!
+ os.Setenv("QTWEBKIT_INSPECTOR_SERVER", "127.0.0.1:99999")
+
+ //qml.Init(nil)
+ qml.Run(run)
+
+ var interrupted = false
+ utils.RegisterInterrupt(func(os.Signal) {
+ interrupted = true
+ })
+
+ utils.HandleInterrupt()
+
// we need to run the interrupt callbacks in case gui is closed
// this skips if we got here by actual interrupt stopping the GUI
if !interrupted {
diff --git a/ethereal/qml_container.go b/ethereal/qml_container.go
index 1b420ee21..85bd7c699 100644
--- a/ethereal/qml_container.go
+++ b/ethereal/qml_container.go
@@ -1,12 +1,14 @@
package main
import (
+ "fmt"
+ "runtime"
+
"github.com/ethereum/eth-go/ethchain"
- "github.com/ethereum/eth-go/ethpub"
+ "github.com/ethereum/eth-go/ethpipe"
"github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
- "github.com/go-qml/qml"
- "runtime"
+ "gopkg.in/qml.v1"
)
type QmlApplication struct {
@@ -25,7 +27,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:]
}
@@ -47,16 +49,12 @@ func (app *QmlApplication) NewWatcher(quitChan chan bool) {
// Events
func (app *QmlApplication) NewBlock(block *ethchain.Block) {
- pblock := &ethpub.PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Bytes2Hex(block.Hash())}
+ pblock := &ethpipe.JSBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Bytes2Hex(block.Hash())}
app.win.Call("onNewBlockCb", pblock)
}
-func (app *QmlApplication) ObjectChanged(stateObject *ethstate.StateObject) {
- app.win.Call("onObjectChangeCb", ethpub.NewPStateObject(stateObject))
-}
-
-func (app *QmlApplication) StorageChanged(storageObject *ethstate.StorageState) {
- app.win.Call("onStorageChangeCb", ethpub.NewPStorageState(storageObject))
+func (self *QmlApplication) Messages(msgs ethstate.Messages, id string) {
+ fmt.Println("IMPLEMENT QML APPLICATION MESSAGES METHOD")
}
// Getters
@@ -66,3 +64,5 @@ func (app *QmlApplication) Engine() *qml.Engine {
func (app *QmlApplication) Window() *qml.Window {
return app.win
}
+
+func (app *QmlApplication) Post(data string, s int) {}
diff --git a/ethereal/ui_lib.go b/ethereal/ui_lib.go
index 6a62fa1df..4b8210da6 100644
--- a/ethereal/ui_lib.go
+++ b/ethereal/ui_lib.go
@@ -1,10 +1,20 @@
package main
import (
+ "bytes"
+ "fmt"
+ "path"
+ "strconv"
+ "strings"
+
"github.com/ethereum/eth-go"
+ "github.com/ethereum/eth-go/ethchain"
+ "github.com/ethereum/eth-go/ethcrypto"
+ "github.com/ethereum/eth-go/ethpipe"
+ "github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
- "github.com/go-qml/qml"
- "path"
+ "github.com/ethereum/go-ethereum/javascript"
+ "gopkg.in/qml.v1"
)
type memAddr struct {
@@ -14,6 +24,7 @@ type memAddr struct {
// UI Library that has some basic functionality exposed
type UiLib struct {
+ *ethpipe.JSPipe
engine *qml.Engine
eth *eth.Ethereum
connected bool
@@ -22,10 +33,57 @@ type UiLib struct {
win *qml.Window
Db *Debugger
DbWindow *DebuggerWindow
+
+ jsEngine *javascript.JSRE
+
+ filterCallbacks map[int][]int
+ filters map[int]*GuiFilter
}
func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib {
- return &UiLib{engine: engine, eth: eth, assetPath: assetPath}
+ return &UiLib{JSPipe: ethpipe.NewJSPipe(eth), engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(eth), filterCallbacks: make(map[int][]int), filters: make(map[int]*GuiFilter)}
+}
+
+func (self *UiLib) LookupDomain(domain string) string {
+ world := self.World()
+
+ if len(domain) > 32 {
+ domain = string(ethcrypto.Sha3Bin([]byte(domain)))
+ }
+ data := world.Config().Get("DnsReg").StorageString(domain).Bytes()
+
+ // Left padded = A record, Right padded = CNAME
+ if data[0] == 0 {
+ data = bytes.TrimLeft(data, "\x00")
+ var ipSlice []string
+ for _, d := range data {
+ ipSlice = append(ipSlice, strconv.Itoa(int(d)))
+ }
+
+ return strings.Join(ipSlice, ".")
+ } else {
+ data = bytes.TrimRight(data, "\x00")
+
+ return string(data)
+ }
+}
+
+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 (self *UiLib) EvalJavascriptString(str string) string {
+ value, err := self.jsEngine.Run(str)
+ if err != nil {
+ return err.Error()
+ }
+
+ return fmt.Sprintf("%v", value)
}
func (ui *UiLib) OpenQml(path string) {
@@ -42,6 +100,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 +156,96 @@ func (self *UiLib) StartDbWithCode(code string) {
func (self *UiLib) StartDebugger() {
dbWindow := NewDebuggerWindow(self)
- //self.DbWindow = dbWindow
dbWindow.Show()
}
+
+func (self *UiLib) RegisterFilter(object map[string]interface{}, seed int) {
+ filter := &GuiFilter{ethpipe.NewJSFilterFromMap(object, self.eth), seed}
+ self.filters[seed] = filter
+
+ filter.MessageCallback = func(messages ethstate.Messages) {
+ for _, callbackSeed := range self.filterCallbacks[seed] {
+ self.win.Root().Call("invokeFilterCallback", filter.MessagesToJson(messages), seed, callbackSeed)
+ }
+ }
+
+}
+
+func (self *UiLib) RegisterFilterString(typ string, seed int) {
+ filter := &GuiFilter{ethpipe.NewJSFilterFromMap(nil, self.eth), seed}
+ self.filters[seed] = filter
+
+ if typ == "chain" {
+ filter.BlockCallback = func(block *ethchain.Block) {
+ for _, callbackSeed := range self.filterCallbacks[seed] {
+ self.win.Root().Call("invokeFilterCallback", "{}", seed, callbackSeed)
+ }
+ }
+ }
+}
+
+func (self *UiLib) RegisterFilterCallback(seed, cbSeed int) {
+ self.filterCallbacks[seed] = append(self.filterCallbacks[seed], cbSeed)
+}
+
+func (self *UiLib) UninstallFilter(seed int) {
+ filter := self.filters[seed]
+ if filter != nil {
+ filter.Uninstall()
+ delete(self.filters, seed)
+ }
+}
+
+type GuiFilter struct {
+ *ethpipe.JSFilter
+ seed int
+}
+
+func (self *UiLib) Transact(object map[string]interface{}) (*ethpipe.JSReceipt, error) {
+ // Default values
+ if object["from"] == nil {
+ object["from"] = ""
+ }
+ if object["to"] == nil {
+ object["to"] = ""
+ }
+ if object["value"] == nil {
+ object["value"] = ""
+ }
+ if object["gas"] == nil {
+ object["gas"] = ""
+ }
+ if object["gasPrice"] == nil {
+ object["gasPrice"] = ""
+ }
+
+ var dataStr string
+ var data []string
+ if list, ok := object["data"].(*qml.List); ok {
+ list.Convert(&data)
+ }
+
+ for _, str := range data {
+ if ethutil.IsHex(str) {
+ str = str[2:]
+
+ if len(str) != 64 {
+ str = ethutil.LeftPadString(str, 64)
+ }
+ } else {
+ str = ethutil.Bytes2Hex(ethutil.LeftPadBytes(ethutil.Big(str).Bytes(), 32))
+ }
+
+ dataStr += str
+ }
+
+ return self.JSPipe.Transact(
+ object["from"].(string),
+ object["to"].(string),
+ object["value"].(string),
+ object["gas"].(string),
+ object["gasPrice"].(string),
+ dataStr,
+ )
+}
diff --git a/ethereum/cmd.go b/ethereum/cmd.go
index ff2b8409c..5ddc91619 100644
--- a/ethereum/cmd.go
+++ b/ethereum/cmd.go
@@ -1,11 +1,13 @@
package main
import (
+ "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"
- "io/ioutil"
- "os"
)
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 4f59ddf06..5ed208411 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 217991074..a8e60dec7 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.3"
)
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.Printf("%s\n", block.State().Dump())
+
+ os.Exit(0)
+ }
+
if ShowGenesis {
utils.ShowGenesis(ethereum)
}
diff --git a/ethereum/repl/repl.go b/ethereum/repl/repl.go
index 92d4ad86a..d08feb7b4 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 3a91b0d44..4c07280f7 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/types.go b/ethereum/repl/types.go
deleted file mode 100644
index 16a18e6e5..000000000
--- a/ethereum/repl/types.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package ethrepl
-
-import (
- "fmt"
- "github.com/ethereum/eth-go/ethpub"
- "github.com/ethereum/eth-go/ethutil"
- "github.com/obscuren/otto"
-)
-
-type JSStateObject struct {
- *ethpub.PStateObject
- eth *JSEthereum
-}
-
-func (self *JSStateObject) EachStorage(call otto.FunctionCall) otto.Value {
- cb := call.Argument(0)
- self.PStateObject.EachStorage(func(key string, value *ethutil.Value) {
- value.Decode()
-
- cb.Call(self.eth.toVal(self), self.eth.toVal(key), self.eth.toVal(ethutil.Bytes2Hex(value.Bytes())))
- })
-
- return otto.UndefinedValue()
-}
-
-// The JSEthereum object attempts to wrap the PEthereum object and returns
-// meaningful javascript objects
-type JSBlock struct {
- *ethpub.PBlock
- eth *JSEthereum
-}
-
-func (self *JSBlock) GetTransaction(hash string) otto.Value {
- return self.eth.toVal(self.PBlock.GetTransaction(hash))
-}
-
-type JSEthereum struct {
- *ethpub.PEthereum
- vm *otto.Otto
-}
-
-func (self *JSEthereum) GetBlock(hash string) otto.Value {
- return self.toVal(&JSBlock{self.PEthereum.GetBlock(hash), self})
-}
-
-func (self *JSEthereum) GetPeers() otto.Value {
- return self.toVal(self.PEthereum.GetPeers())
-}
-
-func (self *JSEthereum) GetKey() otto.Value {
- return self.toVal(self.PEthereum.GetKey())
-}
-
-func (self *JSEthereum) GetStateObject(addr string) otto.Value {
- return self.toVal(&JSStateObject{self.PEthereum.GetStateObject(addr), self})
-}
-
-func (self *JSEthereum) GetStateKeyVals(addr string) otto.Value {
- return self.toVal(self.PEthereum.GetStateObject(addr).StateKeyVal(false))
-}
-
-func (self *JSEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value {
- r, err := self.PEthereum.Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr)
- if err != nil {
- fmt.Println(err)
-
- return otto.UndefinedValue()
- }
-
- return self.toVal(r)
-}
-
-func (self *JSEthereum) Create(key, valueStr, gasStr, gasPriceStr, scriptStr string) otto.Value {
- r, err := self.PEthereum.Create(key, valueStr, gasStr, gasPriceStr, scriptStr)
-
- if err != nil {
- fmt.Println(err)
-
- return otto.UndefinedValue()
- }
-
- return self.toVal(r)
-}
-
-func (self *JSEthereum) toVal(v interface{}) otto.Value {
- result, err := self.vm.ToValue(v)
-
- if err != nil {
- fmt.Println("Value unknown:", err)
-
- return otto.UndefinedValue()
- }
-
- return result
-}
diff --git a/ethereum/repl/javascript_runtime.go b/javascript/javascript_runtime.go
index 41b6216d4..c794c32a8 100644
--- a/ethereum/repl/javascript_runtime.go
+++ b/javascript/javascript_runtime.go
@@ -1,30 +1,32 @@
-package ethrepl
+package javascript
import (
"fmt"
+ "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/ethpipe"
+ "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"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
)
var jsrelogger = ethlog.NewLogger("JSRE")
type JSRE struct {
ethereum *eth.Ethereum
- vm *otto.Otto
- lib *ethpub.PEthereum
+ Vm *otto.Otto
+ pipe *ethpipe.JSPipe
- 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)
}
}
@@ -48,15 +50,15 @@ func NewJSRE(ethereum *eth.Ethereum) *JSRE {
re := &JSRE{
ethereum,
otto.New(),
- ethpub.NewPEthereum(ethereum),
- make(chan ethutil.React, 1),
- make(chan ethutil.React, 1),
+ ethpipe.NewJSPipe(ethereum),
+ 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.pipe, 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 {
@@ -121,24 +123,12 @@ out:
case block := <-self.blockChan:
if _, ok := block.Resource.(*ethchain.Block); ok {
}
- 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))
- 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))
- cb.Call(cb, val)
- }
- }
}
}
}
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 +136,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 +233,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
index c781c43d0..a3e9b8a5b 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/javascript/types.go b/javascript/types.go
new file mode 100644
index 000000000..afa0a41c6
--- /dev/null
+++ b/javascript/types.go
@@ -0,0 +1,138 @@
+package javascript
+
+import (
+ "fmt"
+
+ "github.com/ethereum/eth-go"
+ "github.com/ethereum/eth-go/ethchain"
+ "github.com/ethereum/eth-go/ethpipe"
+ "github.com/ethereum/eth-go/ethstate"
+ "github.com/ethereum/eth-go/ethutil"
+ "github.com/obscuren/otto"
+)
+
+type JSStateObject struct {
+ *ethpipe.JSObject
+ eth *JSEthereum
+}
+
+func (self *JSStateObject) EachStorage(call otto.FunctionCall) otto.Value {
+ cb := call.Argument(0)
+ self.JSObject.EachStorage(func(key string, value *ethutil.Value) {
+ value.Decode()
+
+ cb.Call(self.eth.toVal(self), self.eth.toVal(key), self.eth.toVal(ethutil.Bytes2Hex(value.Bytes())))
+ })
+
+ return otto.UndefinedValue()
+}
+
+// The JSEthereum object attempts to wrap the PEthereum object and returns
+// meaningful javascript objects
+type JSBlock struct {
+ *ethpipe.JSBlock
+ eth *JSEthereum
+}
+
+func (self *JSBlock) GetTransaction(hash string) otto.Value {
+ return self.eth.toVal(self.JSBlock.GetTransaction(hash))
+}
+
+type JSMessage struct {
+ To string `json:"to"`
+ From string `json:"from"`
+ Input string `json:"input"`
+ Output string `json:"output"`
+ Path int `json:"path"`
+ Origin string `json:"origin"`
+ Timestamp int32 `json:"timestamp"`
+ Coinbase string `json:"coinbase"`
+ Block string `json:"block"`
+ Number int32 `json:"number"`
+}
+
+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 {
+ *ethpipe.JSPipe
+ vm *otto.Otto
+ ethereum *eth.Ethereum
+}
+
+func (self *JSEthereum) GetBlock(hash string) otto.Value {
+ return self.toVal(&JSBlock{self.JSPipe.BlockByHash(hash), self})
+}
+
+func (self *JSEthereum) GetPeers() otto.Value {
+ return self.toVal(self.JSPipe.Peers())
+}
+
+func (self *JSEthereum) GetKey() otto.Value {
+ return self.toVal(self.JSPipe.Key())
+}
+
+func (self *JSEthereum) GetStateObject(addr string) otto.Value {
+ return self.toVal(&JSStateObject{ethpipe.NewJSObject(self.JSPipe.World().SafeGet(ethutil.Hex2Bytes(addr))), self})
+}
+
+func (self *JSEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value {
+ r, err := self.JSPipe.Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr)
+ if err != nil {
+ fmt.Println(err)
+
+ return otto.UndefinedValue()
+ }
+
+ return self.toVal(r)
+}
+
+func (self *JSEthereum) Create(key, valueStr, gasStr, gasPriceStr, scriptStr string) otto.Value {
+ r, err := self.JSPipe.Transact(key, "", valueStr, gasStr, gasPriceStr, scriptStr)
+
+ if err != nil {
+ fmt.Println(err)
+
+ return otto.UndefinedValue()
+ }
+
+ return self.toVal(r)
+}
+
+func (self *JSEthereum) toVal(v interface{}) otto.Value {
+ result, err := self.vm.ToValue(v)
+
+ if err != nil {
+ fmt.Println("Value unknown:", err)
+
+ return otto.UndefinedValue()
+ }
+
+ return result
+}
+
+func (self *JSEthereum) Messages(object map[string]interface{}) otto.Value {
+ filter := ethchain.NewFilterFromMap(object, self.ethereum)
+
+ 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 5d0b3463c..cda735c27 100644
--- a/utils/cmd.go
+++ b/utils/cmd.go
@@ -1,25 +1,27 @@
package utils
import (
- "bitbucket.org/kardianos/osext"
"fmt"
- "github.com/ethereum/eth-go"
- "github.com/ethereum/eth-go/ethcrypto"
- "github.com/ethereum/eth-go/ethdb"
- "github.com/ethereum/eth-go/ethlog"
- "github.com/ethereum/eth-go/ethminer"
- "github.com/ethereum/eth-go/ethpub"
- "github.com/ethereum/eth-go/ethrpc"
- "github.com/ethereum/eth-go/ethutil"
- "github.com/ethereum/eth-go/ethwire"
"io"
"log"
"os"
"os/signal"
"path"
"path/filepath"
+ "regexp"
"runtime"
"time"
+
+ "bitbucket.org/kardianos/osext"
+ "github.com/ethereum/eth-go"
+ "github.com/ethereum/eth-go/ethcrypto"
+ "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/ethrpc"
+ "github.com/ethereum/eth-go/ethutil"
+ "github.com/ethereum/eth-go/ethwire"
)
var logger = ethlog.NewLogger("CLI")
@@ -127,6 +129,7 @@ func NewDatabase() ethutil.Database {
}
func NewClientIdentity(clientIdentifier, version, customIdentifier string) *ethwire.SimpleClientIdentity {
+ logger.Infoln("identity created")
return ethwire.NewSimpleClientIdentity(clientIdentifier, version, customIdentifier)
}
@@ -193,7 +196,6 @@ func DefaultAssetPath() string {
}
func KeyTasks(keyManager *ethcrypto.KeyManager, KeyRing string, GenAddr bool, SecretFile string, ExportDir string, NonInteractive bool) {
- ethcrypto.InitWords(DefaultAssetPath()) // Init mnemonic word list
var err error
switch {
@@ -226,7 +228,7 @@ func KeyTasks(keyManager *ethcrypto.KeyManager, KeyRing string, GenAddr bool, Se
func StartRpc(ethereum *eth.Ethereum, RpcPort int) {
var err error
- ethereum.RpcServer, err = ethrpc.NewJsonRpcServer(ethpub.NewPEthereum(ethereum), RpcPort)
+ ethereum.RpcServer, err = ethrpc.NewJsonRpcServer(ethpipe.NewJSPipe(ethereum), RpcPort)
if err != nil {
logger.Errorf("Could not start RPC interface (port %v): %v", RpcPort, err)
} else {
@@ -243,21 +245,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) {
@@ -268,12 +267,23 @@ func StartMining(ethereum *eth.Ethereum) bool {
return false
}
+func FormatTransactionData(data string) []byte {
+ d := ethutil.StringToByteFunc(data, func(s string) (ret []byte) {
+ slice := regexp.MustCompile("\\n|\\s").Split(s, 1000000000)
+ for _, dataItem := range slice {
+ d := ethutil.FormatData(dataItem)
+ ret = append(ret, d...)
+ }
+ return
+ })
+
+ return d
+}
+
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 2c40dd7b8..30568c421 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 }