gui.go 10.6 KB
Newer Older
O
obscuren 已提交
1 2
/*
	This file is part of go-ethereum
F
Felix Lange 已提交
3

O
obscuren 已提交
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
	go-ethereum is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	go-ethereum is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
*/
/**
 * @authors
 * 	Jeffrey Wilcke <i@jev.io>
 */
O
obscuren 已提交
21
package main
O
obscuren 已提交
22

O
obscuren 已提交
23 24
import "C"

O
obscuren 已提交
25
import (
O
obscuren 已提交
26
	"encoding/json"
O
obscuren 已提交
27
	"fmt"
28
	"io/ioutil"
O
obscuren 已提交
29
	"math/big"
O
obscuren 已提交
30
	"path"
O
obscuren 已提交
31
	"runtime"
F
Felix Lange 已提交
32
	"sort"
O
obscuren 已提交
33
	"time"
O
Removed  
obscuren 已提交
34

T
Taylor Gerring 已提交
35
	"github.com/ethereum/go-ethereum/common"
O
obscuren 已提交
36 37
	"github.com/ethereum/go-ethereum/core"
	"github.com/ethereum/go-ethereum/core/types"
38
	"github.com/ethereum/go-ethereum/eth"
39
	"github.com/ethereum/go-ethereum/ethdb"
O
obscuren 已提交
40
	"github.com/ethereum/go-ethereum/logger"
41
	"github.com/ethereum/go-ethereum/ui/qt/qwhisper"
O
obscuren 已提交
42
	"github.com/ethereum/go-ethereum/xeth"
O
obscuren 已提交
43
	"github.com/obscuren/qml"
O
obscuren 已提交
44 45
)

O
obscuren 已提交
46
var guilogger = logger.NewLogger("GUI")
47

48 49 50 51 52 53 54
type ServEv byte

const (
	setup ServEv = iota
	update
)

O
obscuren 已提交
55
type Gui struct {
56 57 58
	// The main application window
	win *qml.Window
	// QML Engine
O
obscuren 已提交
59 60
	engine    *qml.Engine
	component *qml.Common
61
	// The ethereum interface
62 63
	eth           *eth.Ethereum
	serviceEvents chan ServEv
O
obscuren 已提交
64

65
	// The public Ethereum library
66 67
	uiLib   *UiLib
	whisper *qwhisper.Whisper
O
obscuren 已提交
68 69 70

	txDb *ethdb.LDBDatabase

71
	open bool
Z
zelig 已提交
72

O
obscuren 已提交
73
	xeth *xeth.XEth
O
obscuren 已提交
74

75
	Session string
M
Maran 已提交
76

O
obscuren 已提交
77
	plugins map[string]plugin
O
obscuren 已提交
78 79
}

80
// Create GUI, but doesn't start it
81 82
func NewWindow(ethereum *eth.Ethereum) *Gui {
	db, err := ethdb.NewLDBDatabase(path.Join(ethereum.DataDir, "tx_database"))
O
obscuren 已提交
83 84 85
	if err != nil {
		panic(err)
	}
O
obscuren 已提交
86

87
	xeth := xeth.New(ethereum, nil)
88
	gui := &Gui{eth: ethereum,
89 90 91 92 93
		txDb:          db,
		xeth:          xeth,
		open:          false,
		plugins:       make(map[string]plugin),
		serviceEvents: make(chan ServEv, 1),
94
	}
95 96
	data, _ := ioutil.ReadFile(path.Join(ethereum.DataDir, "plugins.json"))
	json.Unmarshal(data, &gui.plugins)
O
obscuren 已提交
97 98

	return gui
O
obscuren 已提交
99 100
}

Z
zelig 已提交
101
func (gui *Gui) Start(assetPath, libPath string) {
O
Cleanup  
obscuren 已提交
102
	defer gui.txDb.Close()
O
obscuren 已提交
103

O
obscuren 已提交
104 105
	guilogger.Infoln("Starting GUI")

106 107
	go gui.service()

108 109
	// Register ethereum functions
	qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{
O
obscuren 已提交
110
		Init: func(p *xeth.Block, obj qml.Object) { p.Number = 0; p.Hash = "" },
O
obscuren 已提交
111
	}, {
O
obscuren 已提交
112
		Init: func(p *xeth.Transaction, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" },
113
	}, {
O
obscuren 已提交
114
		Init: func(p *xeth.KeyVal, obj qml.Object) { p.Key = ""; p.Value = "" },
O
obscuren 已提交
115
	}})
116
	// Create a new QML engine
O
Cleanup  
obscuren 已提交
117 118
	gui.engine = qml.NewEngine()
	context := gui.engine.Context()
Z
zelig 已提交
119
	gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath, libPath)
120
	gui.whisper = qwhisper.New(gui.eth.Whisper())
121 122

	// Expose the eth library and the ui library to QML
123 124
	context.SetVar("gui", gui)
	context.SetVar("eth", gui.uiLib)
O
obscuren 已提交
125
	context.SetVar("shh", gui.whisper)
O
obscuren 已提交
126
	//clipboard.SetQMLClipboard(context)
J
Jarrad Hope 已提交
127

O
obscuren 已提交
128
	win, err := gui.showWallet(context)
O
obscuren 已提交
129
	if err != nil {
O
obscuren 已提交
130
		guilogger.Errorln("asset not found: you can set an alternative asset path on the command line using option 'asset_path'", err)
131

O
obscuren 已提交
132 133 134
		panic(err)
	}

Z
zelig 已提交
135
	gui.open = true
136
	win.Show()
O
obscuren 已提交
137

O
obscuren 已提交
138
	win.Wait()
Z
zelig 已提交
139 140 141 142 143 144 145 146
	gui.open = false
}

func (gui *Gui) Stop() {
	if gui.open {
		gui.open = false
		gui.win.Hide()
	}
147

O
obscuren 已提交
148
	guilogger.Infoln("Stopped")
O
obscuren 已提交
149
}
O
obscuren 已提交
150

O
obscuren 已提交
151
func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
O
obscuren 已提交
152
	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/main.qml"))
O
obscuren 已提交
153 154
	if err != nil {
		return nil, err
155
	}
O
obscuren 已提交
156

157
	gui.createWindow(component)
O
obscuren 已提交
158

159 160 161
	return gui.win, nil
}

162 163 164 165 166 167 168
func (gui *Gui) GenerateKey() {
	_, err := gui.eth.AccountManager().NewAccount("hurr")
	if err != nil {
		// TODO: UI feedback?
	}
}

O
obscuren 已提交
169
func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
Z
zelig 已提交
170
	context.SetVar("lib", gui)
O
obscuren 已提交
171 172 173 174 175 176 177 178
	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml"))
	if err != nil {
		return nil, err
	}
	return gui.createWindow(component), nil
}

func (gui *Gui) createWindow(comp qml.Object) *qml.Window {
179 180
	gui.win = comp.CreateWindow(nil)
	gui.uiLib.win = gui.win
O
obscuren 已提交
181 182

	return gui.win
O
obscuren 已提交
183
}
Z
zelig 已提交
184

O
obscuren 已提交
185
func (gui *Gui) setInitialChain(ancientBlocks bool) {
O
obscuren 已提交
186
	sBlk := gui.eth.ChainManager().LastBlockHash()
O
obscuren 已提交
187 188
	blk := gui.eth.ChainManager().GetBlock(sBlk)
	for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) {
O
obscuren 已提交
189
		sBlk = blk.ParentHash()
M
Maran 已提交
190
		gui.processBlock(blk, true)
191 192 193
	}
}

O
obscuren 已提交
194
func (gui *Gui) loadAddressBook() {
O
obscuren 已提交
195 196 197 198 199 200 201
	/*
		view := gui.getObjectByName("infoView")
		nameReg := gui.xeth.World().Config().Get("NameReg")
		if nameReg != nil {
			it := nameReg.Trie().Iterator()
			for it.Next() {
				if it.Key[0] != 0 {
O
obscuren 已提交
202
					view.Call("addAddress", struct{ Name, Address string }{string(it.Key), common.Bytes2Hex(it.Value)})
O
obscuren 已提交
203
				}
O
obscuren 已提交
204

O
obscuren 已提交
205
			}
O
obscuren 已提交
206
		}
O
obscuren 已提交
207
	*/
O
obscuren 已提交
208 209
}

210
func (self *Gui) loadMergedMiningOptions() {
O
obscuren 已提交
211 212 213 214 215 216 217 218 219 220 221 222
	/*
		view := self.getObjectByName("mergedMiningModel")

		mergeMining := self.xeth.World().Config().Get("MergeMining")
		if mergeMining != nil {
			i := 0
			it := mergeMining.Trie().Iterator()
			for it.Next() {
				view.Call("addMergedMiningOption", struct {
					Checked       bool
					Name, Address string
					Id, ItemId    int
O
obscuren 已提交
223
				}{false, string(it.Key), common.Bytes2Hex(it.Value), 0, i})
O
obscuren 已提交
224 225

				i++
226

O
obscuren 已提交
227
			}
O
obscuren 已提交
228
		}
O
obscuren 已提交
229
	*/
230 231
}

232
func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
O
obscuren 已提交
233
	var inout string
T
Taylor Gerring 已提交
234 235
	from, _ := tx.From()
	if gui.eth.AccountManager().HasAccount(common.Hex2Bytes(from.Hex())) {
O
obscuren 已提交
236 237 238 239 240
		inout = "send"
	} else {
		inout = "recv"
	}

241 242 243 244 245
	ptx := xeth.NewTx(tx)
	ptx.Sender = from.Hex()
	if to := tx.To(); to != nil {
		ptx.Address = to.Hex()
	}
O
obscuren 已提交
246

247
	if window == "post" {
O
obscuren 已提交
248
		//gui.getObjectByName("transactionView").Call("addTx", ptx, inout)
249 250 251
	} else {
		gui.getObjectByName("pendingTxView").Call("addTx", ptx, inout)
	}
O
obscuren 已提交
252
}
O
obscuren 已提交
253

O
obscuren 已提交
254
func (gui *Gui) readPreviousTransactions() {
O
obscuren 已提交
255
	it := gui.txDb.NewIterator()
O
obscuren 已提交
256
	for it.Next() {
257
		tx := types.NewTransactionFromBytes(it.Value())
O
obscuren 已提交
258 259

		gui.insertTransaction("post", tx)
O
obscuren 已提交
260

O
obscuren 已提交
261 262 263 264
	}
	it.Release()
}

265
func (gui *Gui) processBlock(block *types.Block, initial bool) {
T
Taylor Gerring 已提交
266
	name := block.Coinbase().Hex()
O
obscuren 已提交
267
	b := xeth.NewBlock(block)
268 269
	b.Name = name

270
	gui.getObjectByName("chainView").Call("addBlock", b, initial)
O
obscuren 已提交
271 272
}

273 274 275 276
func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) {
	var str string
	if unconfirmedFunds != nil {
		pos := "+"
O
obscuren 已提交
277
		if unconfirmedFunds.Cmp(big.NewInt(0)) < 0 {
278 279
			pos = "-"
		}
O
obscuren 已提交
280 281
		val := common.CurrencyToString(new(big.Int).Abs(common.BigCopy(unconfirmedFunds)))
		str = fmt.Sprintf("%v (%s %v)", common.CurrencyToString(amount), pos, val)
282
	} else {
O
obscuren 已提交
283
		str = fmt.Sprintf("%v", common.CurrencyToString(amount))
284 285 286 287 288
	}

	gui.win.Root().Call("setWalletValue", str)
}

O
obscuren 已提交
289 290 291 292
func (self *Gui) getObjectByName(objectName string) qml.Object {
	return self.win.Root().ObjectByName(objectName)
}

293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
func (gui *Gui) SendCommand(cmd ServEv) {
	gui.serviceEvents <- cmd
}

func (gui *Gui) service() {
	for ev := range gui.serviceEvents {
		switch ev {
		case setup:
			go gui.setup()
		case update:
			go gui.update()
		}
	}
}

func (gui *Gui) setup() {
	for gui.win == nil {
		time.Sleep(time.Millisecond * 200)
	}

	for _, plugin := range gui.plugins {
		guilogger.Infoln("Loading plugin ", plugin.Name)
		gui.win.Root().Call("addPlugin", plugin.Path, "")
316 317 318
	}

	go func() {
O
obscuren 已提交
319
		go gui.setInitialChain(false)
320
		gui.loadAddressBook()
321
		gui.loadMergedMiningOptions()
322 323
		gui.setPeerInfo()
	}()
324

325
	gui.whisper.SetView(gui.getObjectByName("whisperView"))
O
obscuren 已提交
326

327 328
	gui.SendCommand(update)
}
O
obscuren 已提交
329

330 331
// Simple go routine function that updates the list of peers in the GUI
func (gui *Gui) update() {
O
obscuren 已提交
332
	peerUpdateTicker := time.NewTicker(5 * time.Second)
O
obscuren 已提交
333
	generalUpdateTicker := time.NewTicker(500 * time.Millisecond)
O
obscuren 已提交
334
	statsUpdateTicker := time.NewTicker(5 * time.Second)
M
Maran 已提交
335

O
obscuren 已提交
336
	lastBlockLabel := gui.getObjectByName("lastBlockLabel")
O
obscuren 已提交
337
	//miningLabel := gui.getObjectByName("miningLabel")
O
obscuren 已提交
338

F
Felix Lange 已提交
339
	events := gui.eth.EventMux().Subscribe(
340
		core.ChainEvent{},
O
obscuren 已提交
341 342
		core.TxPreEvent{},
		core.TxPostEvent{},
F
Felix Lange 已提交
343 344
	)

345 346 347 348 349 350 351 352
	defer events.Unsubscribe()
	for {
		select {
		case ev, isopen := <-events.Chan():
			if !isopen {
				return
			}
			switch ev := ev.(type) {
353 354
			case core.ChainEvent:
				gui.processBlock(ev.Block, false)
355
			case core.TxPreEvent:
356
				gui.insertTransaction("pre", ev.Tx)
O
obscuren 已提交
357

358
			case core.TxPostEvent:
359
				gui.getObjectByName("pendingTxView").Call("removeTx", xeth.NewTx(ev.Tx))
O
obscuren 已提交
360
			}
361 362 363

		case <-peerUpdateTicker.C:
			gui.setPeerInfo()
F
Felix Lange 已提交
364

365 366 367
		case <-generalUpdateTicker.C:
			statusText := "#" + gui.eth.ChainManager().CurrentBlock().Number().String()
			lastBlockLabel.Set("text", statusText)
368
			//miningLabel.Set("text", strconv.FormatInt(gui.uiLib.Miner().HashRate(), 10))
369 370
		case <-statsUpdateTicker.C:
			gui.setStatsPane()
O
obscuren 已提交
371
		}
372
	}
O
obscuren 已提交
373 374
}

O
obscuren 已提交
375 376 377 378 379
func (gui *Gui) setStatsPane() {
	var memStats runtime.MemStats
	runtime.ReadMemStats(&memStats)

	statsPane := gui.getObjectByName("statsPane")
O
obscuren 已提交
380
	statsPane.Set("text", fmt.Sprintf(`###### Mist %s (%s) #######
O
obscuren 已提交
381 382

eth %d (p2p = %d)
O
obscuren 已提交
383 384 385 386 387 388 389 390 391 392

CPU:        # %d
Goroutines: # %d
CGoCalls:   # %d

Alloc:      %d
Heap Alloc: %d

CGNext:     %x
NumGC:      %d
O
obscuren 已提交
393
`, Version, runtime.Version(),
394
		eth.ProtocolVersion, 2,
O
obscuren 已提交
395
		runtime.NumCPU, runtime.NumGoroutine(), runtime.NumCgoCall(),
O
obscuren 已提交
396 397 398 399 400
		memStats.Alloc, memStats.HeapAlloc,
		memStats.NextGC, memStats.NumGC,
	))
}

401
type qmlpeer struct{ Addr, NodeID, Name, Caps string }
F
Felix Lange 已提交
402 403 404 405 406 407 408

type peersByID []*qmlpeer

func (s peersByID) Len() int           { return len(s) }
func (s peersByID) Less(i, j int) bool { return s[i].NodeID < s[j].NodeID }
func (s peersByID) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

O
obscuren 已提交
409
func (gui *Gui) setPeerInfo() {
F
Felix Lange 已提交
410 411 412 413 414 415
	peers := gui.eth.Peers()
	qpeers := make(peersByID, len(peers))
	for i, p := range peers {
		qpeers[i] = &qmlpeer{
			NodeID: p.ID().String(),
			Addr:   p.RemoteAddr().String(),
416
			Name:   p.Name(),
F
Felix Lange 已提交
417 418 419 420 421 422 423 424 425 426 427 428 429
			Caps:   fmt.Sprint(p.Caps()),
		}
	}
	// we need to sort the peers because they jump around randomly
	// otherwise. order returned by eth.Peers is random because they
	// are taken from a map.
	sort.Sort(qpeers)

	gui.win.Root().Call("setPeerCounters", fmt.Sprintf("%d / %d", len(peers), gui.eth.MaxPeers()))
	gui.win.Root().Call("clearPeers")
	for _, p := range qpeers {
		gui.win.Root().Call("addPeer", p)
	}
O
obscuren 已提交
430 431
}

O
obscuren 已提交
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
/*
func LoadExtension(path string) (uintptr, error) {
	lib, err := ffi.NewLibrary(path)
	if err != nil {
		return 0, err
	}

	so, err := lib.Fct("sharedObject", ffi.Pointer, nil)
	if err != nil {
		return 0, err
	}

	ptr := so()

		err = lib.Close()
		if err != nil {
			return 0, err
		}

	return ptr.Interface().(uintptr), nil
}
*/
/*
	vec, errr := LoadExtension("/Users/jeffrey/Desktop/build-libqmltest-Desktop_Qt_5_2_1_clang_64bit-Debug/liblibqmltest_debug.dylib")
	fmt.Printf("Fetched vec with addr: %#x\n", vec)
	if errr != nil {
		fmt.Println(errr)
	} else {
		context.SetVar("vec", (unsafe.Pointer)(vec))
	}
*/