diff --git a/LICENSE b/LICENSE index 803f2ef4e..b77f7909a 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/README.md b/README.md index d0a08f7b8..034b35f97 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 Go 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). +The current state is "Proof of Concept 3". 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/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) - -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. +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 diff --git a/config.go b/config.go index d13bb863b..bafc3e300 100644 --- a/config.go +++ b/config.go @@ -13,15 +13,23 @@ var AddPeer string var MaxPeer int var GenAddr bool 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") flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") + 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") + 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") flag.Parse() diff --git a/dev_console.go b/dev_console.go index f2283e341..09e06aa22 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 { @@ -136,7 +163,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": @@ -151,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()) - i.ethereum.TxPool.QueueTransaction(tx) - } + 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) @@ -168,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 := ethchain.Compile(i.Editor()) + + contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) + + key := ethutil.Config.Db.GetKeys()[0] + contract.Sign(key.PrivateKey) i.ethereum.TxPool.QueueTransaction(contract) + + fmt.Printf("%x\n", contract.Hash()[12:]) case "exit", "quit", "q": return false case "help": diff --git a/ethereum.go b/ethereum.go index 372d434af..336cd4d00 100644 --- a/ethereum.go +++ b/ethereum.go @@ -5,6 +5,9 @@ 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" "log" "os" @@ -33,13 +36,12 @@ 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. Please keep your keys somewhere save. -Currently Ethereum(G) does not support -exporting keys. ++++++++++++++++ KeyRing +++++++++++++++++++ addr: %x @@ -47,19 +49,45 @@ 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()) } } +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) + pair := ðutil.Key{PrivateKey: key, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) + + fmt.Printf(` +Importing private key + +++++++++++++++++ KeyRing +++++++++++++++++++ +addr: %x +prvk: %x +pubk: %x +++++++++++++++++++++++++++++++++++++++++++++ + +`, pair.Address(), key, pub) +} + 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.ReadConfig(DataDir) ethutil.Config.Seed = UseSeed // Instantiated a eth stack @@ -68,6 +96,7 @@ func main() { 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)") @@ -87,7 +116,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) + os.Exit(0) + } + } else { + CreateKeyPair(false) + } + } + + if ExportKey { + key := ethutil.Config.Db.GetKeys()[0] + fmt.Printf("%x\n", key.PrivateKey) + os.Exit(0) } if ShowGenesis { @@ -111,41 +164,47 @@ func main() { go console.Start() } - RegisterInterupts(ethereum) + if UseGui { + gui := ethui.New(ethereum) + gui.Start() + //ethereum.Stop() + } else { + RegisterInterupts(ethereum) + ethereum.Start() - ethereum.Start() + if StartMining { + log.Printf("Miner started\n") - 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() - // 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()) - 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) - 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) + // 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) + } else { + log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockManager.BlockChain().CurrentBlock) + } } - } - }() - } + }() + } - // Wait for shutdown - ethereum.WaitForShutdown() + // Wait for shutdown + ethereum.WaitForShutdown() + } } diff --git a/net.png b/net.png new file mode 100644 index 000000000..65a20ea00 Binary files /dev/null and b/net.png differ diff --git a/net.pxm b/net.pxm new file mode 100644 index 000000000..20d45d08c Binary files /dev/null and b/net.pxm differ diff --git a/network.png b/network.png new file mode 100644 index 000000000..0a9ffe2ec Binary files /dev/null and b/network.png differ diff --git a/new.png b/new.png new file mode 100644 index 000000000..e80096748 Binary files /dev/null and b/new.png differ diff --git a/test_app.qml b/test_app.qml new file mode 100644 index 000000000..aace4e881 --- /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 Ethereum 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", textField.text) + } + } +} diff --git a/transactions.qml b/transactions.qml new file mode 100644 index 000000000..e9a035a85 --- /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/tx.png b/tx.png new file mode 100644 index 000000000..62204c315 Binary files /dev/null and b/tx.png differ diff --git a/tx.pxm b/tx.pxm new file mode 100644 index 000000000..881420da9 Binary files /dev/null and b/tx.pxm differ diff --git a/ui/gui.go b/ui/gui.go new file mode 100644 index 000000000..556e682a9 --- /dev/null +++ b/ui/gui.go @@ -0,0 +1,217 @@ +package ethui + +import ( + "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" + "math/big" + "strings" +) + +// Block interface exposed to QML +type Block struct { + Number int + 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, Value: ethutil.CurrencyToString(tx.Value), Address: sender} +} + +// 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 { + // The main application window + win *qml.Window + // QML Engine + engine *qml.Engine + component *qml.Common + // The ethereum interface + eth *eth.Ethereum + + // 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) + } + + key := ethutil.Config.Db.GetKeys()[0] + addr := key.Address() + + ethereum.BlockManager.WatchAddr(addr) + + 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 = "" }, + }, { + Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, + }}) + + 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) + } + ui.engine.LoadFile("transactions.qml") + + 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 + //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.readPreviousTransactions() + go ui.update() + + ui.win.Show() + ui.win.Wait() + + ui.eth.Stop() +} + +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) + } + +} + +func (ui *Gui) readPreviousTransactions() { + it := ui.txDb.Db().NewIterator(nil, 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)) +} + +// Simple go routine function that updates the list of peers in the GUI +func (ui *Gui) update() { + 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 + + if txMsg.Type == ethchain.TxPre { + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + 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.Sub(account.Amount, tx.Value) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + amount.Add(account.Amount, tx.Value) + } + + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(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 +func (ui *Gui) Println(v ...interface{}) { + 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") + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } +} diff --git a/ui/library.go b/ui/library.go new file mode 100644 index 000000000..3bbb01314 --- /dev/null +++ b/ui/library.go @@ -0,0 +1,60 @@ +package ethui + +import ( + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "strings" +) + +type EthLib struct { + blockManager *ethchain.BlockManager + blockChain *ethchain.BlockChain + txPool *ethchain.TxPool +} + +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() + } + } + + k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(k) + + amount := ethutil.Big(a) + 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:]) + } else { + ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) + } + + 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/ui/ui_lib.go b/ui/ui_lib.go new file mode 100644 index 000000000..c956fd032 --- /dev/null +++ b/ui/ui_lib.go @@ -0,0 +1,40 @@ +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(button qml.Object) { + if !ui.connected { + ui.eth.Start() + ui.connected = true + button.Set("enabled", false) + } +} + +func (ui *UiLib) ConnectToPeer(addr string) { + ui.eth.ConnectToPeer(addr) +} diff --git a/wallet.qml b/wallet.qml new file mode 100644 index 000000000..8c91039fc --- /dev/null +++ b/wallet.qml @@ -0,0 +1,357 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +ApplicationWindow { + id: root + + width: 900 + height: 600 + minimumHeight: 300 + + title: "Ethereal" + + 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() + } + } + + Menu { + title: "Help" + MenuItem { + text: "About" + onTriggered: { + aboutWin.visible = true + } + } + } + + } + + + property var blockModel: ListModel { + id: blockModel + } + + 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 + Layout.minimumWidth: 80 + Layout.maximumWidth: 80 + anchors.bottom: parent.bottom + anchors.top: parent.top + //color: "#D9DDE7" + color: "#252525" + + ColumnLayout { + y: 50 + anchors.left: parent.left + anchors.right: parent.right + height: 200 + Image { + source: "tx.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(historyView) + } + } + } + Image { + source: "new.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(newTxView) + } + } + } + Image { + source: "net.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(networkView) + } + } + } + } + + } + + property var txModel: ListModel { + id: txModel + } + + Rectangle { + id: historyView + property var title: "Transactions" + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + TableView { + id: txTableView + anchors.fill: parent + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } + + model: txModel + } + } + + Rectangle { + 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 + 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 + } + + 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: parent.top + anchors.bottom: logView.top + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + + model: blockModel + + onDoubleClicked: { + 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" + onAccepted: { + ui.open(openAppDialog.fileUrl.toString()) + } + } + + statusBar: StatusBar { + RowLayout { + anchors.fill: parent + Button { + property var enabled: true + id: connectButton + onClicked: { + if(this.enabled) { + ui.connect(this) + } + } + text: "Connect" + } + Button { + id: importAppButton + anchors.left: connectButton.right + anchors.leftMargin: 5 + onClicked: openAppDialog.open() + text: "Import App" + } + + Label { + anchors.left: importAppButton.right + anchors.leftMargin: 5 + id: walletValueLabel + } + + 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" + } + } + } + + Window { + id: popup + visible: false + property var block + Label { + id: hashLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + + 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) + addPeerWin.visible = false + } + } + } + + Window { + id: aboutWin + visible: false + title: "About" + minimumWidth: 300 + maximumWidth: 300 + maximumHeight: 200 + minimumHeight: 200 + + Text { + font.pointSize: 18 + text: "Eth Go" + } + + } + + function setWalletValue(value) { + walletValueLabel.text = value + } + + 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}) + } + + function addLog(str) { + if(str.len != 0) { + logModel.append({description: str}) + } + } + + function setPeers(text) { + peerLabel.text = text + } +}