diff options
Diffstat (limited to 'ethereal')
-rw-r--r-- | ethereal/assets/ethereum.js | 115 | ||||
-rw-r--r-- | ethereal/assets/icon.png | bin | 0 -> 86700 bytes | |||
-rw-r--r-- | ethereal/assets/qml/newTransaction/_new_contract.qml | 7 | ||||
-rw-r--r-- | ethereal/assets/qml/newTransaction/_simple_send.qml | 6 | ||||
-rw-r--r-- | ethereal/assets/qml/wallet.qml | 5 | ||||
-rw-r--r-- | ethereal/assets/qml/webapp.qml | 130 | ||||
-rw-r--r-- | ethereal/assets/test.html | 55 | ||||
-rw-r--r-- | ethereal/ethereum.go | 2 | ||||
-rw-r--r-- | ethereal/ui/gui.go | 17 | ||||
-rw-r--r-- | ethereal/ui/library.go | 53 | ||||
-rw-r--r-- | ethereal/ui/ui_lib.go | 73 |
11 files changed, 424 insertions, 39 deletions
diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js new file mode 100644 index 000000000..fefad584a --- /dev/null +++ b/ethereal/assets/ethereum.js @@ -0,0 +1,115 @@ +// Helper function for generating pseudo callbacks and sending data to the QML part of the application +function postData(data, cb) { + data._seed = Math.floor(Math.random() * 1000000) + if(cb) { + eth._callbacks[data._seed] = cb; + } + + if(data.args === undefined) { + data.args = []; + } + + navigator.qt.postMessage(JSON.stringify(data)); +} + +// Main Ethereum library +window.eth = { + prototype: Object(), + + // Retrieve block + // + // Either supply a number or a string. Type is determent for the lookup method + // string - Retrieves the block by looking up the hash + // number - Retrieves the block by looking up the block number + getBlock: function(numberOrHash, cb) { + var func; + if(typeof numberOrHash == "string") { + func = "getBlockByHash"; + } else { + func = "getBlockByNumber"; + } + postData({call: func, args: [numberOrHash]}, cb); + }, + + // Create transaction + // + // Creates a transaction with the current account + // If no recipient is set, the Ethereum API will see it as a contract creation + createTx: function(recipient, value, gas, gasPrice, data, cb) { + postData({call: "createTx", args: [recipient, value, gas, gasPrice, data]}, cb); + }, + + getStorage: function(address, storageAddress, cb) { + postData({call: "getStorage", args: [address, storageAddress]}, cb); + }, + + getKey: function(cb) { + postData({call: "getKey"}, cb); + }, + + + on: function(event, cb) { + if(eth._onCallbacks[event] === undefined) { + eth._onCallbacks[event] = []; + } + + eth._onCallbacks[event].push(cb); + + return this + }, + off: function(event, cb) { + if(eth._onCallbacks[event] !== undefined) { + var callbacks = eth._onCallbacks[event]; + for(var i = 0; i < callbacks.length; i++) { + if(callbacks[i] === cb) { + delete callbacks[i]; + } + } + } + + return this + }, + + trigger: function(event, data) { + var callbacks = eth._onCallbacks[event]; + if(callbacks !== undefined) { + for(var i = 0; i < callbacks.length; i++) { + callbacks[i](data); + } + } + }, +} +window.eth._callbacks = {} +window.eth._onCallbacks = {} + +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] + } + } + + document.getElementById("debug").innerHTML += "<br>" + msg +} + +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) { + // Call the callback + cb(data.data); + // Remove the "trigger" callback + delete eth._callbacks[ev._seed]; + } + } + } +} diff --git a/ethereal/assets/icon.png b/ethereal/assets/icon.png Binary files differnew file mode 100644 index 000000000..73e0ceb75 --- /dev/null +++ b/ethereal/assets/icon.png diff --git a/ethereal/assets/qml/newTransaction/_new_contract.qml b/ethereal/assets/qml/newTransaction/_new_contract.qml index abaac1695..0794d3dcd 100644 --- a/ethereal/assets/qml/newTransaction/_new_contract.qml +++ b/ethereal/assets/qml/newTransaction/_new_contract.qml @@ -117,6 +117,7 @@ Component { TextArea { id: codeView + height: 300 anchors.topMargin: 5 Layout.fillWidth: true width: parent.width /2 @@ -135,18 +136,18 @@ Component { Button { id: txButton + /* enabled: false */ states: [ State { name: "READY" - PropertyChanges { target: txButton; enabled: true} + PropertyChanges { target: txButton; /*enabled: true*/} }, State { name: "NOTREADY" - PropertyChanges { target: txButton; enabled:false} + PropertyChanges { target: txButton; /*enabled:false*/} } ] text: "Send" - enabled: false onClicked: { //this.enabled = false var res = eth.createTx(txFuelRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) diff --git a/ethereal/assets/qml/newTransaction/_simple_send.qml b/ethereal/assets/qml/newTransaction/_simple_send.qml index 981766160..d460797ea 100644 --- a/ethereal/assets/qml/newTransaction/_simple_send.qml +++ b/ethereal/assets/qml/newTransaction/_simple_send.qml @@ -63,18 +63,18 @@ Component { } Button { id: txSimpleButton + /*enabled: false*/ states: [ State { name: "READY" - PropertyChanges { target: txSimpleButton; enabled: true} + PropertyChanges { target: txSimpleButton; /*enabled: true*/} }, State { name: "NOTREADY" - PropertyChanges { target: txSimpleButton; enabled: false} + PropertyChanges { target: txSimpleButton; /*enabled: false*/} } ] text: "Send" - enabled: false onClicked: { //this.enabled = false var res = eth.createTx(txSimpleRecipient.text, txSimpleValue.text,"","","") diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 37224c7b4..574fbef86 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -240,7 +240,10 @@ ApplicationWindow { id: openAppDialog title: "Open QML Application" onAccepted: { - ui.open(openAppDialog.fileUrl.toString()) + //ui.open(openAppDialog.fileUrl.toString()) + //ui.openHtml(Qt.resolvedUrl(ui.assetPath("test.html"))) + ui.openHtml(openAppDialog.fileUrl.toString()) + } } diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml new file mode 100644 index 000000000..9cf154e9b --- /dev/null +++ b/ethereal/assets/qml/webapp.qml @@ -0,0 +1,130 @@ +import QtQuick 2.0 +import QtWebKit 3.0 +import QtWebKit.experimental 1.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Window 2.1; +import Ethereum 1.0 + +ApplicationWindow { + id: window + title: "Ethereum" + width: 900 + height: 600 + minimumHeight: 300 + + property alias url: webview.url + property alias webView: webview + + Item { + objectName: "root" + id: root + anchors.fill: parent + state: "inspectorShown" + + WebView { + objectName: "webView" + id: webview + anchors.fill: parent + /* + anchors { + left: parent.left + right: parent.right + bottom: sizeGrip.top + top: parent.top + } + */ + + onTitleChanged: { window.title = title } + experimental.preferences.javascriptEnabled: true + experimental.preferences.navigatorQtObjectEnabled: true + experimental.preferences.developerExtrasEnabled: true + experimental.userScripts: [ui.assetPath("ethereum.js")] + experimental.onMessageReceived: { + //console.log("[onMessageReceived]: ", message.data) + var data = JSON.parse(message.data) + + switch(data.call) { + case "getBlockByNumber": + var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") + postData(data._seed, block) + break + case "getBlockByHash": + var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") + postData(data._seed, block) + break + case "createTx": + if(data.args.length < 5) { + postData(data._seed, null) + } else { + var tx = eth.createTx(data.args[0], data.args[1],data.args[2],data.args[3],data.args[4]) + postData(data._seed, tx) + } + break + case "getStorage": + if(data.args.length < 2) { + postData(data._seed, null) + } else { + var stateObject = eth.getStateObject(data.args[0]) + var storage = stateObject.getStorage(data.args[1]) + postData(data._seed, storage) + } + break + case "getKey": + var keys = eth.getKey() + postData(data._seed, keys) + break + } + } + function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) + } + + function onNewBlockCb(block) { + webview.experimental.postMessage(JSON.stringify({data: block, _event: "block:new"})) + } + } + + Rectangle { + id: sizeGrip + color: "gray" + visible: false + height: 10 + anchors { + left: root.left + right: root.right + } + y: Math.round(root.height * 2 / 3) + + MouseArea { + anchors.fill: parent + drag.target: sizeGrip + drag.minimumY: 0 + drag.maximumY: root.height + drag.axis: Drag.YAxis + } + } + + WebView { + id: inspector + visible: false + url: webview.experimental.remoteInspectorUrl + anchors { + left: root.left + right: root.right + top: sizeGrip.bottom + bottom: root.bottom + } + } + + states: [ + State { + name: "inspectorShown" + PropertyChanges { + target: inspector + url: webview.experimental.remoteInspectorUrl + } + } + ] + } +} diff --git a/ethereal/assets/test.html b/ethereal/assets/test.html new file mode 100644 index 000000000..beb888685 --- /dev/null +++ b/ethereal/assets/test.html @@ -0,0 +1,55 @@ +<html> +<head> +<title>jeffcoin</title> +<script type="text/javascript"> +var jefcoinAddr = "3dff537f51350239abc95c76a5864aa605259e7d" + +function createTransaction() { + var addr = document.querySelector("#addr").value; + var amount = document.querySelector("#amount").value; + + var data = "0x" + addr + "\n" + amount + eth.createTx(jefcoinAddr, 0, "10000000", "250", data, function(tx) { + debug("received tx hash:", tx) + }) +} + +function init() { + eth.getKey(function(key) { + eth.getStorage(jefcoinAddr, key, function(storage) { + document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; + }); + + eth.on("block:new", function() { + eth.getStorage(jefcoinAddr, key, function(storage) { + document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; + }); + }); + }); +} + +</script> +<style type="text/css"> +input[type="text"] { + width: 300px; +} +</style> +</head> + +<body onload="init();"> +<h1>Jeff Coin</h1> + +<img src="icon.png"> +<div id="currentAmount"></div> + +<div id="transactions"> + <input id="addr" type="text" placeholder="Receiver address"></input><br> + <input id="amount" type="text" placeholder="Amount"></input><br> + <button onclick="createTransaction();">Send Tx</button> +</div> + +<div id="debug" style="border: 1px solid block"></div> + +</body> +</html> + diff --git a/ethereal/ethereum.go b/ethereal/ethereum.go index 9cc52039d..0adb9f151 100644 --- a/ethereal/ethereum.go +++ b/ethereal/ethereum.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/ethereal/ui" "github.com/ethereum/go-ethereum/utils" - "github.com/niemeyer/qml" + "github.com/go-qml/qml" "log" "os" "os/signal" diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 1065b716e..80498d718 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" - "github.com/niemeyer/qml" + "github.com/go-qml/qml" "math/big" "strings" ) @@ -24,6 +24,18 @@ type Tx struct { Contract bool } +type Key struct { + Address string +} + +type KeyRing struct { + Keys []interface{} +} + +func NewKeyRing(keys []interface{}) *KeyRing { + return &KeyRing{Keys: keys} +} + func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { hash := hex.EncodeToString(tx.Hash()) sender := hex.EncodeToString(tx.Recipient) @@ -113,6 +125,7 @@ func (ui *Gui) Start(assetPath string) { } if err != nil { ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") + panic(err) } @@ -170,7 +183,7 @@ func (ui *Gui) update() { txChan := make(chan ethchain.TxMsg, 1) ui.eth.TxPool().Subscribe(txChan) - account := ui.eth.StateManager().GetAddrState(ui.addr).Account + account := ui.eth.StateManager().GetAddrState(ui.addr).Object unconfirmedFunds := new(big.Int) ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) for { diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 42aebcd87..5ca2b4273 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -6,11 +6,24 @@ import ( "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" - "github.com/obscuren/mutan" "github.com/obscuren/secp256k1-go" "strings" ) +type Contract struct { + object *ethchain.StateObject +} + +func NewContract(object *ethchain.StateObject) *Contract { + return &Contract{object: object} +} + +func (c *Contract) GetStorage(address string) string { + val := c.object.GetMem(ethutil.Big("0x" + address)) + + return val.BigInt().String() +} + type EthLib struct { stateManager *ethchain.StateManager blockChain *ethchain.BlockChain @@ -44,7 +57,17 @@ func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { return mnemonicString, fmt.Sprintf("%x", pair.Address()), fmt.Sprintf("%x", prv), fmt.Sprintf("%x", pub) } -func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { +func (lib *EthLib) GetKey() string { + return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) +} + +func (lib *EthLib) GetStateObject(address string) *Contract { + stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) + + return NewContract(stateObject) +} + +func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { var hash []byte var contractCreation bool if len(recipient) == 0 { @@ -64,25 +87,24 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin var tx *ethchain.Transaction // Compile and assemble the given data if contractCreation { - asm, errors := mutan.Compile(strings.NewReader(data), false) - if len(errors) > 0 { - var errs string - for _, er := range errors { - if er != nil { - errs += er.Error() - } - } - return "", fmt.Errorf(errs) + // Compile script + mainScript, initScript, err := utils.CompileScript(dataStr) + if err != nil { + return "", err } - code := ethutil.Assemble(asm...) - tx = ethchain.NewContractCreationTx(value, gasPrice, code) + tx = ethchain.NewContractCreationTx(value, gas, gasPrice, mainScript, initScript) } else { - tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, nil) + lines := strings.Split(dataStr, "\n") + var data []byte + for _, line := range lines { + data = append(data, ethutil.BigToBytes(ethutil.Big(line), 256)...) + } + + tx = ethchain.NewTransactionMessage(hash, value, gas, gasPrice, data) } acc := lib.stateManager.GetAddrState(keyPair.Address()) tx.Nonce = acc.Nonce - //acc.Nonce++ tx.Sign(keyPair.PrivateKey) lib.txPool.QueueTransaction(tx) @@ -102,7 +124,6 @@ func (lib *EthLib) GetBlock(hexHash string) *Block { } block := lib.blockChain.GetBlock(hash) - fmt.Println(block) return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} } diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index b2552cdce..08e2267a7 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -6,14 +6,12 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" - "github.com/niemeyer/qml" - "github.com/obscuren/mutan" - "math/big" + "github.com/ethereum/go-ethereum/utils" + "github.com/go-qml/qml" "os" "path" "path/filepath" "runtime" - "strings" ) type memAddr struct { @@ -53,6 +51,56 @@ func (ui *UiLib) Open(path string) { }() } +func (ui *UiLib) OpenHtml(path string) { + component, err := ui.engine.LoadFile(ui.AssetPath("qml/webapp.qml")) + if err != nil { + ethutil.Config.Log.Debugln(err) + + return + } + win := component.CreateWindow(nil) + if filepath.Ext(path) == "eth" { + fmt.Println("Ethereum package not yet supported") + + return + + // TODO + ethutil.OpenPackage(path) + } + win.Set("url", path) + + go func() { + blockChan := make(chan ethutil.React, 1) + quitChan := make(chan bool) + + go func() { + out: + for { + select { + case <-quitChan: + ui.eth.Reactor().Unsubscribe("newBlock", blockChan) + break out + case block := <-blockChan: + if block, ok := block.Resource.(*ethchain.Block); ok { + b := &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} + win.ObjectByName("webView").Call("onNewBlockCb", b) + } + } + } + + // Clean up + close(blockChan) + close(quitChan) + }() + ui.eth.Reactor().Subscribe("newBlock", blockChan) + + win.Show() + win.Wait() + + quitChan <- true + }() +} + func (ui *UiLib) Connect(button qml.Object) { if !ui.connected { ui.eth.Start() @@ -99,27 +147,26 @@ func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) state := ui.eth.BlockChain().CurrentBlock.State() mainInput, _ := ethutil.PreProcess(data) - asm, err := mutan.Compile(strings.NewReader(mainInput), false) + callerScript, err := utils.Compile(mainInput) if err != nil { - fmt.Println(err) - for _, e := range err { - ui.win.Root().Call("addDebugMessage", e.Error()) - } + ethutil.Config.Log.Debugln(err) + + return } - callerScript := ethutil.Assemble(asm...) dis := ethchain.Disassemble(callerScript) ui.win.Root().Call("clearAsm") + for _, str := range dis { ui.win.Root().Call("setAsm", str) } - callerTx := ethchain.NewContractCreationTx(ethutil.Big(valueStr), ethutil.Big(gasPriceStr), callerScript) + callerTx := ethchain.NewContractCreationTx(ethutil.Big(valueStr), ethutil.Big(gasStr), ethutil.Big(gasPriceStr), callerScript, nil) // Contract addr as test address keyPair := ethutil.Config.Db.GetKeys()[0] - account := ui.eth.StateManager().GetAddrState(keyPair.Address()).Account + account := ui.eth.StateManager().GetAddrState(keyPair.Address()).Object c := ethchain.MakeContract(callerTx, state) - callerClosure := ethchain.NewClosure(account, c, c.Script(), state, ethutil.Big(gasStr), new(big.Int)) + callerClosure := ethchain.NewClosure(account, c, c.Script(), state, ethutil.Big(gasStr), ethutil.Big(gasPriceStr), ethutil.Big(valueStr)) block := ui.eth.BlockChain().CurrentBlock vm := ethchain.NewVm(state, ethchain.RuntimeVars{ |