Merge branch 'release/0.6.3'

This commit is contained in:
obscuren 2014-08-21 15:43:45 +02:00
commit 1f59c37b89
39 changed files with 2908 additions and 1571 deletions

31
LICENSE
View File

@ -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 This library is distributed in the hope that it will be useful,
of this software and associated documentation files (the "Software"), to deal but WITHOUT ANY WARRANTY; without even the implied warranty of
in the Software without restriction, including without limitation the rights MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell General Public License for more details.
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in You should have received a copy of the GNU General Public License
all copies or substantial portions of the Software. along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR MA 02110-1301 USA
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.

View File

@ -1,11 +1,13 @@
Ethereum 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. 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). For the development package please see the [eth-go package](https://github.com/ethereum/eth-go).

BIN
ethereal/assets/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

BIN
ethereal/assets/bug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
ethereal/assets/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

View File

@ -1,6 +1,63 @@
// Main Ethereum library // Main Ethereum library
window.eth = { window.eth = {
prototype: Object(), 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 // Retrieve block
// //
@ -20,20 +77,48 @@ window.eth = {
// Create transaction // Create transaction
// //
// Transact between two state objects // Transact between two state objects
transact: function(sec, recipient, value, gas, gasPrice, data, cb) { transact: function(params, cb) {
postData({call: "transact", args: [sec, recipient, value, gas, gasPrice, data]}, 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) { getMessages: function(filter, cb) {
postData({call: "create", args: [sec, value, gas, gasPrice, init, body]}, cb); postData({call: "messages", args: [filter]}, cb);
}, },
getStorageAt: function(address, storageAddress, cb) { getStorageAt: function(address, storageAddress, cb) {
postData({call: "getStorage", args: [address, storageAddress]}, cb); postData({call: "getStorage", args: [address, storageAddress]}, cb);
}, },
getStateKeyVals: function(address, cb){ getEachStorageAt: function(address, cb){
postData({call: "getStateKeyVals", args: [address]}, cb); postData({call: "getEachStorage", args: [address]}, cb);
}, },
getKey: function(cb) { getKey: function(cb) {
@ -66,6 +151,7 @@ window.eth = {
postData({call: "getSecretToAddress", args: [sec]}, cb); postData({call: "getSecretToAddress", args: [sec]}, cb);
}, },
/*
watch: function(address, storageAddrOrCb, cb) { watch: function(address, storageAddrOrCb, cb) {
var ev; var ev;
if(cb === undefined) { if(cb === undefined) {
@ -95,6 +181,16 @@ window.eth = {
postData({call: "disconnect", args: [address, storageAddrOrCb]}); 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) { set: function(props) {
postData({call: "set", args: props}); postData({call: "set", args: props});
@ -137,9 +233,63 @@ window.eth = {
} }
} }
}, },
} }
window.eth._callbacks = {}
window.eth._onCallbacks = {}
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));
}
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() {
})

View File

@ -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))
}

View File

@ -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>

View File

@ -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 // Helper function for generating pseudo callbacks and sending data to the QML part of the application
function postData(data, cb) { function postData(data, cb) {
data._seed = Math.floor(Math.random() * 1000000) 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;
}

View File

@ -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>

BIN
ethereal/assets/pick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

View File

@ -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()
}
}
}

View File

@ -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})
}
}

View File

@ -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})
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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})
}
}

View File

@ -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"
}
}
}

View File

@ -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)})
}
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ import QtQuick 2.0
import QtWebKit 3.0 import QtWebKit 3.0
import QtWebKit.experimental 1.0 import QtWebKit.experimental 1.0
import QtQuick.Controls 1.0; import QtQuick.Controls 1.0;
import QtQuick.Controls.Styles 1.0
import QtQuick.Layouts 1.0; import QtQuick.Layouts 1.0;
import QtQuick.Window 2.1; import QtQuick.Window 2.1;
import Ethereum 1.0 import Ethereum 1.0
@ -9,8 +10,8 @@ import Ethereum 1.0
ApplicationWindow { ApplicationWindow {
id: window id: window
title: "Ethereum" title: "Ethereum"
width: 900 width: 1000
height: 600 height: 800
minimumHeight: 300 minimumHeight: 300
property alias url: webview.url property alias url: webview.url
@ -22,19 +23,113 @@ ApplicationWindow {
anchors.fill: parent anchors.fill: parent
state: "inspectorShown" state: "inspectorShown"
WebView { RowLayout {
objectName: "webView" id: navBar
id: webview height: 40
anchors.fill: parent
/*
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
bottom: sizeGrip.top leftMargin: 7
top: parent.top }
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 {
left: parent.left
right: parent.right
bottom: parent.bottom
top: navBar.bottom
} }
*/
onTitleChanged: { window.title = title } 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.javascriptEnabled: true
experimental.preferences.navigatorQtObjectEnabled: true experimental.preferences.navigatorQtObjectEnabled: true
experimental.preferences.developerExtrasEnabled: true experimental.preferences.developerExtrasEnabled: true
@ -47,37 +142,43 @@ ApplicationWindow {
try { try {
switch(data.call) { switch(data.call) {
case "getCoinBase": case "getCoinBase":
postData(data._seed, eth.getCoinBase()) postData(data._seed, eth.coinBase())
break break
case "getIsListening": case "getIsListening":
postData(data._seed, eth.getIsListening()) postData(data._seed, eth.isListening())
break break
case "getIsMining": case "getIsMining":
postData(data._seed, eth.getIsMining()) postData(data._seed, eth.isMining())
break break
case "getPeerCount": case "getPeerCount":
postData(data._seed, eth.getPeerCount()) postData(data._seed, eth.peerCount())
break break
case "getTxCountAt": case "getTxCountAt":
require(1) require(1)
postData(data._seed, eth.getTxCountAt(data.args[0])) postData(data._seed, eth.txCountAt(data.args[0]))
break break
case "getBlockByNumber": case "getBlockByNumber":
var block = eth.getBlock(data.args[0]) var block = eth.blockByNumber(data.args[0])
postData(data._seed, block) postData(data._seed, block)
break break
case "getBlockByHash": case "getBlockByHash":
var block = eth.getBlock(data.args[0]) var block = eth.blockByHash(data.args[0])
postData(data._seed, block) postData(data._seed, block)
break break
case "transact": case "transact":
require(5) require(5)
@ -85,65 +186,80 @@ ApplicationWindow {
postData(data._seed, tx) postData(data._seed, tx)
break break
case "create":
postData(data._seed, null)
break
case "getStorage": case "getStorage":
require(2); require(2);
var stateObject = eth.getStateObject(data.args[0]) var stateObject = eth.stateObject(data.args[0])
var storage = stateObject.getStorage(data.args[1]) var storage = stateObject.storageAt(data.args[1])
postData(data._seed, storage) postData(data._seed, storage)
break break
case "getStateKeyVals":
case "getEachStorage":
require(1); require(1);
var stateObject = eth.getStateObject(data.args[0]).stateKeyVal(true) var storage = JSON.parse(eth.eachStorage(data.args[0]))
postData(data._seed,stateObject) postData(data._seed, storage)
break break
case "getTransactionsFor": case "getTransactionsFor":
require(1); require(1);
var txs = eth.getTransactionsFor(data.args[0], true) var txs = eth.transactionsFor(data.args[0], true)
postData(data._seed, txs) postData(data._seed, txs)
break break
case "getBalance": case "getBalance":
require(1); require(1);
postData(data._seed, eth.getStateObject(data.args[0]).value()); postData(data._seed, eth.stateObject(data.args[0]).value());
break break
case "getKey": case "getKey":
var key = eth.getKey().privateKey; var key = eth.key().privateKey;
postData(data._seed, key) postData(data._seed, key)
break break
/*
case "watch": case "watch":
require(1) require(1)
eth.watch(data.args[0], data.args[1]); eth.watch(data.args[0], data.args[1]);
break break
*/
case "watch":
require(2)
eth.watch(data.args[0], data.args[1])
case "disconnect": case "disconnect":
require(1) require(1)
postData(data._seed, null) postData(data._seed, null)
break; 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) require(1)
postData(data._seed, eth.secretToAddress(data.args[0])) postData(data._seed, eth.secretToAddress(data.args[0]))
break; break;
case "debug":
console.log(data.args[0]); 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; break;
} }
} catch(e) { } catch(e) {
@ -153,6 +269,11 @@ ApplicationWindow {
} }
} }
function post(seed, data) {
console.log("data", data)
postData(data._seed, data)
}
function require(args, num) { function require(args, num) {
if(args.length < num) { if(args.length < num) {
throw("required argument count of "+num+" got "+args.length); throw("required argument count of "+num+" got "+args.length);
@ -165,6 +286,11 @@ ApplicationWindow {
webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) 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) { function onNewBlockCb(block) {
postEvent("block:new", block) postEvent("block:new", block)
} }
@ -176,31 +302,7 @@ ApplicationWindow {
postEvent(ev, [storageObject.address, storageObject.value]) 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 { Rectangle {
id: sizeGrip id: sizeGrip

BIN
ethereal/assets/wallet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -2,15 +2,16 @@ package main
import ( import (
"fmt" "fmt"
"math/big"
"strconv"
"strings"
"github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethutil"
"github.com/ethereum/eth-go/ethvm" "github.com/ethereum/eth-go/ethvm"
"github.com/ethereum/go-ethereum/utils" "github.com/ethereum/go-ethereum/utils"
"github.com/go-qml/qml" "gopkg.in/qml.v1"
"math/big"
"strconv"
"strings"
) )
type DebuggerWindow struct { 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) { data := utils.FormatTransactionData(dataStr)
slice := strings.Split(dataStr, "\n")
for _, dataItem := range slice {
d := ethutil.FormatData(dataItem)
ret = append(ret, d...)
}
return
})
var err error var err error
script := ethutil.StringToByteFunc(scriptStr, func(s string) (ret []byte) { 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() state := self.lib.eth.StateManager().TransState()
account := self.lib.eth.StateManager().TransState().GetAccount(keyPair.Address()) account := self.lib.eth.StateManager().TransState().GetAccount(keyPair.Address())
contract := ethstate.NewStateObject([]byte{0}) contract := ethstate.NewStateObject([]byte{0})
contract.Amount = value contract.Balance = value
self.SetAsm(script) self.SetAsm(script)
callerClosure := ethvm.NewClosure(account, contract, script, gas, gasPrice)
block := self.lib.eth.BlockChain().CurrentBlock block := self.lib.eth.BlockChain().CurrentBlock
/* callerClosure := ethvm.NewClosure(&ethstate.Message{}, account, contract, script, gas, gasPrice)
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),
})
*/
env := utils.NewEnv(state, block, account.Address(), value) env := utils.NewEnv(state, block, account.Address(), value)
vm := ethvm.New(env) vm := ethvm.New(env)
vm.Verbose = true vm.Verbose = true

View File

@ -1,12 +1,14 @@
package main package main
import ( import (
"fmt" "encoding/json"
"github.com/ethereum/eth-go/ethchain" "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/ethstate"
"github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/javascript"
"github.com/go-qml/qml" "gopkg.in/qml.v1"
) )
type AppContainer interface { type AppContainer interface {
@ -17,34 +19,37 @@ type AppContainer interface {
Engine() *qml.Engine Engine() *qml.Engine
NewBlock(*ethchain.Block) NewBlock(*ethchain.Block)
ObjectChanged(*ethstate.StateObject)
StorageChanged(*ethstate.StorageState)
NewWatcher(chan bool) NewWatcher(chan bool)
Messages(ethstate.Messages, string)
Post(string, int)
} }
type ExtApplication struct { type ExtApplication struct {
*ethpub.PEthereum *ethpipe.JSPipe
eth ethchain.EthManager
blockChan chan ethutil.React blockChan chan ethreact.Event
changeChan chan ethutil.React messageChan chan ethreact.Event
quitChan chan bool quitChan chan bool
watcherQuitChan chan bool watcherQuitChan chan bool
filters map[string]*ethchain.Filter
container AppContainer container AppContainer
lib *UiLib lib *UiLib
registeredEvents []string
} }
func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication { func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication {
app := &ExtApplication{ app := &ExtApplication{
ethpub.NewPEthereum(lib.eth), ethpipe.NewJSPipe(lib.eth),
make(chan ethutil.React, 1), lib.eth,
make(chan ethutil.React, 1), make(chan ethreact.Event, 100),
make(chan ethreact.Event, 100),
make(chan bool), make(chan bool),
make(chan bool), make(chan bool),
make(map[string]*ethchain.Filter),
container, container,
lib, lib,
nil,
} }
return app return app
@ -58,8 +63,7 @@ func (app *ExtApplication) run() {
err := app.container.Create() err := app.container.Create()
if err != nil { if err != nil {
fmt.Println(err) logger.Errorln(err)
return return
} }
@ -69,6 +73,7 @@ func (app *ExtApplication) run() {
// Subscribe to events // Subscribe to events
reactor := app.lib.eth.Reactor() reactor := app.lib.eth.Reactor()
reactor.Subscribe("newBlock", app.blockChan) reactor.Subscribe("newBlock", app.blockChan)
reactor.Subscribe("messages", app.messageChan)
app.container.NewWatcher(app.watcherQuitChan) app.container.NewWatcher(app.watcherQuitChan)
@ -83,9 +88,6 @@ func (app *ExtApplication) stop() {
// Clean up // Clean up
reactor := app.lib.eth.Reactor() reactor := app.lib.eth.Reactor()
reactor.Unsubscribe("newBlock", app.blockChan) reactor.Unsubscribe("newBlock", app.blockChan)
for _, event := range app.registeredEvents {
reactor.Unsubscribe(event, app.changeChan)
}
// Kill the main loop // Kill the main loop
app.quitChan <- true app.quitChan <- true
@ -93,7 +95,6 @@ func (app *ExtApplication) stop() {
close(app.blockChan) close(app.blockChan)
close(app.quitChan) close(app.quitChan)
close(app.changeChan)
app.container.Destroy() app.container.Destroy()
} }
@ -108,26 +109,37 @@ out:
if block, ok := block.Resource.(*ethchain.Block); ok { if block, ok := block.Resource.(*ethchain.Block); ok {
app.container.NewBlock(block) app.container.NewBlock(block)
} }
case object := <-app.changeChan: case msg := <-app.messageChan:
if stateObject, ok := object.Resource.(*ethstate.StateObject); ok { if messages, ok := msg.Resource.(ethstate.Messages); ok {
app.container.ObjectChanged(stateObject) for id, filter := range app.filters {
} else if storageObject, ok := object.Resource.(*ethstate.StorageState); ok { msgs := filter.FilterMessages(messages)
app.container.StorageChanged(storageObject) if len(msgs) > 0 {
app.container.Messages(msgs, id)
}
}
} }
} }
} }
} }
func (app *ExtApplication) Watch(addr, storageAddr string) { func (self *ExtApplication) Watch(filterOptions map[string]interface{}, identifier string) {
var event string self.filters[identifier] = ethchain.NewFilterFromMap(filterOptions, self.eth)
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)
} }
app.registeredEvents = append(app.registeredEvents, event) 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() + "}"
}
return string(b)
} }

View File

@ -2,31 +2,41 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"math/big"
"os"
"strconv"
"strings"
"time"
"github.com/ethereum/eth-go" "github.com/ethereum/eth-go"
"github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethdb"
"github.com/ethereum/eth-go/ethlog" "github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/eth-go/ethminer" "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/ethutil"
"github.com/ethereum/eth-go/ethwire" "github.com/ethereum/eth-go/ethwire"
"github.com/ethereum/go-ethereum/utils" "github.com/ethereum/go-ethereum/utils"
"github.com/go-qml/qml" "gopkg.in/qml.v1"
"math/big"
"strconv"
"strings"
"time"
) )
var logger = ethlog.NewLogger("GUI") var logger = ethlog.NewLogger("GUI")
type plugin struct {
Name string `json:"name"`
Path string `json:"path"`
}
type Gui struct { type Gui struct {
// The main application window // The main application window
win *qml.Window win *qml.Window
// QML Engine // QML Engine
engine *qml.Engine engine *qml.Engine
component *qml.Common component *qml.Common
qmlDone bool
// The ethereum interface // The ethereum interface
eth *eth.Ethereum eth *eth.Ethereum
@ -35,14 +45,17 @@ type Gui struct {
txDb *ethdb.LDBDatabase txDb *ethdb.LDBDatabase
pub *ethpub.PEthereum
logLevel ethlog.LogLevel logLevel ethlog.LogLevel
open bool open bool
pipe *ethpipe.JSPipe
Session string Session string
clientIdentity *ethwire.SimpleClientIdentity clientIdentity *ethwire.SimpleClientIdentity
config *ethutil.ConfigManager config *ethutil.ConfigManager
plugins map[string]plugin
miner *ethminer.Miner miner *ethminer.Miner
} }
@ -53,9 +66,17 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, clientIden
panic(err) 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))
return &Gui{eth: ethereum, txDb: db, pub: pub, logLevel: ethlog.LogLevel(logLevel), Session: session, open: false, clientIdentity: clientIdentity, config: config} json.Unmarshal([]byte(data), &gui.plugins)
return gui
} }
func (gui *Gui) Start(assetPath string) { func (gui *Gui) Start(assetPath string) {
@ -64,22 +85,20 @@ func (gui *Gui) Start(assetPath string) {
// Register ethereum functions // Register ethereum functions
qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ 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 // Create a new QML engine
gui.engine = qml.NewEngine() gui.engine = qml.NewEngine()
context := gui.engine.Context() context := gui.engine.Context()
gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath)
// Expose the eth library and the ui library to QML // Expose the eth library and the ui library to QML
context.SetVar("eth", gui) context.SetVar("gui", gui)
context.SetVar("pub", gui.pub) context.SetVar("eth", gui.uiLib)
gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath)
context.SetVar("ui", gui.uiLib)
// Load the main QML interface // Load the main QML interface
data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) data, _ := ethutil.Config.Db.Get([]byte("KeyRing"))
@ -102,11 +121,13 @@ func (gui *Gui) Start(assetPath string) {
logger.Infoln("Starting GUI") logger.Infoln("Starting GUI")
gui.open = true gui.open = true
win.Show() win.Show()
// only add the gui logger after window is shown otherwise slider wont be shown // only add the gui logger after window is shown otherwise slider wont be shown
if addlog { if addlog {
ethlog.AddLogSystem(gui) ethlog.AddLogSystem(gui)
} }
win.Wait() win.Wait()
// need to silence gui logger after window closed otherwise logsystem hangs (but do not save loglevel) // need to silence gui logger after window closed otherwise logsystem hangs (but do not save loglevel)
gui.logLevel = ethlog.Silence gui.logLevel = ethlog.Silence
gui.open = false gui.open = false
@ -118,6 +139,9 @@ func (gui *Gui) Stop() {
gui.open = false gui.open = false
gui.win.Hide() gui.win.Hide()
} }
gui.uiLib.jsEngine.Stop()
logger.Infoln("Stopped") logger.Infoln("Stopped")
} }
@ -141,18 +165,54 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
return nil, err return nil, err
} }
win := gui.createWindow(component) gui.win = gui.createWindow(component)
go func() { gui.update()
gui.setInitialBlockChain()
gui.loadAddressBook()
gui.readPreviousTransactions()
gui.setPeerInfo()
}()
go gui.update() return gui.win, nil
}
return win, nil func (self *Gui) DumpState(hash, path string) {
var stateDump []byte
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))
}
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) { func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
@ -218,24 +278,24 @@ type address struct {
} }
func (gui *Gui) loadAddressBook() { 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 { if nameReg != nil {
nameReg.EachStorage(func(name string, value *ethutil.Value) { nameReg.EachStorage(func(name string, value *ethutil.Value) {
if name[0] != 0 { if name[0] != 0 {
value.Decode() 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() { func (gui *Gui) insertTransaction(window string, tx *ethchain.Transaction) {
it := gui.txDb.Db().NewIterator(nil, nil) nameReg := ethpipe.New(gui.eth).World().Config().Get("NameReg")
addr := gui.address() addr := gui.address()
for it.Next() {
tx := ethchain.NewTransactionFromBytes(it.Value())
var inout string var inout string
if bytes.Compare(tx.Sender(), addr) == 0 { if bytes.Compare(tx.Sender(), addr) == 0 {
@ -244,18 +304,58 @@ func (gui *Gui) readPreviousTransactions() {
inout = "recv" inout = "recv"
} }
gui.win.Root().Call("addTx", ethpub.NewPTx(tx), inout) 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 {
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.insertTransaction("post", tx)
} }
it.Release() it.Release()
} }
func (gui *Gui) processBlock(block *ethchain.Block, initial bool) { func (gui *Gui) processBlock(block *ethchain.Block, initial bool) {
name := ethpub.FindNameInNameReg(gui.eth.StateManager(), block.Coinbase) name := strings.Trim(gui.pipe.World().Config().Get("NameReg").Storage(block.Coinbase).Str(), "\x00")
b := ethpub.NewPBlock(block) b := ethpipe.NewJSBlock(block)
b.Name = name 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) { func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) {
@ -280,78 +380,81 @@ func (self *Gui) getObjectByName(objectName string) qml.Object {
// Simple go routine function that updates the list of peers in the GUI // Simple go routine function that updates the list of peers in the GUI
func (gui *Gui) update() { func (gui *Gui) update() {
reactor := gui.eth.Reactor() // We have to wait for qml to be done loading all the windows.
for !gui.qmlDone {
time.Sleep(500 * time.Millisecond)
}
go func() {
go gui.setInitialBlockChain()
gui.loadAddressBook()
gui.setPeerInfo()
gui.readPreviousTransactions()
}()
for _, plugin := range gui.plugins {
gui.win.Root().Call("addPlugin", plugin.Path, "")
}
var ( var (
blockChan = make(chan ethutil.React, 1) blockChan = make(chan ethreact.Event, 100)
txChan = make(chan ethutil.React, 1) txChan = make(chan ethreact.Event, 100)
objectChan = make(chan ethutil.React, 1) objectChan = make(chan ethreact.Event, 100)
peerChan = make(chan ethutil.React, 1) peerChan = make(chan ethreact.Event, 100)
chainSyncChan = make(chan ethutil.React, 1) chainSyncChan = make(chan ethreact.Event, 100)
miningChan = make(chan ethutil.React, 1) miningChan = make(chan ethreact.Event, 100)
) )
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 := ethpub.EthereumConfig(gui.eth.StateManager()).NameReg()
if nameReg != nil {
reactor.Subscribe("object:"+string(nameReg.Address()), objectChan)
}
reactor.Subscribe("peerList", peerChan)
peerUpdateTicker := time.NewTicker(5 * time.Second) peerUpdateTicker := time.NewTicker(5 * time.Second)
generalUpdateTicker := time.NewTicker(1 * time.Second) generalUpdateTicker := time.NewTicker(1 * time.Second)
state := gui.eth.StateManager().TransState() state := gui.eth.StateManager().TransState()
unconfirmedFunds := new(big.Int) 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()) gui.getObjectByName("syncProgressIndicator").Set("visible", !gui.eth.IsUpToDate())
lastBlockLabel := gui.getObjectByName("lastBlockLabel") lastBlockLabel := gui.getObjectByName("lastBlockLabel")
go func() {
for { for {
select { select {
case b := <-blockChan: case b := <-blockChan:
block := b.Resource.(*ethchain.Block) block := b.Resource.(*ethchain.Block)
gui.processBlock(block, false) gui.processBlock(block, false)
if bytes.Compare(block.Coinbase, gui.address()) == 0 { if bytes.Compare(block.Coinbase, gui.address()) == 0 {
gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.address()).Amount, nil) gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.address()).Balance, nil)
} }
case txMsg := <-txChan: case txMsg := <-txChan:
tx := txMsg.Resource.(*ethchain.Transaction) tx := txMsg.Resource.(*ethchain.Transaction)
if txMsg.Event == "newTx:pre" { if txMsg.Name == "newTx:pre" {
object := state.GetAccount(gui.address()) object := state.GetAccount(gui.address())
if bytes.Compare(tx.Sender(), gui.address()) == 0 { if bytes.Compare(tx.Sender(), gui.address()) == 0 {
gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "send")
gui.txDb.Put(tx.Hash(), tx.RlpEncode())
unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) unconfirmedFunds.Sub(unconfirmedFunds, tx.Value)
} else if bytes.Compare(tx.Recipient, gui.address()) == 0 { } else if bytes.Compare(tx.Recipient, gui.address()) == 0 {
gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "recv")
gui.txDb.Put(tx.Hash(), tx.RlpEncode())
unconfirmedFunds.Add(unconfirmedFunds, tx.Value) unconfirmedFunds.Add(unconfirmedFunds, tx.Value)
} }
gui.setWalletValue(object.Amount, unconfirmedFunds) gui.setWalletValue(object.Balance, unconfirmedFunds)
gui.insertTransaction("pre", tx)
} else { } else {
object := state.GetAccount(gui.address()) object := state.GetAccount(gui.address())
if bytes.Compare(tx.Sender(), gui.address()) == 0 { if bytes.Compare(tx.Sender(), gui.address()) == 0 {
object.SubAmount(tx.Value) object.SubAmount(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 { } else if bytes.Compare(tx.Recipient, gui.address()) == 0 {
object.AddAmount(tx.Value) object.AddAmount(tx.Value)
gui.getObjectByName("transactionView").Call("addTx", ethpipe.NewJSTx(tx), "recv")
gui.txDb.Put(tx.Hash(), tx.RlpEncode())
} }
gui.setWalletValue(object.Amount, nil) gui.setWalletValue(object.Balance, nil)
state.UpdateStateObject(object) state.UpdateStateObject(object)
} }
@ -366,12 +469,11 @@ func (gui *Gui) update() {
case <-peerUpdateTicker.C: case <-peerUpdateTicker.C:
gui.setPeerInfo() gui.setPeerInfo()
case msg := <-miningChan: case msg := <-miningChan:
if msg.Event == "miner:start" { if msg.Name == "miner:start" {
gui.miner = msg.Resource.(*ethminer.Miner) gui.miner = msg.Resource.(*ethminer.Miner)
} else { } else {
gui.miner = nil gui.miner = nil
} }
case <-generalUpdateTicker.C: case <-generalUpdateTicker.C:
statusText := "#" + gui.eth.BlockChain().CurrentBlock.Number.String() statusText := "#" + gui.eth.BlockChain().CurrentBlock.Number.String()
if gui.miner != nil { if gui.miner != nil {
@ -383,13 +485,33 @@ func (gui *Gui) update() {
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() { func (gui *Gui) setPeerInfo() {
gui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", gui.eth.PeerCount(), gui.eth.MaxPeers)) gui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", gui.eth.PeerCount(), gui.eth.MaxPeers))
gui.win.Root().Call("resetPeers") gui.win.Root().Call("resetPeers")
for _, peer := range gui.pub.GetPeers() { for _, peer := range gui.pipe.Peers() {
gui.win.Root().Call("addPeer", peer) gui.win.Root().Call("addPeer", peer)
} }
} }
@ -402,18 +524,19 @@ func (gui *Gui) address() []byte {
return gui.eth.KeyManager().Address() return gui.eth.KeyManager().Address()
} }
func (gui *Gui) RegisterName(name string) { func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (*ethpipe.JSReceipt, error) {
name = fmt.Sprintf("\"register\"\n\"%s\"", name) var data string
if len(recipient) == 0 {
gui.pub.Transact(gui.privateKey(), "NameReg", "", "10000", "10000000000000", name) 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) Transact(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) { return gui.pipe.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data)
return gui.pub.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data)
}
func (gui *Gui) Create(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) {
return gui.pub.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data)
} }
func (gui *Gui) SetCustomIdentifier(customIdentifier string) { func (gui *Gui) SetCustomIdentifier(customIdentifier string) {
@ -435,6 +558,20 @@ func (gui *Gui) GetLogLevel() ethlog.LogLevel {
return gui.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 // this extra function needed to give int typecast value to gui widget
// that sets initial loglevel to default // that sets initial loglevel to default
func (gui *Gui) GetLogLevelInt() int { 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 // Print function that logs directly to the GUI
func (gui *Gui) printLog(s string) { func (gui *Gui) printLog(s string) {
/*
str := strings.TrimRight(s, "\n") str := strings.TrimRight(s, "\n")
lines := strings.Split(str, "\n") lines := strings.Split(str, "\n")
view := gui.getObjectByName("infoView")
for _, line := range lines { for _, line := range lines {
gui.win.Root().Call("addLog", line) view.Call("addLog", line)
} }
*/
} }

View File

@ -1,18 +1,22 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"github.com/ethereum/eth-go/ethchain" "fmt"
"github.com/ethereum/eth-go/ethpub"
"github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
"github.com/go-qml/qml"
"github.com/howeyc/fsnotify"
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath" "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 { type HtmlApplication struct {
@ -41,7 +45,7 @@ func (app *HtmlApplication) Create() error {
return errors.New("Ethereum package not yet supported") return errors.New("Ethereum package not yet supported")
// TODO // TODO
ethutil.OpenPackage(app.path) //ethutil.OpenPackage(app.path)
} }
win := component.CreateWindow(nil) win := component.CreateWindow(nil)
@ -118,18 +122,26 @@ func (app *HtmlApplication) Window() *qml.Window {
} }
func (app *HtmlApplication) NewBlock(block *ethchain.Block) { 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) app.webView.Call("onNewBlockCb", b)
} }
func (app *HtmlApplication) ObjectChanged(stateObject *ethstate.StateObject) { func (self *HtmlApplication) Messages(messages ethstate.Messages, id string) {
app.webView.Call("onObjectChangeCb", ethpub.NewPStateObject(stateObject)) var msgs []javascript.JSMessage
for _, m := range messages {
msgs = append(msgs, javascript.NewJSMessage(m))
} }
func (app *HtmlApplication) StorageChanged(storageObject *ethstate.StorageState) { b, _ := json.Marshal(msgs)
app.webView.Call("onStorageChangeCb", ethpub.NewPStorageState(storageObject))
self.webView.Call("onWatchedCb", string(b), id)
} }
func (app *HtmlApplication) Destroy() { func (app *HtmlApplication) Destroy() {
app.engine.Destroy() app.engine.Destroy()
} }
func (app *HtmlApplication) Post(data string, seed int) {
fmt.Println("about to call 'post'")
app.webView.Call("post", seed, data)
}

View File

@ -1,30 +1,23 @@
package main package main
import ( import (
"github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/go-ethereum/utils"
"github.com/go-qml/qml"
"os" "os"
"runtime" "runtime"
"github.com/ethereum/eth-go"
"github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/go-ethereum/utils"
"gopkg.in/qml.v1"
) )
const ( const (
ClientIdentifier = "Ethereal" ClientIdentifier = "Ethereal"
Version = "0.6.0" Version = "0.6.3"
) )
func main() { var ethereum *eth.Ethereum
runtime.GOMAXPROCS(runtime.NumCPU())
qml.Init(nil)
var interrupted = false
utils.RegisterInterrupt(func(os.Signal) {
interrupted = true
})
utils.HandleInterrupt()
func run() error {
// precedence: code-internal flag default < config file < environment variables < command line // precedence: code-internal flag default < config file < environment variables < command line
Init() // parsing command line Init() // parsing command line
@ -43,7 +36,7 @@ func main() {
clientIdentity := utils.NewClientIdentity(ClientIdentifier, Version, Identifier) 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 { if ShowGenesis {
utils.ShowGenesis(ethereum) utils.ShowGenesis(ethereum)
@ -61,6 +54,26 @@ func main() {
utils.StartEthereum(ethereum, UseSeed) utils.StartEthereum(ethereum, UseSeed)
// gui blocks the main thread // gui blocks the main thread
gui.Start(AssetPath) 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 // we need to run the interrupt callbacks in case gui is closed
// this skips if we got here by actual interrupt stopping the GUI // this skips if we got here by actual interrupt stopping the GUI
if !interrupted { if !interrupted {

View File

@ -1,12 +1,14 @@
package main package main
import ( import (
"fmt"
"runtime"
"github.com/ethereum/eth-go/ethchain" "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/ethstate"
"github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethutil"
"github.com/go-qml/qml" "gopkg.in/qml.v1"
"runtime"
) )
type QmlApplication struct { type QmlApplication struct {
@ -25,7 +27,7 @@ func (app *QmlApplication) Create() error {
path := string(app.path) 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 // 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:] path = app.path[1:]
} }
@ -47,16 +49,12 @@ func (app *QmlApplication) NewWatcher(quitChan chan bool) {
// Events // Events
func (app *QmlApplication) NewBlock(block *ethchain.Block) { 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) app.win.Call("onNewBlockCb", pblock)
} }
func (app *QmlApplication) ObjectChanged(stateObject *ethstate.StateObject) { func (self *QmlApplication) Messages(msgs ethstate.Messages, id string) {
app.win.Call("onObjectChangeCb", ethpub.NewPStateObject(stateObject)) fmt.Println("IMPLEMENT QML APPLICATION MESSAGES METHOD")
}
func (app *QmlApplication) StorageChanged(storageObject *ethstate.StorageState) {
app.win.Call("onStorageChangeCb", ethpub.NewPStorageState(storageObject))
} }
// Getters // Getters
@ -66,3 +64,5 @@ func (app *QmlApplication) Engine() *qml.Engine {
func (app *QmlApplication) Window() *qml.Window { func (app *QmlApplication) Window() *qml.Window {
return app.win return app.win
} }
func (app *QmlApplication) Post(data string, s int) {}

View File

@ -1,10 +1,20 @@
package main package main
import ( import (
"github.com/ethereum/eth-go" "bytes"
"github.com/ethereum/eth-go/ethutil" "fmt"
"github.com/go-qml/qml"
"path" "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/ethereum/go-ethereum/javascript"
"gopkg.in/qml.v1"
) )
type memAddr struct { type memAddr struct {
@ -14,6 +24,7 @@ type memAddr struct {
// UI Library that has some basic functionality exposed // UI Library that has some basic functionality exposed
type UiLib struct { type UiLib struct {
*ethpipe.JSPipe
engine *qml.Engine engine *qml.Engine
eth *eth.Ethereum eth *eth.Ethereum
connected bool connected bool
@ -22,10 +33,57 @@ type UiLib struct {
win *qml.Window win *qml.Window
Db *Debugger Db *Debugger
DbWindow *DebuggerWindow DbWindow *DebuggerWindow
jsEngine *javascript.JSRE
filterCallbacks map[int][]int
filters map[int]*GuiFilter
} }
func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { 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) { func (ui *UiLib) OpenQml(path string) {
@ -42,6 +100,10 @@ func (ui *UiLib) OpenHtml(path string) {
go app.run() go app.run()
} }
func (ui *UiLib) OpenBrowser() {
ui.OpenHtml("file://" + ui.AssetPath("ext/home.html"))
}
func (ui *UiLib) Muted(content string) { func (ui *UiLib) Muted(content string) {
component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml")) component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml"))
if err != nil { if err != nil {
@ -94,7 +156,96 @@ func (self *UiLib) StartDbWithCode(code string) {
func (self *UiLib) StartDebugger() { func (self *UiLib) StartDebugger() {
dbWindow := NewDebuggerWindow(self) dbWindow := NewDebuggerWindow(self)
//self.DbWindow = dbWindow
dbWindow.Show() 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,
)
}

View File

@ -1,11 +1,13 @@
package main package main
import ( import (
"github.com/ethereum/eth-go"
"github.com/ethereum/go-ethereum/ethereum/repl"
"github.com/ethereum/go-ethereum/utils"
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/ethereum/eth-go"
"github.com/ethereum/go-ethereum/ethereum/repl"
"github.com/ethereum/go-ethereum/javascript"
"github.com/ethereum/go-ethereum/utils"
) )
func InitJsConsole(ethereum *eth.Ethereum) { func InitJsConsole(ethereum *eth.Ethereum) {
@ -25,7 +27,7 @@ func ExecJsFile(ethereum *eth.Ethereum, InputFile string) {
if err != nil { if err != nil {
logger.Fatalln(err) logger.Fatalln(err)
} }
re := ethrepl.NewJSRE(ethereum) re := javascript.NewJSRE(ethereum)
utils.RegisterInterrupt(func(os.Signal) { utils.RegisterInterrupt(func(os.Signal) {
re.Stop() re.Stop()
}) })

View File

@ -3,10 +3,11 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"github.com/ethereum/eth-go/ethlog"
"os" "os"
"os/user" "os/user"
"path" "path"
"github.com/ethereum/eth-go/ethlog"
) )
var Identifier string var Identifier string
@ -31,6 +32,9 @@ var LogFile string
var ConfigFile string var ConfigFile string
var DebugFile string var DebugFile string
var LogLevel int var LogLevel int
var Dump bool
var DumpHash string
var DumpNumber int
// flags specific to cli client // flags specific to cli client
var StartMining bool var StartMining bool
@ -71,6 +75,10 @@ func Init() {
flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0") 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.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(&StartMining, "mine", false, "start dagger mining")
flag.BoolVar(&StartJsConsole, "js", false, "launches javascript console") flag.BoolVar(&StartJsConsole, "js", false, "launches javascript console")

View File

@ -1,15 +1,19 @@
package main package main
import ( import (
"fmt"
"os"
"runtime"
"github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethlog" "github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethutil"
"github.com/ethereum/go-ethereum/utils" "github.com/ethereum/go-ethereum/utils"
"runtime"
) )
const ( const (
ClientIdentifier = "Ethereum(G)" ClientIdentifier = "Ethereum(G)"
Version = "0.6.0" Version = "0.6.3"
) )
var logger = ethlog.NewLogger("CLI") var logger = ethlog.NewLogger("CLI")
@ -23,7 +27,7 @@ func main() {
Init() // parsing command line Init() // parsing command line
// If the difftool option is selected ignore all other log output // If the difftool option is selected ignore all other log output
if DiffTool { if DiffTool || Dump {
LogLevel = 0 LogLevel = 0
} }
@ -46,6 +50,32 @@ func main() {
ethereum := utils.NewEthereum(db, clientIdentity, keyManager, UseUPnP, OutboundPort, MaxPeer) 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 { if ShowGenesis {
utils.ShowGenesis(ethereum) utils.ShowGenesis(ethereum)
} }

View File

@ -3,12 +3,14 @@ package ethrepl
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"github.com/ethereum/eth-go"
"github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/eth-go/ethutil"
"io" "io"
"os" "os"
"path" "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") var logger = ethlog.NewLogger("REPL")
@ -19,7 +21,7 @@ type Repl interface {
} }
type JSRepl struct { type JSRepl struct {
re *JSRE re *javascript.JSRE
prompt string prompt string
@ -34,7 +36,7 @@ func NewJSRepl(ethereum *eth.Ethereum) *JSRepl {
panic(err) panic(err)
} }
return &JSRepl{re: NewJSRE(ethereum), prompt: "> ", history: hist} return &JSRepl{re: javascript.NewJSRE(ethereum), prompt: "> ", history: hist}
} }
func (self *JSRepl) Start() { func (self *JSRepl) Start() {

View File

@ -115,8 +115,8 @@ L:
} }
func (self *JSRepl) PrintValue(v interface{}) { func (self *JSRepl) PrintValue(v interface{}) {
method, _ := self.re.vm.Get("prettyPrint") method, _ := self.re.Vm.Get("prettyPrint")
v, err := self.re.vm.ToValue(v) v, err := self.re.Vm.ToValue(v)
if err == nil { if err == nil {
method.Call(method, v) method.Call(method, v)
} }

View File

@ -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
}

View File

@ -1,30 +1,32 @@
package ethrepl package javascript
import ( import (
"fmt" "fmt"
"github.com/ethereum/eth-go"
"github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/eth-go/ethpub"
"github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
"github.com/ethereum/go-ethereum/utils"
"github.com/obscuren/otto"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"github.com/ethereum/eth-go"
"github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethlog"
"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"
) )
var jsrelogger = ethlog.NewLogger("JSRE") var jsrelogger = ethlog.NewLogger("JSRE")
type JSRE struct { type JSRE struct {
ethereum *eth.Ethereum ethereum *eth.Ethereum
vm *otto.Otto Vm *otto.Otto
lib *ethpub.PEthereum pipe *ethpipe.JSPipe
blockChan chan ethutil.React blockChan chan ethreact.Event
changeChan chan ethutil.React changeChan chan ethreact.Event
quitChan chan bool quitChan chan bool
objectCb map[string][]otto.Value objectCb map[string][]otto.Value
@ -33,9 +35,9 @@ type JSRE struct {
func (jsre *JSRE) LoadExtFile(path string) { func (jsre *JSRE) LoadExtFile(path string) {
result, err := ioutil.ReadFile(path) result, err := ioutil.ReadFile(path)
if err == nil { if err == nil {
jsre.vm.Run(result) jsre.Vm.Run(result)
} else { } 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{ re := &JSRE{
ethereum, ethereum,
otto.New(), otto.New(),
ethpub.NewPEthereum(ethereum), ethpipe.NewJSPipe(ethereum),
make(chan ethutil.React, 1), make(chan ethreact.Event, 10),
make(chan ethutil.React, 1), make(chan ethreact.Event, 10),
make(chan bool), make(chan bool),
make(map[string][]otto.Value), make(map[string][]otto.Value),
} }
// Init the JS lib // Init the JS lib
re.vm.Run(jsLib) re.Vm.Run(jsLib)
// Load extra javascript files // Load extra javascript files
re.LoadIntFile("string.js") 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" // We have to make sure that, whoever calls this, calls "Stop"
go re.mainLoop() 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() re.initStdFuncs()
@ -75,11 +81,11 @@ func NewJSRE(ethereum *eth.Ethereum) *JSRE {
} }
func (self *JSRE) Bind(name string, v interface{}) { 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) { 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 { func (self *JSRE) Require(file string) error {
@ -109,10 +115,6 @@ func (self *JSRE) Stop() {
} }
func (self *JSRE) mainLoop() { func (self *JSRE) mainLoop() {
// Subscribe to events
reactor := self.ethereum.Reactor()
reactor.Subscribe("newBlock", self.blockChan)
out: out:
for { for {
select { select {
@ -121,24 +123,12 @@ out:
case block := <-self.blockChan: case block := <-self.blockChan:
if _, ok := block.Resource.(*ethchain.Block); ok { 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() { func (self *JSRE) initStdFuncs() {
t, _ := self.vm.Get("eth") t, _ := self.Vm.Get("eth")
eth := t.Object() eth := t.Object()
eth.Set("watch", self.watch) eth.Set("watch", self.watch)
eth.Set("addPeer", self.addPeer) eth.Set("addPeer", self.addPeer)
@ -146,19 +136,51 @@ func (self *JSRE) initStdFuncs() {
eth.Set("stopMining", self.stopMining) eth.Set("stopMining", self.stopMining)
eth.Set("startMining", self.startMining) eth.Set("startMining", self.startMining)
eth.Set("execBlock", self.execBlock) eth.Set("execBlock", self.execBlock)
eth.Set("dump", self.dump)
} }
/* /*
* The following methods are natively implemented javascript functions * 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 { 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 return v
} }
func (self *JSRE) startMining(call otto.FunctionCall) otto.Value { 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 return v
} }
@ -211,7 +233,7 @@ func (self *JSRE) require(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue() return otto.UndefinedValue()
} }
t, _ := self.vm.Get("exports") t, _ := self.Vm.Get("exports")
return t return t
} }

View File

@ -1,4 +1,4 @@
package ethrepl package javascript
const jsLib = ` const jsLib = `
function pp(object) { function pp(object) {

138
javascript/types.go Normal file
View File

@ -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
}

View File

@ -1,25 +1,27 @@
package utils package utils
import ( import (
"bitbucket.org/kardianos/osext"
"fmt" "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" "io"
"log" "log"
"os" "os"
"os/signal" "os/signal"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"time" "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") var logger = ethlog.NewLogger("CLI")
@ -127,6 +129,7 @@ func NewDatabase() ethutil.Database {
} }
func NewClientIdentity(clientIdentifier, version, customIdentifier string) *ethwire.SimpleClientIdentity { func NewClientIdentity(clientIdentifier, version, customIdentifier string) *ethwire.SimpleClientIdentity {
logger.Infoln("identity created")
return ethwire.NewSimpleClientIdentity(clientIdentifier, version, customIdentifier) 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) { 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 var err error
switch { switch {
@ -226,7 +228,7 @@ func KeyTasks(keyManager *ethcrypto.KeyManager, KeyRing string, GenAddr bool, Se
func StartRpc(ethereum *eth.Ethereum, RpcPort int) { func StartRpc(ethereum *eth.Ethereum, RpcPort int) {
var err error var err error
ethereum.RpcServer, err = ethrpc.NewJsonRpcServer(ethpub.NewPEthereum(ethereum), RpcPort) ethereum.RpcServer, err = ethrpc.NewJsonRpcServer(ethpipe.NewJSPipe(ethereum), RpcPort)
if err != nil { if err != nil {
logger.Errorf("Could not start RPC interface (port %v): %v", RpcPort, err) logger.Errorf("Could not start RPC interface (port %v): %v", RpcPort, err)
} else { } else {
@ -243,21 +245,18 @@ func GetMiner() *ethminer.Miner {
func StartMining(ethereum *eth.Ethereum) bool { func StartMining(ethereum *eth.Ethereum) bool {
if !ethereum.Mining { if !ethereum.Mining {
ethereum.Mining = true ethereum.Mining = true
addr := ethereum.KeyManager().Address() addr := ethereum.KeyManager().Address()
go func() { go func() {
logger.Infoln("Start mining")
if miner == nil { if miner == nil {
miner = ethminer.NewDefaultMiner(addr, ethereum) miner = ethminer.NewDefaultMiner(addr, ethereum)
} }
// Give it some time to connect with peers // Give it some time to connect with peers
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
for !ethereum.IsUpToDate() { for !ethereum.IsUpToDate() {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
} }
logger.Infoln("Miner started")
miner.Start() miner.Start()
}() }()
RegisterInterrupt(func(os.Signal) { RegisterInterrupt(func(os.Signal) {
@ -268,12 +267,23 @@ func StartMining(ethereum *eth.Ethereum) bool {
return false 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 { func StopMining(ethereum *eth.Ethereum) bool {
if ethereum.Mining && miner != nil { if ethereum.Mining && miner != nil {
miner.Stop() miner.Stop()
logger.Infoln("Stopped mining")
logger.Infoln("Miner stopped")
ethereum.Mining = false ethereum.Mining = false
return true return true

View File

@ -1,9 +1,10 @@
package utils package utils
import ( import (
"math/big"
"github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethchain"
"github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethstate"
"math/big"
) )
type VMEnv struct { 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) Coinbase() []byte { return self.block.Coinbase }
func (self *VMEnv) Time() int64 { return self.block.Time } func (self *VMEnv) Time() int64 { return self.block.Time }
func (self *VMEnv) Difficulty() *big.Int { return self.block.Difficulty } 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) Value() *big.Int { return self.value }
func (self *VMEnv) State() *ethstate.State { return self.state } func (self *VMEnv) State() *ethstate.State { return self.state }