From 8c8554f5584dcc0efb4526c1d5f4d50bf2ed3b01 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 18 Feb 2014 01:34:33 +0100 Subject: [PATCH 01/21] Added license name and updated block output from the dev console --- LICENSE | 2 +- dev_console.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 803f2ef4e3..b77f7909a6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Geff Obscura +Copyright (c) 2013 Jeffrey Wilcke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/dev_console.go b/dev_console.go index f2283e3411..6670d2a449 100644 --- a/dev_console.go +++ b/dev_console.go @@ -136,7 +136,8 @@ func (i *Console) ParseInput(input string) bool { case "block": encoded, _ := hex.DecodeString(tokens[1]) block := i.ethereum.BlockManager.BlockChain().GetBlock(encoded) - fmt.Println(block) + info := block.BlockInfo() + fmt.Printf("++++++++++ #%d ++++++++++\n%v\n", info.Number, block) case "say": i.ethereum.Broadcast(ethwire.MsgTalkTy, []interface{}{tokens[1]}) case "addp": From ab7dc924042b4cdb36ec7f2b26ab14c40d34ec9d Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 18 Feb 2014 12:09:26 +0100 Subject: [PATCH 02/21] Added import/exporting of private keys --- config.go | 4 ++++ ethereum.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index d13bb863b2..573f5ded2c 100644 --- a/config.go +++ b/config.go @@ -13,6 +13,8 @@ var AddPeer string var MaxPeer int var GenAddr bool var UseSeed bool +var ImportKey string +var ExportKey bool func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") @@ -21,7 +23,9 @@ func Init() { flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") + flag.BoolVar(&ExportKey, "export", false, "export private key") flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") flag.Parse() diff --git a/ethereum.go b/ethereum.go index 372d434af6..36700a6d43 100644 --- a/ethereum.go +++ b/ethereum.go @@ -38,8 +38,6 @@ func CreateKeyPair(force bool) { fmt.Printf(` Generating new address and keypair. Please keep your keys somewhere save. -Currently Ethereum(G) does not support -exporting keys. ++++++++++++++++ KeyRing +++++++++++++++++++ addr: %x @@ -54,6 +52,29 @@ pubk: %x } } +func ImportPrivateKey(prvKey string) { + key := ethutil.FromHex(prvKey) + msg := []byte("tmp") + // Couldn't think of a better way to get the pub key + sig, _ := secp256k1.Sign(msg, key) + pub, _ := secp256k1.RecoverPubkey(msg, sig) + addr := ethutil.Sha3Bin(pub[1:])[12:] + + fmt.Printf(` +Importing private key + +++++++++++++++++ KeyRing +++++++++++++++++++ +addr: %x +prvk: %x +pubk: %x +++++++++++++++++++++++++++++++++++++++++++++ + +`, addr, key, pub) + + keyRing := ethutil.NewValue([]interface{}{key, addr, pub[1:]}) + ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) +} + func main() { runtime.GOMAXPROCS(runtime.NumCPU()) Init() @@ -87,7 +108,31 @@ func main() { } os.Exit(0) } else { - CreateKeyPair(false) + if len(ImportKey) > 0 { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + + if r == "y" { + ImportPrivateKey(ImportKey) + } + } else { + CreateKeyPair(false) + } + } + + if ExportKey { + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + fmt.Printf("%x\n", keyRing.Get(0).Bytes()) + os.Exit(0) } if ShowGenesis { From 6736c03711b3ef35285392c12a79bbf6e4cdf914 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 18 Feb 2014 12:09:36 +0100 Subject: [PATCH 03/21] Added editor for contracts --- dev_console.go | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/dev_console.go b/dev_console.go index 6670d2a449..2e6b385df2 100644 --- a/dev_console.go +++ b/dev_console.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "bytes" "encoding/hex" "errors" "fmt" @@ -78,6 +79,32 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { } } +func (i *Console) Editor() []string { + var buff bytes.Buffer + for { + reader := bufio.NewReader(os.Stdin) + str, _, err := reader.ReadLine() + if len(str) > 0 { + buff.Write(str) + buff.WriteString("\n") + } + + if err != nil && err.Error() == "EOF" { + break + } + } + + scanner := bufio.NewScanner(strings.NewReader(buff.String())) + scanner.Split(bufio.ScanLines) + + var lines []string + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + return lines +} + func (i *Console) PrintRoot() { root := ethutil.NewValue(i.trie.Root) if len(root.Bytes()) != 0 { @@ -169,10 +196,17 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("gettx: tx not found") } case "contract": - contract := ethchain.NewTransaction([]byte{}, ethutil.Big(tokens[1]), []string{"PUSH", "1234"}) - fmt.Printf("%x\n", contract.Hash()) + fmt.Println("Contract editor (Ctrl-D = done)") + code := i.Editor() + + contract := ethchain.NewTransaction([]byte{}, ethutil.Big(tokens[1]), code) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + contract.Sign(keyRing.Get(0).Bytes()) i.ethereum.TxPool.QueueTransaction(contract) + + fmt.Printf("%x\n", contract.Hash()) case "exit", "quit", "q": return false case "help": From d7ecc92c4134e3987b2b370bb53b0cd560fc0f7b Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 00:47:07 +0100 Subject: [PATCH 04/21] Fixed broken links. Fixes #18 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d0a08f7b8f..e0d19b7120 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ of Concept 2". For build instructions see the [Wiki](https://github.com/ethereum Ethereum Go is split up in several sub packages Please refer to each individual package for more information. 1. [eth](https://github.com/ethereum/eth-go) - 2. [ethchain](https://github.com/ethereum/ethchain-go) - 3. [ethwire](https://github.com/ethereum/ethwire-go) - 4. [ethdb](https://github.com/ethereum/ethdb-go) - 5. [ethutil](https://github.com/ethereum/ethutil-go) + 2. [ethchain](https://github.com/ethereum/eth-go/ethchain) + 3. [ethwire](https://github.com/ethereum/eth-go/ethwire) + 4. [ethdb](https://github.com/ethereum/eth-go/ethdb) + 5. [ethutil](https://github.com/ethereum/eth-go/ethutil) The [eth](https://github.com/ethereum/eth-go) is the top-level package of the Ethereum protocol. It functions as the Ethereum bootstrapping and From 05c353eca0c4e01457412dd643529200816ab159 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 12:37:40 +0100 Subject: [PATCH 05/21] Added a basic UI --- config.go | 2 ++ ethereum.go | 86 +++++++++++++++++++++++++++++++---------------------- ui/gui.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ wallet.qml | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 ui/gui.go create mode 100644 wallet.qml diff --git a/config.go b/config.go index 573f5ded2c..c25c8f2a3f 100644 --- a/config.go +++ b/config.go @@ -15,11 +15,13 @@ var GenAddr bool var UseSeed bool var ImportKey string var ExportKey bool +var UseGui bool func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") + flag.BoolVar(&UseGui, "gui", false, "use the gui") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") diff --git a/ethereum.go b/ethereum.go index 36700a6d43..5eda09a445 100644 --- a/ethereum.go +++ b/ethereum.go @@ -5,6 +5,8 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/ui" + "github.com/niemeyer/qml" "github.com/obscuren/secp256k1-go" "log" "os" @@ -76,9 +78,16 @@ pubk: %x } func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) Init() + // Qt has to be initialized in the main thread or it will throw errors + // It has to be called BEFORE setting the maximum procs. + if UseGui { + qml.Init(nil) + } + + runtime.GOMAXPROCS(runtime.NumCPU()) + ethchain.InitFees() ethutil.ReadConfig(".ethereum") ethutil.Config.Seed = UseSeed @@ -156,41 +165,46 @@ func main() { go console.Start() } - RegisterInterupts(ethereum) - - ethereum.Start() - - if StartMining { - log.Printf("Miner started\n") - - // Fake block mining. It broadcasts a new block every 5 seconds - go func() { - pow := ðchain.EasyPow{} - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(1).Bytes() - - for { - txs := ethereum.TxPool.Flush() - // Create a new block which we're going to mine - block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) - // Apply all transactions to the block - ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) - - ethereum.BlockManager.AccumelateRewards(block, block) - - // Search the nonce - block.Nonce = pow.Search(block) - err := ethereum.BlockManager.ProcessBlock(block) - if err != nil { - log.Println(err) - } else { - log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockManager.BlockChain().CurrentBlock) + if UseGui { + gui := ethui.New(ethereum) + gui.Start() + //ethereum.Stop() + } else { + RegisterInterupts(ethereum) + ethereum.Start() + + if StartMining { + log.Printf("Miner started\n") + + // Fake block mining. It broadcasts a new block every 5 seconds + go func() { + pow := ðchain.EasyPow{} + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(1).Bytes() + + for { + txs := ethereum.TxPool.Flush() + // Create a new block which we're going to mine + block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) + // Apply all transactions to the block + ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) + + ethereum.BlockManager.AccumelateRewards(block, block) + + // Search the nonce + block.Nonce = pow.Search(block) + err := ethereum.BlockManager.ProcessBlock(block) + if err != nil { + log.Println(err) + } else { + log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockManager.BlockChain().CurrentBlock) + } } - } - }() - } + }() + } - // Wait for shutdown - ethereum.WaitForShutdown() + // Wait for shutdown + ethereum.WaitForShutdown() + } } diff --git a/ui/gui.go b/ui/gui.go new file mode 100644 index 0000000000..b2a8dad73d --- /dev/null +++ b/ui/gui.go @@ -0,0 +1,83 @@ +package ethui + +import ( + "bufio" + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/niemeyer/qml" + "strings" +) + +type Gui struct { + win *qml.Window + engine *qml.Engine + component *qml.Common + eth *eth.Ethereum +} + +func New(ethereum *eth.Ethereum) *Gui { + return &Gui{eth: ethereum} +} + +type Block struct { + Number int + Hash string +} + +func NewBlockFromBlock(block *ethchain.Block) *Block { + info := block.BlockInfo() + hash := hex.EncodeToString(block.Hash()) + + return &Block{Number: int(info.Number), Hash: hash} +} + +func (ui *Gui) Start() { + qml.RegisterTypes("GoExtensions", 1, 0, []qml.TypeSpec{{ + Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, + }}) + + ethutil.Config.Log.Infoln("[GUI] Starting GUI") + ui.engine = qml.NewEngine() + component, err := ui.engine.LoadFile("wallet.qml") + if err != nil { + panic(err) + } + + ui.win = component.CreateWindow(nil) + root := ui.win.Root() + + context := ui.engine.Context() + context.SetVar("tester", &Tester{root: root}) + + ui.eth.BlockManager.SecondaryBlockProcessor = ui + ui.eth.Start() + + ui.win.Show() + ui.win.Wait() +} + +func (ui *Gui) ProcessBlock(block *ethchain.Block) { + ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) +} + +type Tester struct { + root qml.Object +} + +func (t *Tester) Compile(area qml.Object) { + fmt.Println(area) + ethutil.Config.Log.Infoln("[TESTER] Compiling") + + code := area.String("text") + + scanner := bufio.NewScanner(strings.NewReader(code)) + scanner.Split(bufio.ScanLines) + + var lines []string + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } +} diff --git a/wallet.qml b/wallet.qml new file mode 100644 index 0000000000..2bf4e45765 --- /dev/null +++ b/wallet.qml @@ -0,0 +1,81 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import GoExtensions 1.0 + +ApplicationWindow { + id: root + + width: 800 + height: 600 + minimumHeight: 300 + + title: "Ethereal" + + toolBar: ToolBar { + id: mainToolbar + + RowLayout { + width: parent.width + Button { + text: "Send" + onClicked: tester.compile(codeView) + } + + TextField { + width: 200 + placeholderText: "Amount" + } + + TextField { + width: 300 + placeholderText: "Receiver Address (or empty for contract)" + Layout.fillWidth: true + } + + } + } + + SplitView { + id: splitView + height: 200 + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + TextArea { + id: codeView + width: parent.width /2 + } + + TextArea { + readOnly: true + } + } + + property var blockModel: ListModel { + id: blockModel + } + + TableView { + width: parent.width + height: 100 + anchors.bottom: parent.bottom + anchors.top: splitView.bottom + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + + model: blockModel + } + + + statusBar: StatusBar { + RowLayout { + Label { text: "0.0.1" } + } + } + + function addBlock(block) { + blockModel.append({number: block.number, hash: block.hash}) + } +} From aaac0c9998ee78d796c1dbab118f721f886ce426 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 13:06:17 +0100 Subject: [PATCH 06/21] Initial block chain fetching of existing blocks --- ui/gui.go | 12 +++++++++++- wallet.qml | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index b2a8dad73d..aae1320fc9 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -53,12 +53,22 @@ func (ui *Gui) Start() { context.SetVar("tester", &Tester{root: root}) ui.eth.BlockManager.SecondaryBlockProcessor = ui - ui.eth.Start() + + go ui.setInitialBlockChain() ui.win.Show() ui.win.Wait() } +func (ui *Gui) setInitialBlockChain() { + chain := ui.eth.BlockManager.BlockChain().GetChain(ui.eth.BlockManager.BlockChain().CurrentBlock.Hash(), 10) + for _, block := range chain { + ui.ProcessBlock(block) + } + + ui.eth.Start() +} + func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } diff --git a/wallet.qml b/wallet.qml index 2bf4e45765..cbce7ebcce 100644 --- a/wallet.qml +++ b/wallet.qml @@ -76,6 +76,6 @@ ApplicationWindow { } function addBlock(block) { - blockModel.append({number: block.number, hash: block.hash}) + blockModel.insert(0, {number: block.number, hash: block.hash}) } } From 95a48cea18eccd4ea2cb298027dbd01bd21f43e8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 13:23:35 +0100 Subject: [PATCH 07/21] Peer amount update --- network.png | Bin 0 -> 2900 bytes ui/gui.go | 9 +++++++++ wallet.qml | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 network.png diff --git a/network.png b/network.png new file mode 100644 index 0000000000000000000000000000000000000000..0a9ffe2ec6023846413a040c75b44710249d355b GIT binary patch literal 2900 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 Date: Fri, 21 Feb 2014 17:29:59 +0100 Subject: [PATCH 08/21] WIP library, sample app --- test_app.qml | 35 ++++++++++++++++++++++++++++++ ui/gui.go | 28 +++++++++++++++++++++--- ui/library.go | 42 ++++++++++++++++++++++++++++++++++++ wallet.qml | 59 +++++++++++++++++++++++++++++++++------------------ 4 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 test_app.qml create mode 100644 ui/library.go diff --git a/test_app.qml b/test_app.qml new file mode 100644 index 0000000000..c7593bf12e --- /dev/null +++ b/test_app.qml @@ -0,0 +1,35 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import GoExtensions 1.0 + +ApplicationWindow { + minimumWidth: 500 + maximumWidth: 500 + maximumHeight: 100 + minimumHeight: 100 + + title: "Ethereum Dice" + + TextField { + id: textField + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Amount" + } + Label { + id: txHash + anchors.bottom: textField.top + anchors.bottomMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + } + Button { + anchors.top: textField.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 5 + text: "Place bet" + onClicked: { + txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", parseInt(textField.text)) + } + } +} diff --git a/ui/gui.go b/ui/gui.go index e223fe2626..8f063843c3 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -17,10 +17,15 @@ type Gui struct { engine *qml.Engine component *qml.Common eth *eth.Ethereum + + // The Ethereum library + lib *EthLib } func New(ethereum *eth.Ethereum) *Gui { - return &Gui{eth: ethereum} + lib := &EthLib{blockManager: ethereum.BlockManager, blockChain: ethereum.BlockManager.BlockChain(), txPool: ethereum.TxPool} + + return &Gui{eth: ethereum, lib: lib} } type Block struct { @@ -48,10 +53,10 @@ func (ui *Gui) Start() { } ui.win = component.CreateWindow(nil) - root := ui.win.Root() context := ui.engine.Context() - context.SetVar("tester", &Tester{root: root}) + context.SetVar("eth", ui.lib) + context.SetVar("ui", &UiLib{engine: ui.engine}) ui.eth.BlockManager.SecondaryBlockProcessor = ui @@ -82,6 +87,23 @@ func (ui *Gui) updatePeers() { } } +type UiLib struct { + engine *qml.Engine +} + +func (ui *UiLib) Open(path string) { + component, err := ui.engine.LoadFile(path[7:]) + if err != nil { + ethutil.Config.Log.Debugln(err) + } + win := component.CreateWindow(nil) + + go func() { + win.Show() + win.Wait() + }() +} + type Tester struct { root qml.Object } diff --git a/ui/library.go b/ui/library.go new file mode 100644 index 0000000000..36952e198e --- /dev/null +++ b/ui/library.go @@ -0,0 +1,42 @@ +package ethui + +import ( + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "math/big" +) + +type EthLib struct { + blockManager *ethchain.BlockManager + blockChain *ethchain.BlockChain + txPool *ethchain.TxPool +} + +func (lib *EthLib) CreateTx(receiver string, amount uint64) string { + hash, err := hex.DecodeString(receiver) + if err != nil { + return err.Error() + } + + tx := ethchain.NewTransaction(hash, big.NewInt(int64(amount)), []string{""}) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + tx.Sign(keyRing.Get(0).Bytes()) + + lib.txPool.QueueTransaction(tx) + + return ethutil.Hex(tx.Hash()) +} + +func (lib *EthLib) GetBlock(hexHash string) *Block { + hash, err := hex.DecodeString(hexHash) + if err != nil { + return nil + } + + block := lib.blockChain.GetBlock(hash) + fmt.Println(block) + return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} +} diff --git a/wallet.qml b/wallet.qml index b19e7f32bb..f9bd8ec76d 100644 --- a/wallet.qml +++ b/wallet.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; import GoExtensions 1.0 ApplicationWindow { @@ -12,6 +13,7 @@ ApplicationWindow { title: "Ethereal" + toolBar: ToolBar { id: mainToolbar @@ -19,7 +21,7 @@ ApplicationWindow { width: parent.width Button { text: "Send" - onClicked: tester.compile(codeView) + onClicked: console.log("SEND") } TextField { @@ -66,35 +68,50 @@ ApplicationWindow { TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } model: blockModel + + onDoubleClicked: { + console.log(eth.getBlock(blockModel.get(row).hash)) + } } + FileDialog { + id: openAppDialog + title: "Open QML Application" + onAccepted: { + ui.open(openAppDialog.fileUrl.toString()) + } + } statusBar: StatusBar { - RowLayout { - anchors.fill: parent - Label { text: "0.0.1" } - Label { - anchors.right: peerImage.left - anchors.rightMargin: 5 - id: peerLabel - font.pixelSize: 8 - text: "0 / 0" - } - - Image { - id: peerImage - anchors.right: parent.right - width: 10; height: 10 - source: "network.png" - } + RowLayout { + anchors.fill: parent + Button { + onClicked: openAppDialog.open() + text: "Import App" + } + + Label { text: "0.0.1" } + Label { + anchors.right: peerImage.left + anchors.rightMargin: 5 + id: peerLabel + font.pixelSize: 8 + text: "0 / 0" } + Image { + id: peerImage + anchors.right: parent.right + width: 10; height: 10 + source: "network.png" + } + } } function addBlock(block) { - blockModel.insert(0, {number: block.number, hash: block.hash}) + blockModel.insert(0, {number: block.number, hash: block.hash}) } function setPeers(text) { - peerLabel.text = text - } + peerLabel.text = text + } } From 2b967558cebcef9d3ef9719cbb28a5e596982a5d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 22 Feb 2014 01:52:47 +0100 Subject: [PATCH 09/21] Working out UI --- config.go | 2 +- ui/gui.go | 21 +++++++++++++++++++-- wallet.qml | 46 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index c25c8f2a3f..79d40d7069 100644 --- a/config.go +++ b/config.go @@ -21,7 +21,7 @@ func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") - flag.BoolVar(&UseGui, "gui", false, "use the gui") + flag.BoolVar(&UseGui, "gui", true, "use the gui") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") diff --git a/ui/gui.go b/ui/gui.go index 8f063843c3..fad7e95911 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -56,10 +56,12 @@ func (ui *Gui) Start() { context := ui.engine.Context() context.SetVar("eth", ui.lib) - context.SetVar("ui", &UiLib{engine: ui.engine}) + context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) ui.eth.BlockManager.SecondaryBlockProcessor = ui + ethutil.Config.Log.AddLogSystem(ui) + go ui.setInitialBlockChain() go ui.updatePeers() @@ -73,13 +75,23 @@ func (ui *Gui) setInitialBlockChain() { ui.ProcessBlock(block) } - ui.eth.Start() } func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } +func (ui *Gui) Println(v ...interface{}) { + str := fmt.Sprintln(v...) + // remove last \n + ui.win.Root().Call("addLog", str[:len(str)-1]) +} + +func (ui *Gui) Printf(format string, v ...interface{}) { + str := strings.TrimRight(fmt.Sprintf(format, v...), "\n") + ui.win.Root().Call("addLog", str) +} + func (ui *Gui) updatePeers() { for { ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) @@ -89,6 +101,7 @@ func (ui *Gui) updatePeers() { type UiLib struct { engine *qml.Engine + eth *eth.Ethereum } func (ui *UiLib) Open(path string) { @@ -104,6 +117,10 @@ func (ui *UiLib) Open(path string) { }() } +func (ui *UiLib) Connect() { + ui.eth.Start() +} + type Tester struct { root qml.Object } diff --git a/wallet.qml b/wallet.qml index f9bd8ec76d..e6cb32b182 100644 --- a/wallet.qml +++ b/wallet.qml @@ -2,6 +2,7 @@ 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 GoExtensions 1.0 ApplicationWindow { @@ -60,20 +61,36 @@ ApplicationWindow { } TableView { + id: blockTable width: parent.width - height: 100 - anchors.bottom: parent.bottom anchors.top: splitView.bottom + anchors.bottom: logView.top TableViewColumn{ role: "number" ; title: "#" ; width: 100 } TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } model: blockModel onDoubleClicked: { - console.log(eth.getBlock(blockModel.get(row).hash)) + popup.visible = true + popup.block = eth.getBlock(blockModel.get(row).hash) + popup.hashLabel.text = popup.block.hash } } + property var logModel: ListModel { + id: logModel + } + + TableView { + id: logView + width: parent.width + height: 150 + anchors.bottom: parent.bottom + TableViewColumn{ role: "description" ; title: "log" } + + model: logModel + } + FileDialog { id: openAppDialog title: "Open QML Application" @@ -86,6 +103,13 @@ ApplicationWindow { RowLayout { anchors.fill: parent Button { + id: connectButton + onClicked: ui.connect() + text: "Connect" + } + Button { + anchors.left: connectButton.right + anchors.leftMargin: 5 onClicked: openAppDialog.open() text: "Import App" } @@ -107,10 +131,26 @@ ApplicationWindow { } } + Window { + id: popup + visible: false + property var block + Label { + id: hashLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + function addBlock(block) { blockModel.insert(0, {number: block.number, hash: block.hash}) } + function addLog(str) { + console.log(str) + logModel.insert(0, {description: str}) + } + function setPeers(text) { peerLabel.text = text } From aa33a4b2fb9dc07468498decceb6fdb56d38f54d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 22 Feb 2014 23:19:38 +0100 Subject: [PATCH 10/21] Added some ui elements to make it easier to connect to nodes --- config.go | 2 ++ ethereum.go | 3 +- test_app.qml | 4 +-- ui/gui.go | 81 +++++++++++++++++++++++++++++++++++++-------------- ui/library.go | 10 ++++--- wallet.qml | 70 ++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 136 insertions(+), 34 deletions(-) diff --git a/config.go b/config.go index 79d40d7069..bafc3e300f 100644 --- a/config.go +++ b/config.go @@ -16,6 +16,7 @@ var UseSeed bool var ImportKey string var ExportKey bool var UseGui bool +var DataDir string func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") @@ -27,6 +28,7 @@ func Init() { flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") flag.BoolVar(&ExportKey, "export", false, "export private key") flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.StringVar(&DataDir, "dir", ".ethereum", "ethereum data directory") flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") diff --git a/ethereum.go b/ethereum.go index 5eda09a445..384b227483 100644 --- a/ethereum.go +++ b/ethereum.go @@ -89,11 +89,12 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) ethchain.InitFees() - ethutil.ReadConfig(".ethereum") + ethutil.ReadConfig(DataDir) ethutil.Config.Seed = UseSeed // Instantiated a eth stack ethereum, err := eth.New(eth.CapDefault, UseUPnP) + ethereum.Port = OutboundPort if err != nil { log.Println("eth start err:", err) return diff --git a/test_app.qml b/test_app.qml index c7593bf12e..aace4e881f 100644 --- a/test_app.qml +++ b/test_app.qml @@ -1,7 +1,7 @@ import QtQuick 2.0 import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; -import GoExtensions 1.0 +import Ethereum 1.0 ApplicationWindow { minimumWidth: 500 @@ -29,7 +29,7 @@ ApplicationWindow { anchors.topMargin: 5 text: "Place bet" onClicked: { - txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", parseInt(textField.text)) + txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", textField.text) } } } diff --git a/ui/gui.go b/ui/gui.go index fad7e95911..51fd6d9a84 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -12,41 +12,56 @@ import ( "time" ) +// Block interface exposed to QML +type Block struct { + Number int + Hash string +} + +// Creates a new QML Block from a chain block +func NewBlockFromBlock(block *ethchain.Block) *Block { + info := block.BlockInfo() + hash := hex.EncodeToString(block.Hash()) + + return &Block{Number: int(info.Number), Hash: hash} +} + type Gui struct { - win *qml.Window + // The main application window + win *qml.Window + // QML Engine engine *qml.Engine component *qml.Common - eth *eth.Ethereum + // The ethereum interface + eth *eth.Ethereum - // The Ethereum library + // The public Ethereum library lib *EthLib } +// Create GUI, but doesn't start it func New(ethereum *eth.Ethereum) *Gui { lib := &EthLib{blockManager: ethereum.BlockManager, blockChain: ethereum.BlockManager.BlockChain(), txPool: ethereum.TxPool} - return &Gui{eth: ethereum, lib: lib} -} + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(1).Bytes() -type Block struct { - Number int - Hash string -} - -func NewBlockFromBlock(block *ethchain.Block) *Block { - info := block.BlockInfo() - hash := hex.EncodeToString(block.Hash()) + ethereum.BlockManager.WatchAddr(addr) - return &Block{Number: int(info.Number), Hash: hash} + return &Gui{eth: ethereum, lib: lib} } func (ui *Gui) Start() { - qml.RegisterTypes("GoExtensions", 1, 0, []qml.TypeSpec{{ + // Register ethereum functions + qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, }}) ethutil.Config.Log.Infoln("[GUI] Starting GUI") + // Create a new QML engine ui.engine = qml.NewEngine() + // Load the main QML interface component, err := ui.engine.LoadFile("wallet.qml") if err != nil { panic(err) @@ -55,13 +70,18 @@ func (ui *Gui) Start() { ui.win = component.CreateWindow(nil) context := ui.engine.Context() + + // Expose the eth library and the ui library to QML context.SetVar("eth", ui.lib) context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) + // Register the ui as a block processor ui.eth.BlockManager.SecondaryBlockProcessor = ui + // Add the ui as a log system so we can log directly to the UGI ethutil.Config.Log.AddLogSystem(ui) + // Loads previous blocks go ui.setInitialBlockChain() go ui.updatePeers() @@ -70,6 +90,7 @@ func (ui *Gui) Start() { } func (ui *Gui) setInitialBlockChain() { + // Load previous 10 blocks chain := ui.eth.BlockManager.BlockChain().GetChain(ui.eth.BlockManager.BlockChain().CurrentBlock.Hash(), 10) for _, block := range chain { ui.ProcessBlock(block) @@ -81,17 +102,24 @@ func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } +// Logging functions that log directly to the GUI interface func (ui *Gui) Println(v ...interface{}) { - str := fmt.Sprintln(v...) - // remove last \n - ui.win.Root().Call("addLog", str[:len(str)-1]) + str := strings.TrimRight(fmt.Sprintln(v...), "\n") + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } } func (ui *Gui) Printf(format string, v ...interface{}) { str := strings.TrimRight(fmt.Sprintf(format, v...), "\n") - ui.win.Root().Call("addLog", str) + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } } +// Simple go routine function that updates the list of peers in the GUI func (ui *Gui) updatePeers() { for { ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) @@ -99,11 +127,14 @@ func (ui *Gui) updatePeers() { } } +// UI Library that has some basic functionality exposed type UiLib struct { - engine *qml.Engine - eth *eth.Ethereum + engine *qml.Engine + eth *eth.Ethereum + connected bool } +// Opens a QML file (external application) func (ui *UiLib) Open(path string) { component, err := ui.engine.LoadFile(path[7:]) if err != nil { @@ -118,7 +149,13 @@ func (ui *UiLib) Open(path string) { } func (ui *UiLib) Connect() { - ui.eth.Start() + if !ui.connected { + ui.eth.Start() + } +} + +func (ui *UiLib) ConnectToPeer(addr string) { + ui.eth.ConnectToPeer(addr) } type Tester struct { diff --git a/ui/library.go b/ui/library.go index 36952e198e..0dfb10ac75 100644 --- a/ui/library.go +++ b/ui/library.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" - "math/big" ) type EthLib struct { @@ -14,15 +13,18 @@ type EthLib struct { txPool *ethchain.TxPool } -func (lib *EthLib) CreateTx(receiver string, amount uint64) string { +func (lib *EthLib) CreateTx(receiver, a string) string { hash, err := hex.DecodeString(receiver) if err != nil { return err.Error() } - - tx := ethchain.NewTransaction(hash, big.NewInt(int64(amount)), []string{""}) data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) + + amount := ethutil.Big(a) + tx := ethchain.NewTransaction(hash, amount, []string{""}) + tx.Nonce = lib.blockManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce + tx.Sign(keyRing.Get(0).Bytes()) lib.txPool.QueueTransaction(tx) diff --git a/wallet.qml b/wallet.qml index e6cb32b182..b186148012 100644 --- a/wallet.qml +++ b/wallet.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; import QtQuick.Dialogs 1.0; import QtQuick.Window 2.1; -import GoExtensions 1.0 +import Ethereum 1.0 ApplicationWindow { id: root @@ -22,15 +22,19 @@ ApplicationWindow { width: parent.width Button { text: "Send" - onClicked: console.log("SEND") + onClicked: { + console.log(eth.createTx(txReceiver.text, txAmount.text)) + } } TextField { + id: txAmount width: 200 placeholderText: "Amount" } TextField { + id: txReceiver width: 300 placeholderText: "Receiver Address (or empty for contract)" Layout.fillWidth: true @@ -47,15 +51,43 @@ ApplicationWindow { anchors.left: parent.left TextArea { - id: codeView - width: parent.width /2 + id: codeView + width: parent.width /2 } TextArea { - readOnly: true + readOnly: true } } + MenuBar { + Menu { + title: "File" + MenuItem { + text: "Import App" + shortcut: "Ctrl+o" + onTriggered: openAppDialog.open() + } + } + + Menu { + title: "Network" + MenuItem { + text: "Add Peer" + shortcut: "Ctrl+p" + onTriggered: { + addPeerWin.visible = true + } + } + + MenuItem { + text: "Start" + onTriggered: ui.connect() + } + } + } + + property var blockModel: ListModel { id: blockModel } @@ -142,6 +174,34 @@ ApplicationWindow { } } + Window { + id: addPeerWin + visible: false + minimumWidth: 230 + maximumWidth: 230 + maximumHeight: 50 + minimumHeight: 50 + + TextField { + id: addrField + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + placeholderText: "address:port" + } + Button { + anchors.left: addrField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Add" + onClicked: { + ui.connectToPeer(addrField.text) + addrPeerWin.visible = false + } + } + } + + function addBlock(block) { blockModel.insert(0, {number: block.number, hash: block.hash}) } From 0656f465b0c0690f237e42ac1e8f306dcda6c40b Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 23 Feb 2014 01:56:04 +0100 Subject: [PATCH 11/21] Added transactions window --- transactions.qml | 9 +++ ui/gui.go | 19 +++++ wallet.qml | 177 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 163 insertions(+), 42 deletions(-) create mode 100644 transactions.qml diff --git a/transactions.qml b/transactions.qml new file mode 100644 index 0000000000..e9a035a854 --- /dev/null +++ b/transactions.qml @@ -0,0 +1,9 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; + +Rectangle { + id: transactionView + visible: false + Text { text: "TX VIEW" } +} diff --git a/ui/gui.go b/ui/gui.go index 51fd6d9a84..f0e281de1c 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -18,6 +18,17 @@ type Block struct { Hash string } +type Tx struct { + Value, Hash, Address string +} + +func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { + hash := hex.EncodeToString(tx.Hash()) + sender := hex.EncodeToString(tx.Recipient) + + return &Tx{Hash: hash[:4], Value: tx.Value.String(), Address: sender} +} + // Creates a new QML Block from a chain block func NewBlockFromBlock(block *ethchain.Block) *Block { info := block.BlockInfo() @@ -56,6 +67,8 @@ func (ui *Gui) Start() { // Register ethereum functions qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, + }, { + Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, }}) ethutil.Config.Log.Infoln("[GUI] Starting GUI") @@ -66,6 +79,7 @@ func (ui *Gui) Start() { if err != nil { panic(err) } + ui.engine.LoadFile("transactions.qml") ui.win = component.CreateWindow(nil) @@ -77,6 +91,7 @@ func (ui *Gui) Start() { // Register the ui as a block processor ui.eth.BlockManager.SecondaryBlockProcessor = ui + ui.eth.TxPool.SecondaryProcessor = ui // Add the ui as a log system so we can log directly to the UGI ethutil.Config.Log.AddLogSystem(ui) @@ -102,6 +117,10 @@ func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } +func (ui *Gui) ProcessTransaction(tx *ethchain.Transaction) { + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) +} + // Logging functions that log directly to the GUI interface func (ui *Gui) Println(v ...interface{}) { str := strings.TrimRight(fmt.Sprintln(v...), "\n") diff --git a/wallet.qml b/wallet.qml index b186148012..6ee6ff1103 100644 --- a/wallet.qml +++ b/wallet.qml @@ -3,18 +3,18 @@ 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 ApplicationWindow { id: root - width: 800 + width: 900 height: 600 minimumHeight: 300 title: "Ethereal" - toolBar: ToolBar { id: mainToolbar @@ -43,22 +43,6 @@ ApplicationWindow { } } - SplitView { - id: splitView - height: 200 - anchors.top: parent.top - anchors.right: parent.right - anchors.left: parent.left - - TextArea { - id: codeView - width: parent.width /2 - } - - TextArea { - readOnly: true - } - } MenuBar { Menu { @@ -85,42 +69,133 @@ ApplicationWindow { onTriggered: ui.connect() } } + + Menu { + title: "Help" + MenuItem { + text: "About" + onTriggered: { + aboutWin.visible = true + } + } + } + } property var blockModel: ListModel { id: blockModel } + function setView(view) { + mainView.visible = false + transactionView.visible = false + view.visible = true + } - TableView { - id: blockTable - width: parent.width - anchors.top: splitView.bottom - anchors.bottom: logView.top - TableViewColumn{ role: "number" ; title: "#" ; width: 100 } - TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + SplitView { + anchors.fill: parent + + + Rectangle { + id: menu + width: 200 + anchors.bottom: parent.bottom + anchors.top: parent.top + color: "#D9DDE7" + + GridLayout { + columns: 1 + Button { + text: "Main" + onClicked: { + setView(mainView) + } + } + Button { + text: "Transactions" + onClicked: { + setView(transactionView) + } + } + } + + } - model: blockModel + property var txModel: ListModel { + id: txModel + } - onDoubleClicked: { - popup.visible = true - popup.block = eth.getBlock(blockModel.get(row).hash) - popup.hashLabel.text = popup.block.hash + Rectangle { + id: transactionView + visible: false + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + TableView { + id: txTableView + anchors.fill: parent + TableViewColumn{ role: "hash" ; title: "#" ; width: 150 } + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "address" ; title: "Address" ; } + + model: txModel + } } - } - property var logModel: ListModel { - id: logModel - } + Rectangle { + id: mainView + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.top: parent.top + SplitView { + id: splitView + height: 200 + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + TextArea { + id: codeView + width: parent.width /2 + } + + TextArea { + readOnly: true + } + } + + TableView { + id: blockTable + width: parent.width + anchors.top: splitView.bottom + anchors.bottom: logView.top + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + + model: blockModel - TableView { - id: logView - width: parent.width - height: 150 - anchors.bottom: parent.bottom - TableViewColumn{ role: "description" ; title: "log" } + onDoubleClicked: { + popup.visible = true + popup.block = eth.getBlock(blockModel.get(row).hash) + popup.hashLabel.text = popup.block.hash + } + } - model: logModel + property var logModel: ListModel { + id: logModel + } + + TableView { + id: logView + width: parent.width + height: 150 + anchors.bottom: parent.bottom + TableViewColumn{ role: "description" ; title: "log" } + + model: logModel + } + } } FileDialog { @@ -146,7 +221,6 @@ ApplicationWindow { text: "Import App" } - Label { text: "0.0.1" } Label { anchors.right: peerImage.left anchors.rightMargin: 5 @@ -201,6 +275,25 @@ ApplicationWindow { } } + Window { + id: aboutWin + visible: false + title: "About" + minimumWidth: 300 + maximumWidth: 300 + maximumHeight: 200 + minimumHeight: 200 + + Text { + font.pointSize: 18 + text: "Eth Go" + } + + } + + function addTx(tx) { + txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) + } function addBlock(block) { blockModel.insert(0, {number: block.number, hash: block.hash}) From fe9eb472887baec464cc50657affd85b13bcbeba Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 24 Feb 2014 13:51:16 +0100 Subject: [PATCH 12/21] Minor fixes that to reflect changes in library --- dev_console.go | 6 +++--- ethereum.go | 2 ++ ui/library.go | 28 +++++++++++++++++++++------- wallet.qml | 7 ++++--- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/dev_console.go b/dev_console.go index 2e6b385df2..696493cdde 100644 --- a/dev_console.go +++ b/dev_console.go @@ -197,16 +197,16 @@ func (i *Console) ParseInput(input string) bool { } case "contract": fmt.Println("Contract editor (Ctrl-D = done)") - code := i.Editor() + code := ethchain.Compile(i.Editor()) - contract := ethchain.NewTransaction([]byte{}, ethutil.Big(tokens[1]), code) + contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) contract.Sign(keyRing.Get(0).Bytes()) i.ethereum.TxPool.QueueTransaction(contract) - fmt.Printf("%x\n", contract.Hash()) + fmt.Printf("%x\n", contract.Hash()[12:]) case "exit", "quit", "q": return false case "help": diff --git a/ethereum.go b/ethereum.go index 384b227483..0b941dce3f 100644 --- a/ethereum.go +++ b/ethereum.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/ethwire" "github.com/ethereum/go-ethereum/ui" "github.com/niemeyer/qml" "github.com/obscuren/secp256k1-go" @@ -195,6 +196,7 @@ func main() { // Search the nonce block.Nonce = pow.Search(block) + ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) err := ethereum.BlockManager.ProcessBlock(block) if err != nil { log.Println(err) diff --git a/ui/library.go b/ui/library.go index 0dfb10ac75..c9273e8c5e 100644 --- a/ui/library.go +++ b/ui/library.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "strings" ) type EthLib struct { @@ -13,22 +14,34 @@ type EthLib struct { txPool *ethchain.TxPool } -func (lib *EthLib) CreateTx(receiver, a string) string { - hash, err := hex.DecodeString(receiver) - if err != nil { - return err.Error() +func (lib *EthLib) CreateTx(receiver, a, data string) string { + var hash []byte + if len(receiver) == 0 { + hash = ethchain.ContractAddr + } else { + var err error + hash, err = hex.DecodeString(receiver) + if err != nil { + return err.Error() + } } - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) + + k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(k) amount := ethutil.Big(a) - tx := ethchain.NewTransaction(hash, amount, []string{""}) + code := ethchain.Compile(strings.Split(data, "\n")) + tx := ethchain.NewTransaction(hash, amount, code) tx.Nonce = lib.blockManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce tx.Sign(keyRing.Get(0).Bytes()) lib.txPool.QueueTransaction(tx) + if len(receiver) == 0 { + ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } + return ethutil.Hex(tx.Hash()) } @@ -40,5 +53,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/wallet.qml b/wallet.qml index 6ee6ff1103..e7145cef37 100644 --- a/wallet.qml +++ b/wallet.qml @@ -23,7 +23,7 @@ ApplicationWindow { Button { text: "Send" onClicked: { - console.log(eth.createTx(txReceiver.text, txAmount.text)) + console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) } } @@ -300,8 +300,9 @@ ApplicationWindow { } function addLog(str) { - console.log(str) - logModel.insert(0, {description: str}) + if(str.len != 0) { + logModel.append({description: str}) + } } function setPeers(text) { From e60ff6ca41832c8124acfab6b8408516d60ac140 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 25 Feb 2014 10:54:15 +0100 Subject: [PATCH 13/21] Moved ui lib --- ui/ui_lib.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ui/ui_lib.go diff --git a/ui/ui_lib.go b/ui/ui_lib.go new file mode 100644 index 0000000000..93712eba22 --- /dev/null +++ b/ui/ui_lib.go @@ -0,0 +1,38 @@ +package ethui + +import ( + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethutil" + "github.com/niemeyer/qml" +) + +// UI Library that has some basic functionality exposed +type UiLib struct { + engine *qml.Engine + eth *eth.Ethereum + connected bool +} + +// Opens a QML file (external application) +func (ui *UiLib) Open(path string) { + component, err := ui.engine.LoadFile(path[7:]) + if err != nil { + ethutil.Config.Log.Debugln(err) + } + win := component.CreateWindow(nil) + + go func() { + win.Show() + win.Wait() + }() +} + +func (ui *UiLib) Connect() { + if !ui.connected { + ui.eth.Start() + } +} + +func (ui *UiLib) ConnectToPeer(addr string) { + ui.eth.ConnectToPeer(addr) +} From 78b6e7ad9531461f389c5de3cef10fc665607050 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 25 Feb 2014 10:54:37 +0100 Subject: [PATCH 14/21] Proper Nonce --- ui/gui.go | 143 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index f0e281de1c..72e5b976aa 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -1,15 +1,15 @@ package ethui import ( - "bufio" + "bytes" "encoding/hex" "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" "strings" - "time" ) // Block interface exposed to QML @@ -26,7 +26,7 @@ func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { hash := hex.EncodeToString(tx.Hash()) sender := hex.EncodeToString(tx.Recipient) - return &Tx{Hash: hash[:4], Value: tx.Value.String(), Address: sender} + return &Tx{Hash: hash, Value: tx.Value.String(), Address: sender} } // Creates a new QML Block from a chain block @@ -48,11 +48,19 @@ type Gui struct { // The public Ethereum library lib *EthLib + + txDb *ethdb.LDBDatabase + + addr []byte } // Create GUI, but doesn't start it func New(ethereum *eth.Ethereum) *Gui { lib := &EthLib{blockManager: ethereum.BlockManager, blockChain: ethereum.BlockManager.BlockChain(), txPool: ethereum.TxPool} + db, err := ethdb.NewLDBDatabase("tx_database") + if err != nil { + panic(err) + } data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) @@ -60,10 +68,12 @@ func New(ethereum *eth.Ethereum) *Gui { ethereum.BlockManager.WatchAddr(addr) - return &Gui{eth: ethereum, lib: lib} + return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} } func (ui *Gui) Start() { + defer ui.txDb.Close() + // Register ethereum functions qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, @@ -91,17 +101,20 @@ func (ui *Gui) Start() { // Register the ui as a block processor ui.eth.BlockManager.SecondaryBlockProcessor = ui - ui.eth.TxPool.SecondaryProcessor = ui + //ui.eth.TxPool.SecondaryProcessor = ui // Add the ui as a log system so we can log directly to the UGI ethutil.Config.Log.AddLogSystem(ui) // Loads previous blocks go ui.setInitialBlockChain() - go ui.updatePeers() + go ui.readPreviousTransactions() + go ui.update() ui.win.Show() ui.win.Wait() + + ui.eth.Stop() } func (ui *Gui) setInitialBlockChain() { @@ -113,12 +126,72 @@ func (ui *Gui) setInitialBlockChain() { } +func (ui *Gui) readPreviousTransactions() { + it := ui.txDb.Db().NewIterator(nil) + for it.Next() { + tx := ethchain.NewTransactionFromBytes(it.Value()) + + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + } + it.Release() +} + func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } func (ui *Gui) ProcessTransaction(tx *ethchain.Transaction) { + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + + // TODO replace with general subscribe model +} + +// Simple go routine function that updates the list of peers in the GUI +func (ui *Gui) update() { + txChan := make(chan ethchain.TxMsg) + ui.eth.TxPool.Subscribe(txChan) + + account := ui.eth.BlockManager.GetAddrState(ui.addr).Account + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", account.Amount)) + for { + select { + case txMsg := <-txChan: + tx := txMsg.Tx + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + // Yeah, yeah, stupid code. Refactor next week + if txMsg.Type == ethchain.TxPre { + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (- %v)", account.Amount, tx.Value)) + ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 + fmt.Println("Nonce", ui.eth.BlockManager.GetAddrState(ui.addr).Nonce) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (+ %v)", account.Amount, tx.Value)) + } + } else { + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + amount := account.Amount.Sub(account.Amount, tx.Value) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", amount)) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + amount := account.Amount.Sub(account.Amount, tx.Value) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", amount)) + } + } + } + + /* + accountAmount := ui.eth.BlockManager.GetAddrState(ui.addr).Account.Amount + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", accountAmount)) + + ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) + + time.Sleep(1 * time.Second) + */ + + } } // Logging functions that log directly to the GUI interface @@ -137,61 +210,3 @@ func (ui *Gui) Printf(format string, v ...interface{}) { ui.win.Root().Call("addLog", line) } } - -// Simple go routine function that updates the list of peers in the GUI -func (ui *Gui) updatePeers() { - for { - ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) - time.Sleep(1 * time.Second) - } -} - -// UI Library that has some basic functionality exposed -type UiLib struct { - engine *qml.Engine - eth *eth.Ethereum - connected bool -} - -// Opens a QML file (external application) -func (ui *UiLib) Open(path string) { - component, err := ui.engine.LoadFile(path[7:]) - if err != nil { - ethutil.Config.Log.Debugln(err) - } - win := component.CreateWindow(nil) - - go func() { - win.Show() - win.Wait() - }() -} - -func (ui *UiLib) Connect() { - if !ui.connected { - ui.eth.Start() - } -} - -func (ui *UiLib) ConnectToPeer(addr string) { - ui.eth.ConnectToPeer(addr) -} - -type Tester struct { - root qml.Object -} - -func (t *Tester) Compile(area qml.Object) { - fmt.Println(area) - ethutil.Config.Log.Infoln("[TESTER] Compiling") - - code := area.String("text") - - scanner := bufio.NewScanner(strings.NewReader(code)) - scanner.Split(bufio.ScanLines) - - var lines []string - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } -} From 6451a7187a5eeff6ac819ded11b6e7f0a5aa1b1b Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 25 Feb 2014 10:55:44 +0100 Subject: [PATCH 15/21] Minor UI change --- ui/library.go | 2 ++ wallet.qml | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ui/library.go b/ui/library.go index c9273e8c5e..3bbb01314d 100644 --- a/ui/library.go +++ b/ui/library.go @@ -40,6 +40,8 @@ func (lib *EthLib) CreateTx(receiver, a, data string) string { if len(receiver) == 0 { ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } else { + ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) } return ethutil.Hex(tx.Hash()) diff --git a/wallet.qml b/wallet.qml index e7145cef37..3e921b78d6 100644 --- a/wallet.qml +++ b/wallet.qml @@ -221,6 +221,10 @@ ApplicationWindow { text: "Import App" } + Label { + id: walletValueLabel + } + Label { anchors.right: peerImage.left anchors.rightMargin: 5 @@ -270,7 +274,7 @@ ApplicationWindow { text: "Add" onClicked: { ui.connectToPeer(addrField.text) - addrPeerWin.visible = false + addPeerWin.visible = false } } } @@ -291,6 +295,10 @@ ApplicationWindow { } + function setWalletValue(value) { + walletValueLabel.text = value + } + function addTx(tx) { txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) } From dba1ba38220c92f96aaeeb2d4cc99c1991220dc4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 25 Feb 2014 11:24:04 +0100 Subject: [PATCH 16/21] Currency to string --- ui/gui.go | 14 +++++++------- wallet.qml | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index 72e5b976aa..6e30b5891b 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -26,7 +26,7 @@ func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { hash := hex.EncodeToString(tx.Hash()) sender := hex.EncodeToString(tx.Recipient) - return &Tx{Hash: hash, Value: tx.Value.String(), Address: sender} + return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender} } // Creates a new QML Block from a chain block @@ -154,7 +154,7 @@ func (ui *Gui) update() { ui.eth.TxPool.Subscribe(txChan) account := ui.eth.BlockManager.GetAddrState(ui.addr).Account - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", account.Amount)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) for { select { case txMsg := <-txChan: @@ -162,22 +162,22 @@ func (ui *Gui) update() { ui.txDb.Put(tx.Hash(), tx.RlpEncode()) ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - // Yeah, yeah, stupid code. Refactor next week + // TODO FOR THE LOVE OF EVERYTHING GOOD IN THIS WORLD REFACTOR ME if txMsg.Type == ethchain.TxPre { if bytes.Compare(tx.Sender(), ui.addr) == 0 { - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (- %v)", account.Amount, tx.Value)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (- %v)", ethutil.CurrencyToString(account.Amount), ethutil.CurrencyToString(tx.Value))) ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 fmt.Println("Nonce", ui.eth.BlockManager.GetAddrState(ui.addr).Nonce) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (+ %v)", account.Amount, tx.Value)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (+ %v)", ethutil.CurrencyToString(account.Amount), ethutil.CurrencyToString(tx.Value))) } } else { if bytes.Compare(tx.Sender(), ui.addr) == 0 { amount := account.Amount.Sub(account.Amount, tx.Value) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", amount)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { amount := account.Amount.Sub(account.Amount, tx.Value) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", amount)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) } } } diff --git a/wallet.qml b/wallet.qml index 3e921b78d6..04c1ffaed1 100644 --- a/wallet.qml +++ b/wallet.qml @@ -215,6 +215,7 @@ ApplicationWindow { text: "Connect" } Button { + id: importAppButton anchors.left: connectButton.right anchors.leftMargin: 5 onClicked: openAppDialog.open() @@ -222,6 +223,8 @@ ApplicationWindow { } Label { + anchors.left: importAppButton.right + anchors.leftMargin: 5 id: walletValueLabel } From 8d1d72abeeee68ddcdffe02a28587a9bec1647e9 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:16:46 +0100 Subject: [PATCH 17/21] Improved overall UI design and added a bunch of icons --- net.png | Bin 0 -> 4669 bytes net.pxm | Bin 0 -> 95418 bytes new.png | Bin 0 -> 4776 bytes tx.png | Bin 0 -> 4070 bytes tx.pxm | Bin 0 -> 93227 bytes ui/gui.go | 51 +++++++++-------- wallet.qml | 157 ++++++++++++++++++++++++++++++++--------------------- 7 files changed, 124 insertions(+), 84 deletions(-) create mode 100644 net.png create mode 100644 net.pxm create mode 100644 new.png create mode 100644 tx.png create mode 100644 tx.pxm diff --git a/net.png b/net.png new file mode 100644 index 0000000000000000000000000000000000000000..65a20ea00cc191087fd90704f5874dc376c0433b GIT binary patch literal 4669 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

xNm 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 literal 0 HcmV?d00001 diff --git a/new.png b/new.png new file mode 100644 index 0000000000000000000000000000000000000000..e8009674889f3b244c595866ff2003ce24149d1e GIT binary patch literal 4776 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 literal 0 HcmV?d00001 diff --git a/tx.png b/tx.png new file mode 100644 index 0000000000000000000000000000000000000000..62204c3152f55b9fa0265bb322721bf352518025 GIT binary patch literal 4070 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 literal 0 HcmV?d00001 diff --git a/tx.pxm b/tx.pxm new file mode 100644 index 0000000000000000000000000000000000000000..881420da9670686061c113d6fb2c6f27c72ac1bb GIT binary patch literal 93227 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 literal 0 HcmV?d00001 diff --git a/ui/gui.go b/ui/gui.go index 6e30b5891b..556e682a90 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" + "math/big" "strings" ) @@ -62,9 +63,8 @@ func New(ethereum *eth.Ethereum) *Gui { panic(err) } - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(1).Bytes() + key := ethutil.Config.Db.GetKeys()[0] + addr := key.Address() ethereum.BlockManager.WatchAddr(addr) @@ -127,7 +127,7 @@ func (ui *Gui) setInitialBlockChain() { } func (ui *Gui) readPreviousTransactions() { - it := ui.txDb.Db().NewIterator(nil) + it := ui.txDb.Db().NewIterator(nil, nil) for it.Next() { tx := ethchain.NewTransactionFromBytes(it.Value()) @@ -140,45 +140,50 @@ func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } -func (ui *Gui) ProcessTransaction(tx *ethchain.Transaction) { - ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - - // TODO replace with general subscribe model -} - // Simple go routine function that updates the list of peers in the GUI func (ui *Gui) update() { - txChan := make(chan ethchain.TxMsg) + txChan := make(chan ethchain.TxMsg, 1) ui.eth.TxPool.Subscribe(txChan) account := ui.eth.BlockManager.GetAddrState(ui.addr).Account + unconfirmedFunds := new(big.Int) ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) for { select { case txMsg := <-txChan: tx := txMsg.Tx - ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - // TODO FOR THE LOVE OF EVERYTHING GOOD IN THIS WORLD REFACTOR ME if txMsg.Type == ethchain.TxPre { if bytes.Compare(tx.Sender(), ui.addr) == 0 { - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (- %v)", ethutil.CurrencyToString(account.Amount), ethutil.CurrencyToString(tx.Value))) + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 - fmt.Println("Nonce", ui.eth.BlockManager.GetAddrState(ui.addr).Nonce) + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (+ %v)", ethutil.CurrencyToString(account.Amount), ethutil.CurrencyToString(tx.Value))) + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + unconfirmedFunds.Add(unconfirmedFunds, tx.Value) + } + + pos := "+" + if unconfirmedFunds.Cmp(big.NewInt(0)) >= 0 { + pos = "-" } + val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds))) + str := fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(account.Amount), pos, val) + + ui.win.Root().Call("setWalletValue", str) } else { + amount := account.Amount if bytes.Compare(tx.Sender(), ui.addr) == 0 { - amount := account.Amount.Sub(account.Amount, tx.Value) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) + amount.Sub(account.Amount, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - amount := account.Amount.Sub(account.Amount, tx.Value) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) + amount.Add(account.Amount, tx.Value) } + + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) } } diff --git a/wallet.qml b/wallet.qml index 04c1ffaed1..8c91039fcf 100644 --- a/wallet.qml +++ b/wallet.qml @@ -15,35 +15,6 @@ ApplicationWindow { title: "Ethereal" - toolBar: ToolBar { - id: mainToolbar - - RowLayout { - width: parent.width - Button { - text: "Send" - onClicked: { - console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) - } - } - - TextField { - id: txAmount - width: 200 - placeholderText: "Amount" - } - - TextField { - id: txReceiver - width: 300 - placeholderText: "Receiver Address (or empty for contract)" - Layout.fillWidth: true - } - - } - } - - MenuBar { Menu { title: "File" @@ -86,35 +57,61 @@ ApplicationWindow { property var blockModel: ListModel { id: blockModel } - function setView(view) { - mainView.visible = false - transactionView.visible = false - view.visible = true - } + + function setView(view) { + networkView.visible = false + historyView.visible = false + newTxView.visible = false + view.visible = true + //root.title = "Ethereal - " = view.title + } SplitView { anchors.fill: parent - + resizing: false Rectangle { id: menu - width: 200 + Layout.minimumWidth: 80 + Layout.maximumWidth: 80 anchors.bottom: parent.bottom anchors.top: parent.top - color: "#D9DDE7" + //color: "#D9DDE7" + color: "#252525" - GridLayout { - columns: 1 - Button { - text: "Main" - onClicked: { - setView(mainView) + ColumnLayout { + y: 50 + anchors.left: parent.left + anchors.right: parent.right + height: 200 + Image { + source: "tx.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(historyView) + } } } - Button { - text: "Transactions" - onClicked: { - setView(transactionView) + Image { + source: "new.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(newTxView) + } + } + } + Image { + source: "net.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(networkView) + } } } } @@ -126,8 +123,8 @@ ApplicationWindow { } Rectangle { - id: transactionView - visible: false + id: historyView + property var title: "Transactions" anchors.right: parent.right anchors.left: menu.right anchors.bottom: parent.bottom @@ -135,40 +132,73 @@ ApplicationWindow { TableView { id: txTableView anchors.fill: parent - TableViewColumn{ role: "hash" ; title: "#" ; width: 150 } TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } - TableViewColumn{ role: "address" ; title: "Address" ; } + TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } model: txModel } } Rectangle { - id: mainView + id: newTxView + property var title: "New transaction" + visible: false anchors.right: parent.right + anchors.left: menu.right anchors.bottom: parent.bottom anchors.top: parent.top - SplitView { - id: splitView - height: 200 - anchors.top: parent.top - anchors.right: parent.right + color: "#00000000" + + ColumnLayout { + width: 400 anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 5 + anchors.topMargin: 5 + TextField { + id: txAmount + width: 200 + placeholderText: "Amount" + } + + TextField { + id: txReceiver + placeholderText: "Receiver Address (or empty for contract)" + Layout.fillWidth: true + } + Label { + text: "Transaction data" + } TextArea { id: codeView + anchors.topMargin: 5 + Layout.fillWidth: true width: parent.width /2 } - TextArea { - readOnly: true + Button { + text: "Send" + onClicked: { + console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) + } } } + } + + + Rectangle { + id: networkView + property var title: "Network" + visible: false + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.top: parent.top TableView { id: blockTable width: parent.width - anchors.top: splitView.bottom + anchors.top: parent.top anchors.bottom: logView.top TableViewColumn{ role: "number" ; title: "#" ; width: 100 } TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } @@ -210,8 +240,13 @@ ApplicationWindow { RowLayout { anchors.fill: parent Button { + property var enabled: true id: connectButton - onClicked: ui.connect() + onClicked: { + if(this.enabled) { + ui.connect(this) + } + } text: "Connect" } Button { From 5e7f8cca4f83566fdfbd1eb76c8b565094958e6c Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:17:02 +0100 Subject: [PATCH 18/21] Exit after importing a key --- ethereum.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/ethereum.go b/ethereum.go index 0b941dce3f..336cd4d001 100644 --- a/ethereum.go +++ b/ethereum.go @@ -36,7 +36,8 @@ func CreateKeyPair(force bool) { data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) if len(data) == 0 || force { pub, prv := secp256k1.GenerateKeyPair() - addr := ethutil.Sha3Bin(pub[1:])[12:] + pair := ðutil.Key{PrivateKey: prv, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) fmt.Printf(` Generating new address and keypair. @@ -48,10 +49,8 @@ prvk: %x pubk: %x ++++++++++++++++++++++++++++++++++++++++++++ -`, addr, prv, pub) +`, pair.Address(), prv, pub) - keyRing := ethutil.NewValue([]interface{}{prv, addr, pub[1:]}) - ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) } } @@ -61,7 +60,8 @@ func ImportPrivateKey(prvKey string) { // Couldn't think of a better way to get the pub key sig, _ := secp256k1.Sign(msg, key) pub, _ := secp256k1.RecoverPubkey(msg, sig) - addr := ethutil.Sha3Bin(pub[1:])[12:] + pair := ðutil.Key{PrivateKey: key, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) fmt.Printf(` Importing private key @@ -72,10 +72,7 @@ prvk: %x pubk: %x ++++++++++++++++++++++++++++++++++++++++++++ -`, addr, key, pub) - - keyRing := ethutil.NewValue([]interface{}{key, addr, pub[1:]}) - ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) +`, pair.Address(), key, pub) } func main() { @@ -95,11 +92,11 @@ func main() { // Instantiated a eth stack ethereum, err := eth.New(eth.CapDefault, UseUPnP) - ethereum.Port = OutboundPort if err != nil { log.Println("eth start err:", err) return } + ethereum.Port = OutboundPort if GenAddr { fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") @@ -133,6 +130,7 @@ func main() { if r == "y" { ImportPrivateKey(ImportKey) + os.Exit(0) } } else { CreateKeyPair(false) @@ -140,9 +138,8 @@ func main() { } if ExportKey { - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - fmt.Printf("%x\n", keyRing.Get(0).Bytes()) + key := ethutil.Config.Db.GetKeys()[0] + fmt.Printf("%x\n", key.PrivateKey) os.Exit(0) } From 075acec9e709d3cda8a791cb0829720f5a1ae142 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:17:26 +0100 Subject: [PATCH 19/21] Changed to new get keys method on database interface --- dev_console.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dev_console.go b/dev_console.go index 696493cdde..09e06aa22e 100644 --- a/dev_console.go +++ b/dev_console.go @@ -179,13 +179,13 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("recipient err:", err) } else { tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - tx.Sign(keyRing.Get(0).Bytes()) - fmt.Printf("%x\n", tx.Hash()) + + key := ethutil.Config.Db.GetKeys()[0] + tx.Sign(key.PrivateKey) i.ethereum.TxPool.QueueTransaction(tx) - } + fmt.Printf("%x\n", tx.Hash()) + } case "gettx": addr, _ := hex.DecodeString(tokens[1]) data, _ := ethutil.Config.Db.Get(addr) @@ -200,9 +200,9 @@ func (i *Console) ParseInput(input string) bool { code := ethchain.Compile(i.Editor()) contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - contract.Sign(keyRing.Get(0).Bytes()) + + key := ethutil.Config.Db.GetKeys()[0] + contract.Sign(key.PrivateKey) i.ethereum.TxPool.QueueTransaction(contract) From 3a35d45ea84e055a4574a7a1d1b4b5485bea1331 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:17:43 +0100 Subject: [PATCH 20/21] Updated readme --- README.md | 41 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index e0d19b7120..034b35f972 100644 --- a/README.md +++ b/README.md @@ -3,44 +3,18 @@ Ethereum [![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) -Ethereum Go developer client (c) Jeffrey Wilcke - -Ethereum is currently in its testing phase. The current state is "Proof -of Concept 2". For build instructions see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge). - -Ethereum Go is split up in several sub packages Please refer to each -individual package for more information. - 1. [eth](https://github.com/ethereum/eth-go) - 2. [ethchain](https://github.com/ethereum/eth-go/ethchain) - 3. [ethwire](https://github.com/ethereum/eth-go/ethwire) - 4. [ethdb](https://github.com/ethereum/eth-go/ethdb) - 5. [ethutil](https://github.com/ethereum/eth-go/ethutil) - -The [eth](https://github.com/ethereum/eth-go) is the top-level package -of the Ethereum protocol. It functions as the Ethereum bootstrapping and -peer communication layer. The [ethchain](https://github.com/ethereum/ethchain-go) -contains the Ethereum blockchain, block manager, transaction and -transaction handlers. The [ethwire](https://github.com/ethereum/ethwire-go) contains -the Ethereum [wire protocol](http://wiki.ethereum.org/index.php/Wire_Protocol) which can be used -to hook in to the Ethereum network. [ethutil](https://github.com/ethereum/ethutil-go) contains -utility functions which are not Ethereum specific. The utility package -contains the [patricia trie](http://wiki.ethereum.org/index.php/Patricia_Tree), -[RLP Encoding](http://wiki.ethereum.org/index.php/RLP) and hex encoding -helpers. The [ethdb](https://github.com/ethereum/ethdb-go) package -contains the LevelDB interface and memory DB interface. - -This executable is the front-end (currently nothing but a dev console) for -the Ethereum Go implementation. - -If you'd like to start developing your own tools please check out the -[development](https://github.com/ethereum/eth-go) package. +Ethereum Go Client (c) Jeffrey Wilcke + +The current state is "Proof of Concept 3". For build instructions see +the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge). + +For the development Go Package please see [eth-go package](https://github.com/ethereum/eth-go). Build ======= For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge) - Command line options ==================== @@ -52,6 +26,9 @@ Command line options -upnp Enable UPnP (= false) -x Desired amount of peers (= 5) -h This help +-gui Launch with GUI (= true) +-dir Data directory used to store configs and databases (=".ethereum") +-import Import a private key (hex) ``` Developer console commands From 560a7073f457a32c9d053f1a4b20f76d949f519d Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:18:19 +0100 Subject: [PATCH 21/21] Fixed connection button spamming --- ui/ui_lib.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/ui_lib.go b/ui/ui_lib.go index 93712eba22..c956fd0324 100644 --- a/ui/ui_lib.go +++ b/ui/ui_lib.go @@ -27,9 +27,11 @@ func (ui *UiLib) Open(path string) { }() } -func (ui *UiLib) Connect() { +func (ui *UiLib) Connect(button qml.Object) { if !ui.connected { ui.eth.Start() + ui.connected = true + button.Set("enabled", false) } }