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

... Ethereum ...

+ + + + diff --git a/ethereal/assets/ext/messaging.js b/ethereal/assets/ext/messaging.js new file mode 100644 index 0000000000..e7bc630202 --- /dev/null +++ b/ethereal/assets/ext/messaging.js @@ -0,0 +1,117 @@ +function handleMessage(message) { + console.log("[onMessageReceived]: ", message.data) + // TODO move to messaging.js + var data = JSON.parse(message.data) + + try { + switch(data.call) { + case "getCoinBase": + postData(data._seed, eth.getCoinBase()) + + break + case "getIsListening": + postData(data._seed, eth.getIsListening()) + + break + case "getIsMining": + postData(data._seed, eth.getIsMining()) + + break + case "getPeerCount": + postData(data._seed, eth.getPeerCount()) + + break + + case "getTxCountAt": + require(1) + postData(data._seed, eth.getTxCountAt(data.args[0])) + + break + case "getBlockByNumber": + var block = eth.getBlock(data.args[0]) + postData(data._seed, block) + + break + case "getBlockByHash": + var block = eth.getBlock(data.args[0]) + postData(data._seed, block) + + break + case "transact": + require(5) + + var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5]) + postData(data._seed, tx) + + break + case "create": + postData(data._seed, null) + + break + case "getStorage": + require(2); + + var stateObject = eth.getStateObject(data.args[0]) + var storage = stateObject.getStorage(data.args[1]) + postData(data._seed, storage) + + break + case "getStateKeyVals": + require(1); + var stateObject = eth.getStateObject(data.args[0]).stateKeyVal(true) + postData(data._seed,stateObject) + + break + case "getTransactionsFor": + require(1); + var txs = eth.getTransactionsFor(data.args[0], true) + postData(data._seed, txs) + + break + case "getBalance": + require(1); + + postData(data._seed, eth.getStateObject(data.args[0]).value()); + + break + case "getKey": + var key = eth.getKey().privateKey; + + postData(data._seed, key) + break + case "watch": + require(1) + eth.watch(data.args[0], data.args[1]); + break + case "disconnect": + require(1) + postData(data._seed, null) + break; + case "set": + console.log("'Set' has been depcrecated") + /* + for(var key in data.args) { + if(webview.hasOwnProperty(key)) { + window[key] = data.args[key]; + } + } + */ + break; + case "getSecretToAddress": + require(1) + postData(data._seed, eth.secretToAddress(data.args[0])) + break; + case "debug": + console.log(data.args[0]); + break; + } + } catch(e) { + console.log(data.call + ": " + e) + + postData(data._seed, null); + } +} + +function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) +} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index eef49824f1..e3ef148b0b 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -26,6 +26,27 @@ ApplicationWindow { shortcut: "Ctrl+o" onTriggered: openAppDialog.open() } + + MenuItem { + text: "Browser" + onTriggered: ui.openBrowser() + } + + MenuSeparator {} + + MenuItem { + text: "Import key" + shortcut: "Ctrl+i" + onTriggered: importDialog.open() + } + + MenuItem { + text: "Export keys" + shortcut: "Ctrl+e" + onTriggered: exportDialog.open() + } + + //MenuSeparator {} } Menu { @@ -35,6 +56,13 @@ ApplicationWindow { shortcut: "Ctrl+d" onTriggered: ui.startDebugger() } + + MenuItem { + text: "Import Tx" + onTriggered: { + txImportDialog.visible = true + } + } } Menu { @@ -77,6 +105,7 @@ ApplicationWindow { historyView.visible = false newTxView.visible = false infoView.visible = false + pendingTxView.visible = false view.visible = true //root.title = "Ethereal - " = view.title } @@ -140,6 +169,17 @@ ApplicationWindow { } } } + + Image { + source: "../tx.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(pendingTxView) + } + } + } } } @@ -344,6 +384,28 @@ ApplicationWindow { } } + Rectangle { + anchors.fill: parent + visible: false + id: pendingTxView + property var title: "Pending Transactions" + + 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 + } + } + /* signal addPlugin(string name) Component { @@ -375,9 +437,7 @@ ApplicationWindow { //ui.open(openAppDialog.fileUrl.toString()) //ui.openHtml(Qt.resolvedUrl(ui.assetPath("test.html"))) var path = openAppDialog.fileUrl.toString() - console.log(path) var ext = path.split('.').pop() - console.log(ext) if(ext == "html" || ext == "htm") { ui.openHtml(path) }else if(ext == "qml"){ @@ -386,6 +446,22 @@ ApplicationWindow { } } + FileDialog { + id: exportDialog + title: "Export keys" + onAccepted: { + } + } + + FileDialog { + id: importDialog + title: "Import key" + onAccepted: { + var path = this.fileUrl.toString() + ui.importKey(path) + } + } + statusBar: StatusBar { height: 30 RowLayout { @@ -465,6 +541,36 @@ ApplicationWindow { } } + Window { + id: txImportDialog + minimumWidth: 270 + maximumWidth: 270 + maximumHeight: 50 + minimumHeight: 50 + TextField { + id: txImportField + width: 170 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + onAccepted: { + } + } + Button { + anchors.left: txImportField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Import" + onClicked: { + eth.importTx(txImportField.text) + txImportField.visible = false + } + } + Component.onCompleted: { + addrField.focus = true + } + } + Window { id: popup visible: false @@ -660,7 +766,7 @@ ApplicationWindow { anchors.left: aboutIcon.right anchors.leftMargin: 10 font.pointSize: 12 - text: "

Ethereal


Development

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

Ethereal - Adrastea


Development

Jeffrey Wilcke
Maran Hidskes
Viktor Trón
" } } @@ -684,7 +790,7 @@ ApplicationWindow { walletValueLabel.text = value } - function addTx(tx, inout) { + function addTx(type, tx, inout) { var isContract if (tx.contract == true){ isContract = "Yes" @@ -692,13 +798,19 @@ ApplicationWindow { isContract = "No" } - var address; - if(inout == "recv") { - address = tx.sender; - } else { - address = tx.address; + + if(type == "post") { + 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}) + } else if(type == "pre") { + pendingTxModel.insert(0, {hash: tx.hash, to: tx.address, from: tx.sender, value: tx.value, contract: isContract}) } - txModel.insert(0, {inout: inout, hash: tx.hash, address: address, value: tx.value, contract: isContract}) } function addBlock(block, initial) { @@ -714,7 +826,7 @@ ApplicationWindow { 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{ + } 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)}) } } @@ -770,7 +882,7 @@ ApplicationWindow { // ****************************************** Window { id: peerWindow - //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint + //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint height: 200 width: 700 Rectangle { @@ -897,10 +1009,10 @@ ApplicationWindow { placeholderText: "Gas" text: "500" /* - onTextChanged: { - contractFormReady() - } - */ + onTextChanged: { + contractFormReady() + } + */ } Label { id: atLabel @@ -914,10 +1026,10 @@ ApplicationWindow { text: "10" validator: RegExpValidator { regExp: /\d*/ } /* - onTextChanged: { - contractFormReady() - } - */ + onTextChanged: { + contractFormReady() + } + */ } ComboBox { @@ -962,7 +1074,7 @@ ApplicationWindow { var gasPrice = txGasPrice.text + denomModel.get(gasDenom.currentIndex).zeros; var res = eth.create(txFuelRecipient.text, value, txGas.text, gasPrice, codeView.text) if(res[1]) { - txResult.text = "Your contract could not be send over the network:\n" + txResult.text = "Your contract could not be sent over the network:\n" txResult.text += res[1].error() txResult.text += "" mainContractColumn.state = "ERROR" diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 5e4c035d8d..15177e3fd0 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -21,19 +21,54 @@ ApplicationWindow { id: root anchors.fill: parent state: "inspectorShown" + TextField { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + id: uriNav + //text: webview.url + + Keys.onReturnPressed: { + var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/ + + var uri = this.text; + if(reg.test(uri)) { + this.text.replace(reg, function(match, pre, domain, path) { + uri = pre; + + var lookup = eth.lookupDomain(domain.substring(0, domain.length - 4)); + var ip = []; + for(var i = 0, l = lookup.length; i < l; i++) { + ip.push(lookup.charCodeAt(i)) + } + + if(ip.length != 0) { + uri += ip.join("."); + } else { + uri += domain; + } + + uri += path; + }); + } + + console.log("connecting to ...", uri) + + webview.url = uri; + } + } WebView { objectName: "webView" id: webview - anchors.fill: parent - /* - anchors { - left: parent.left - right: parent.right - bottom: sizeGrip.top - top: parent.top - } - */ + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + top: uriNav.bottom + } onTitleChanged: { window.title = title } experimental.preferences.javascriptEnabled: true experimental.preferences.navigatorQtObjectEnabled: true @@ -46,50 +81,50 @@ ApplicationWindow { try { switch(data.call) { - case "getCoinBase": - postData(data._seed, eth.getCoinBase()) + case "getCoinBase": + postData(data._seed, eth.getCoinBase()) - break - case "getIsListening": - postData(data._seed, eth.getIsListening()) + break + case "getIsListening": + postData(data._seed, eth.getIsListening()) - break - case "getIsMining": - postData(data._seed, eth.getIsMining()) + break + case "getIsMining": + postData(data._seed, eth.getIsMining()) - break - case "getPeerCount": - postData(data._seed, eth.getPeerCount()) + break + case "getPeerCount": + postData(data._seed, eth.getPeerCount()) - break + break - case "getTxCountAt": - require(1) - postData(data._seed, eth.getTxCountAt(data.args[0])) + case "getTxCountAt": + require(1) + postData(data._seed, eth.getTxCountAt(data.args[0])) - break - case "getBlockByNumber": + break + case "getBlockByNumber": var block = eth.getBlock(data.args[0]) postData(data._seed, block) break - case "getBlockByHash": + case "getBlockByHash": var block = eth.getBlock(data.args[0]) postData(data._seed, block) break - case "transact": + case "transact": require(5) var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5]) postData(data._seed, tx) break - case "create": + case "create": postData(data._seed, null) break - case "getStorage": + case "getStorage": require(2); var stateObject = eth.getStateObject(data.args[0]) @@ -97,52 +132,52 @@ ApplicationWindow { postData(data._seed, storage) break - case "getStateKeyVals": - require(1); - var stateObject = eth.getStateObject(data.args[0]).stateKeyVal(true) - postData(data._seed,stateObject) + case "getStateKeyVals": + require(1); + var stateObject = eth.getStateObject(data.args[0]).stateKeyVal(true) + postData(data._seed,stateObject) break - case "getTransactionsFor": - require(1); - var txs = eth.getTransactionsFor(data.args[0], true) - postData(data._seed, txs) + case "getTransactionsFor": + require(1); + var txs = eth.getTransactionsFor(data.args[0], true) + postData(data._seed, txs) - break - case "getBalance": + break + case "getBalance": require(1); postData(data._seed, eth.getStateObject(data.args[0]).value()); break - case "getKey": + case "getKey": var key = eth.getKey().privateKey; postData(data._seed, key) break - case "watch": + case "watch": require(1) eth.watch(data.args[0], data.args[1]); break - case "disconnect": + case "disconnect": require(1) postData(data._seed, null) break; - case "set": - console.log("'Set' has been depcrecated") - /* - for(var key in data.args) { - if(webview.hasOwnProperty(key)) { - window[key] = data.args[key]; - } - } - */ + case "set": + console.log("'Set' has been depcrecated") + /* + for(var key in data.args) { + if(webview.hasOwnProperty(key)) { + window[key] = data.args[key]; + } + } + */ break; - case "getSecretToAddress": + case "getSecretToAddress": require(1) postData(data._seed, eth.secretToAddress(data.args[0])) break; - case "debug": + case "debug": console.log(data.args[0]); break; } @@ -191,12 +226,12 @@ ApplicationWindow { inspector.visible = false }else{ inspector.visible = true - inspector.url = webview.experimental.remoteInspectorUrl + inspector.url = webview.experimental.remoteInspectorUrl } } onDoubleClicked: { - console.log('refreshing') - webView.reload() + console.log('refreshing') + webView.reload() } anchors.fill: parent } diff --git a/ethereal/debugger.go b/ethereal/debugger.go index 0963874057..1cf5e0b664 100644 --- a/ethereal/debugger.go +++ b/ethereal/debugger.go @@ -2,15 +2,16 @@ package main import ( "fmt" + "math/big" + "strconv" + "strings" + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethvm" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" - "math/big" - "strconv" - "strings" ) type DebuggerWindow struct { @@ -134,26 +135,13 @@ func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, data state := self.lib.eth.StateManager().TransState() account := self.lib.eth.StateManager().TransState().GetAccount(keyPair.Address()) contract := ethstate.NewStateObject([]byte{0}) - contract.Amount = value + contract.Balance = value self.SetAsm(script) - callerClosure := ethvm.NewClosure(account, contract, script, gas, gasPrice) - block := self.lib.eth.BlockChain().CurrentBlock - /* - vm := ethchain.NewVm(state, self.lib.eth.StateManager(), ethchain.RuntimeVars{ - Block: block, - Origin: account.Address(), - BlockNumber: block.Number, - PrevHash: block.PrevHash, - Coinbase: block.Coinbase, - Time: block.Time, - Diff: block.Difficulty, - Value: ethutil.Big(valueStr), - }) - */ + callerClosure := ethvm.NewClosure(account, contract, script, gas, gasPrice) env := utils.NewEnv(state, block, account.Address(), value) vm := ethvm.New(env) vm.Verbose = true diff --git a/ethereal/ext_app.go b/ethereal/ext_app.go index ac3e090f90..697630504f 100644 --- a/ethereal/ext_app.go +++ b/ethereal/ext_app.go @@ -1,9 +1,9 @@ package main import ( - "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethreact" "github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethutil" "github.com/go-qml/qml" @@ -25,8 +25,8 @@ type AppContainer interface { type ExtApplication struct { *ethpub.PEthereum - blockChan chan ethutil.React - changeChan chan ethutil.React + blockChan chan ethreact.Event + changeChan chan ethreact.Event quitChan chan bool watcherQuitChan chan bool @@ -38,8 +38,8 @@ type ExtApplication struct { func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication { app := &ExtApplication{ ethpub.NewPEthereum(lib.eth), - make(chan ethutil.React, 1), - make(chan ethutil.React, 1), + make(chan ethreact.Event, 100), + make(chan ethreact.Event, 100), make(chan bool), make(chan bool), container, @@ -58,8 +58,7 @@ func (app *ExtApplication) run() { err := app.container.Create() if err != nil { - fmt.Println(err) - + logger.Errorln(err) return } diff --git a/ethereal/gui.go b/ethereal/gui.go index df01cddda0..36e147ba97 100644 --- a/ethereal/gui.go +++ b/ethereal/gui.go @@ -3,20 +3,23 @@ package main import ( "bytes" "fmt" + "math/big" + "strconv" + "strings" + "time" + "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethlog" "github.com/ethereum/eth-go/ethminer" + "github.com/ethereum/eth-go/ethpipe" "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethreact" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethwire" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" - "math/big" - "strconv" - "strings" - "time" ) var logger = ethlog.NewLogger("GUI") @@ -144,17 +147,20 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { win := gui.createWindow(component) go func() { - gui.setInitialBlockChain() + go gui.setInitialBlockChain() gui.loadAddressBook() - gui.readPreviousTransactions() gui.setPeerInfo() + gui.readPreviousTransactions() }() - go gui.update() + gui.update() return win, nil } +func (gui *Gui) ImportKey(filePath string) { +} + func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) { context.SetVar("lib", gui) component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml")) @@ -231,20 +237,54 @@ func (gui *Gui) loadAddressBook() { } } +func (gui *Gui) insertTransaction(window string, tx *ethchain.Transaction) { + nameReg := ethpipe.New(gui.eth).World().Config().Get("NameReg") + addr := gui.address() + + var inout string + if bytes.Compare(tx.Sender(), addr) == 0 { + inout = "send" + } else { + inout = "recv" + } + + var ( + ptx = ethpub.NewPTx(tx) + send = nameReg.Storage(tx.Sender()) + rec = nameReg.Storage(tx.Recipient) + s, r string + ) + + if tx.CreatesContract() { + rec = nameReg.Storage(tx.CreationAddress()) + } + + if send.Len() != 0 { + s = strings.Trim(send.Str(), "\x00") + } else { + s = ethutil.Bytes2Hex(tx.Sender()) + } + if rec.Len() != 0 { + r = strings.Trim(rec.Str(), "\x00") + } else { + if tx.CreatesContract() { + r = ethutil.Bytes2Hex(tx.CreationAddress()) + } else { + r = ethutil.Bytes2Hex(tx.Recipient) + } + } + ptx.Sender = s + ptx.Address = r + + gui.win.Root().Call("addTx", window, ptx, inout) +} + func (gui *Gui) readPreviousTransactions() { it := gui.txDb.Db().NewIterator(nil, nil) - addr := gui.address() for it.Next() { tx := ethchain.NewTransactionFromBytes(it.Value()) - var inout string - if bytes.Compare(tx.Sender(), addr) == 0 { - inout = "send" - } else { - inout = "recv" - } - - gui.win.Root().Call("addTx", ethpub.NewPTx(tx), inout) + gui.insertTransaction("post", tx) } it.Release() @@ -280,17 +320,100 @@ func (self *Gui) getObjectByName(objectName string) qml.Object { // Simple go routine function that updates the list of peers in the GUI func (gui *Gui) update() { - reactor := gui.eth.Reactor() var ( - blockChan = make(chan ethutil.React, 1) - txChan = make(chan ethutil.React, 1) - objectChan = make(chan ethutil.React, 1) - peerChan = make(chan ethutil.React, 1) - chainSyncChan = make(chan ethutil.React, 1) - miningChan = make(chan ethutil.React, 1) + blockChan = make(chan ethreact.Event, 100) + txChan = make(chan ethreact.Event, 100) + objectChan = make(chan ethreact.Event, 100) + peerChan = make(chan ethreact.Event, 100) + chainSyncChan = make(chan ethreact.Event, 100) + miningChan = make(chan ethreact.Event, 100) ) + peerUpdateTicker := time.NewTicker(5 * time.Second) + generalUpdateTicker := time.NewTicker(1 * time.Second) + + state := gui.eth.StateManager().TransState() + + unconfirmedFunds := new(big.Int) + gui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(state.GetAccount(gui.address()).Balance))) + gui.getObjectByName("syncProgressIndicator").Set("visible", !gui.eth.IsUpToDate()) + + lastBlockLabel := gui.getObjectByName("lastBlockLabel") + + go func() { + for { + select { + case b := <-blockChan: + block := b.Resource.(*ethchain.Block) + gui.processBlock(block, false) + if bytes.Compare(block.Coinbase, gui.address()) == 0 { + gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.address()).Balance, nil) + } + case txMsg := <-txChan: + tx := txMsg.Resource.(*ethchain.Transaction) + + if txMsg.Name == "newTx:pre" { + object := state.GetAccount(gui.address()) + + if bytes.Compare(tx.Sender(), gui.address()) == 0 { + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) + } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { + unconfirmedFunds.Add(unconfirmedFunds, tx.Value) + } + + gui.setWalletValue(object.Balance, unconfirmedFunds) + + gui.insertTransaction("pre", tx) + } else { + object := state.GetAccount(gui.address()) + if bytes.Compare(tx.Sender(), gui.address()) == 0 { + object.SubAmount(tx.Value) + + gui.win.Root().Call("addTx", "post", ethpub.NewPTx(tx), "send") + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { + object.AddAmount(tx.Value) + + gui.win.Root().Call("addTx", "post", ethpub.NewPTx(tx), "recv") + gui.txDb.Put(tx.Hash(), tx.RlpEncode()) + } + + gui.setWalletValue(object.Balance, nil) + + state.UpdateStateObject(object) + } + case msg := <-chainSyncChan: + sync := msg.Resource.(bool) + gui.win.Root().ObjectByName("syncProgressIndicator").Set("visible", sync) + + case <-objectChan: + gui.loadAddressBook() + case <-peerChan: + gui.setPeerInfo() + case <-peerUpdateTicker.C: + gui.setPeerInfo() + case msg := <-miningChan: + if msg.Name == "miner:start" { + gui.miner = msg.Resource.(*ethminer.Miner) + } else { + gui.miner = nil + } + case <-generalUpdateTicker.C: + statusText := "#" + gui.eth.BlockChain().CurrentBlock.Number.String() + if gui.miner != nil { + pow := gui.miner.GetPow() + if pow.GetHashrate() != 0 { + statusText = "Mining @ " + strconv.FormatInt(pow.GetHashrate(), 10) + "Khash - " + statusText + } + } + lastBlockLabel.Set("text", statusText) + } + } + }() + + reactor := gui.eth.Reactor() + reactor.Subscribe("newBlock", blockChan) reactor.Subscribe("newTx:pre", txChan) reactor.Subscribe("newTx:post", txChan) @@ -303,86 +426,6 @@ func (gui *Gui) update() { reactor.Subscribe("object:"+string(nameReg.Address()), objectChan) } reactor.Subscribe("peerList", peerChan) - - peerUpdateTicker := time.NewTicker(5 * time.Second) - generalUpdateTicker := time.NewTicker(1 * time.Second) - - state := gui.eth.StateManager().TransState() - - unconfirmedFunds := new(big.Int) - gui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(state.GetAccount(gui.address()).Amount))) - gui.getObjectByName("syncProgressIndicator").Set("visible", !gui.eth.IsUpToDate()) - - lastBlockLabel := gui.getObjectByName("lastBlockLabel") - - for { - select { - case b := <-blockChan: - block := b.Resource.(*ethchain.Block) - gui.processBlock(block, false) - if bytes.Compare(block.Coinbase, gui.address()) == 0 { - gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.address()).Amount, nil) - } - - case txMsg := <-txChan: - tx := txMsg.Resource.(*ethchain.Transaction) - - if txMsg.Event == "newTx:pre" { - object := state.GetAccount(gui.address()) - - if bytes.Compare(tx.Sender(), gui.address()) == 0 { - gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "send") - gui.txDb.Put(tx.Hash(), tx.RlpEncode()) - - unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) - } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { - gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "recv") - gui.txDb.Put(tx.Hash(), tx.RlpEncode()) - - unconfirmedFunds.Add(unconfirmedFunds, tx.Value) - } - - gui.setWalletValue(object.Amount, unconfirmedFunds) - } else { - object := state.GetAccount(gui.address()) - if bytes.Compare(tx.Sender(), gui.address()) == 0 { - object.SubAmount(tx.Value) - } else if bytes.Compare(tx.Recipient, gui.address()) == 0 { - object.AddAmount(tx.Value) - } - - gui.setWalletValue(object.Amount, nil) - - state.UpdateStateObject(object) - } - case msg := <-chainSyncChan: - sync := msg.Resource.(bool) - gui.win.Root().ObjectByName("syncProgressIndicator").Set("visible", sync) - - case <-objectChan: - gui.loadAddressBook() - case <-peerChan: - gui.setPeerInfo() - case <-peerUpdateTicker.C: - gui.setPeerInfo() - case msg := <-miningChan: - if msg.Event == "miner:start" { - gui.miner = msg.Resource.(*ethminer.Miner) - } else { - gui.miner = nil - } - - case <-generalUpdateTicker.C: - statusText := "#" + gui.eth.BlockChain().CurrentBlock.Number.String() - if gui.miner != nil { - pow := gui.miner.GetPow() - if pow.GetHashrate() != 0 { - statusText = "Mining @ " + strconv.FormatInt(pow.GetHashrate(), 10) + "Khash - " + statusText - } - } - lastBlockLabel.Set("text", statusText) - } - } } func (gui *Gui) setPeerInfo() { @@ -416,6 +459,11 @@ func (gui *Gui) Create(recipient, value, gas, gasPrice, data string) (*ethpub.PR return gui.pub.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data) } +func (self *Gui) ImportTx(rlpTx string) { + tx := ethchain.NewTransactionFromBytes(ethutil.Hex2Bytes(rlpTx)) + self.eth.TxPool().QueueTransaction(tx) +} + func (gui *Gui) SetCustomIdentifier(customIdentifier string) { gui.clientIdentity.SetCustomIdentifier(customIdentifier) gui.config.Save("id", customIdentifier) diff --git a/ethereal/html_container.go b/ethereal/html_container.go index b00d3f78e0..40a9f55842 100644 --- a/ethereal/html_container.go +++ b/ethereal/html_container.go @@ -2,17 +2,18 @@ package main import ( "errors" + "io/ioutil" + "net/url" + "os" + "path" + "path/filepath" + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethpub" "github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethutil" "github.com/go-qml/qml" "github.com/howeyc/fsnotify" - "io/ioutil" - "net/url" - "os" - "path" - "path/filepath" ) type HtmlApplication struct { @@ -41,7 +42,7 @@ func (app *HtmlApplication) Create() error { return errors.New("Ethereum package not yet supported") // TODO - ethutil.OpenPackage(app.path) + //ethutil.OpenPackage(app.path) } win := component.CreateWindow(nil) diff --git a/ethereal/main.go b/ethereal/main.go index 0f99be8865..04a04536d2 100644 --- a/ethereal/main.go +++ b/ethereal/main.go @@ -1,16 +1,17 @@ package main import ( + "os" + "runtime" + "github.com/ethereum/eth-go/ethlog" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" - "os" - "runtime" ) const ( ClientIdentifier = "Ethereal" - Version = "0.6.0" + Version = "0.6.1" ) func main() { diff --git a/ethereal/qml_container.go b/ethereal/qml_container.go index 1b420ee219..53ff13c2f3 100644 --- a/ethereal/qml_container.go +++ b/ethereal/qml_container.go @@ -25,7 +25,7 @@ func (app *QmlApplication) Create() error { path := string(app.path) // For some reason for windows we get /c:/path/to/something, windows doesn't like the first slash but is fine with the others so we are removing it - if string(app.path[0]) == "/" && runtime.GOOS == "windows" { + if app.path[0] == '/' && runtime.GOOS == "windows" { path = app.path[1:] } diff --git a/ethereal/ui_lib.go b/ethereal/ui_lib.go index 6a62fa1df2..42c5c9ad2d 100644 --- a/ethereal/ui_lib.go +++ b/ethereal/ui_lib.go @@ -1,10 +1,11 @@ package main import ( + "path" + "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethutil" "github.com/go-qml/qml" - "path" ) type memAddr struct { @@ -42,6 +43,10 @@ func (ui *UiLib) OpenHtml(path string) { go app.run() } +func (ui *UiLib) OpenBrowser() { + ui.OpenHtml("file://" + ui.AssetPath("ext/home.html")) +} + func (ui *UiLib) Muted(content string) { component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml")) if err != nil { diff --git a/ethereum/flags.go b/ethereum/flags.go index 4f59ddf060..5ed208411b 100644 --- a/ethereum/flags.go +++ b/ethereum/flags.go @@ -3,10 +3,11 @@ package main import ( "flag" "fmt" - "github.com/ethereum/eth-go/ethlog" "os" "os/user" "path" + + "github.com/ethereum/eth-go/ethlog" ) var Identifier string @@ -31,6 +32,9 @@ var LogFile string var ConfigFile string var DebugFile string var LogLevel int +var Dump bool +var DumpHash string +var DumpNumber int // flags specific to cli client var StartMining bool @@ -71,6 +75,10 @@ func Init() { flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0") flag.StringVar(&DiffType, "diff", "all", "sets the level of diff output [vm, all]. Has no effect if difftool=false") + flag.BoolVar(&Dump, "dump", false, "output the ethereum state in JSON format. Sub args [number, hash]") + flag.StringVar(&DumpHash, "hash", "", "specify arg in hex") + flag.IntVar(&DumpNumber, "number", -1, "specify arg in number") + flag.BoolVar(&StartMining, "mine", false, "start dagger mining") flag.BoolVar(&StartJsConsole, "js", false, "launches javascript console") diff --git a/ethereum/main.go b/ethereum/main.go index 2179910748..17838997c4 100644 --- a/ethereum/main.go +++ b/ethereum/main.go @@ -1,15 +1,19 @@ package main import ( + "fmt" + "os" + "runtime" + + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethlog" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" - "runtime" ) const ( ClientIdentifier = "Ethereum(G)" - Version = "0.6.0" + Version = "0.6.1" ) var logger = ethlog.NewLogger("CLI") @@ -23,7 +27,7 @@ func main() { Init() // parsing command line // If the difftool option is selected ignore all other log output - if DiffTool { + if DiffTool || Dump { LogLevel = 0 } @@ -46,6 +50,32 @@ func main() { ethereum := utils.NewEthereum(db, clientIdentity, keyManager, UseUPnP, OutboundPort, MaxPeer) + if Dump { + var block *ethchain.Block + + if len(DumpHash) == 0 && DumpNumber == -1 { + block = ethereum.BlockChain().CurrentBlock + } else if len(DumpHash) > 0 { + block = ethereum.BlockChain().GetBlock(ethutil.Hex2Bytes(DumpHash)) + } else { + block = ethereum.BlockChain().GetBlockByNumber(uint64(DumpNumber)) + } + + if block == nil { + fmt.Fprintln(os.Stderr, "block not found") + + // We want to output valid JSON + fmt.Println("{}") + + os.Exit(1) + } + + // Leave the Println. This needs clean output for piping + fmt.Println(block.State().Dump()) + + os.Exit(0) + } + if ShowGenesis { utils.ShowGenesis(ethereum) } diff --git a/ethereum/repl/javascript_runtime.go b/ethereum/repl/javascript_runtime.go index 41b6216d49..026e6f374c 100644 --- a/ethereum/repl/javascript_runtime.go +++ b/ethereum/repl/javascript_runtime.go @@ -2,18 +2,20 @@ package ethrepl import ( "fmt" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethlog" - "github.com/ethereum/eth-go/ethpub" - "github.com/ethereum/eth-go/ethstate" - "github.com/ethereum/eth-go/ethutil" - "github.com/ethereum/go-ethereum/utils" - "github.com/obscuren/otto" "io/ioutil" "os" "path" "path/filepath" + + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethlog" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethreact" + "github.com/ethereum/eth-go/ethstate" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" + "github.com/obscuren/otto" ) var jsrelogger = ethlog.NewLogger("JSRE") @@ -23,8 +25,8 @@ type JSRE struct { vm *otto.Otto lib *ethpub.PEthereum - blockChan chan ethutil.React - changeChan chan ethutil.React + blockChan chan ethreact.Event + changeChan chan ethreact.Event quitChan chan bool objectCb map[string][]otto.Value @@ -49,8 +51,8 @@ func NewJSRE(ethereum *eth.Ethereum) *JSRE { ethereum, otto.New(), ethpub.NewPEthereum(ethereum), - make(chan ethutil.React, 1), - make(chan ethutil.React, 1), + make(chan ethreact.Event, 10), + make(chan ethreact.Event, 10), make(chan bool), make(map[string][]otto.Value), } @@ -65,6 +67,10 @@ func NewJSRE(ethereum *eth.Ethereum) *JSRE { // We have to make sure that, whoever calls this, calls "Stop" go re.mainLoop() + // Subscribe to events + reactor := ethereum.Reactor() + reactor.Subscribe("newBlock", re.blockChan) + re.Bind("eth", &JSEthereum{re.lib, re.vm}) re.initStdFuncs() @@ -109,10 +115,6 @@ func (self *JSRE) Stop() { } func (self *JSRE) mainLoop() { - // Subscribe to events - reactor := self.ethereum.Reactor() - reactor.Subscribe("newBlock", self.blockChan) - out: for { select { @@ -146,12 +148,44 @@ func (self *JSRE) initStdFuncs() { eth.Set("stopMining", self.stopMining) eth.Set("startMining", self.startMining) eth.Set("execBlock", self.execBlock) + eth.Set("dump", self.dump) } /* * The following methods are natively implemented javascript functions */ +func (self *JSRE) dump(call otto.FunctionCall) otto.Value { + var state *ethstate.State + + if len(call.ArgumentList) > 0 { + var block *ethchain.Block + if call.Argument(0).IsNumber() { + num, _ := call.Argument(0).ToInteger() + block = self.ethereum.BlockChain().GetBlockByNumber(uint64(num)) + } else if call.Argument(0).IsString() { + hash, _ := call.Argument(0).ToString() + block = self.ethereum.BlockChain().GetBlock(ethutil.Hex2Bytes(hash)) + } else { + fmt.Println("invalid argument for dump. Either hex string or number") + } + + if block == nil { + fmt.Println("block not found") + + return otto.UndefinedValue() + } + + state = block.State() + } else { + state = self.ethereum.StateManager().CurrentState() + } + + v, _ := self.vm.ToValue(state.Dump()) + + return v +} + func (self *JSRE) stopMining(call otto.FunctionCall) otto.Value { v, _ := self.vm.ToValue(utils.StopMining(self.ethereum)) return v diff --git a/utils/cmd.go b/utils/cmd.go index 5d0b3463c5..0eeb1049e1 100644 --- a/utils/cmd.go +++ b/utils/cmd.go @@ -127,6 +127,7 @@ func NewDatabase() ethutil.Database { } func NewClientIdentity(clientIdentifier, version, customIdentifier string) *ethwire.SimpleClientIdentity { + logger.Infoln("identity created") return ethwire.NewSimpleClientIdentity(clientIdentifier, version, customIdentifier) } @@ -243,21 +244,18 @@ func GetMiner() *ethminer.Miner { func StartMining(ethereum *eth.Ethereum) bool { if !ethereum.Mining { ethereum.Mining = true - addr := ethereum.KeyManager().Address() go func() { + logger.Infoln("Start mining") if miner == nil { miner = ethminer.NewDefaultMiner(addr, ethereum) } - // Give it some time to connect with peers time.Sleep(3 * time.Second) for !ethereum.IsUpToDate() { time.Sleep(5 * time.Second) } - - logger.Infoln("Miner started") miner.Start() }() RegisterInterrupt(func(os.Signal) { @@ -271,9 +269,7 @@ func StartMining(ethereum *eth.Ethereum) bool { func StopMining(ethereum *eth.Ethereum) bool { if ethereum.Mining && miner != nil { miner.Stop() - - logger.Infoln("Miner stopped") - + logger.Infoln("Stopped mining") ethereum.Mining = false return true