gui.go 12.2 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
	"bytes"
O
obscuren 已提交
27
	"encoding/json"
O
obscuren 已提交
28
	"fmt"
29
	"io/ioutil"
O
obscuren 已提交
30
	"math/big"
31
	"os"
O
obscuren 已提交
32
	"path"
O
obscuren 已提交
33
	"runtime"
F
Felix Lange 已提交
34
	"sort"
O
obscuren 已提交
35 36
	"strconv"
	"time"
O
Removed  
obscuren 已提交
37

O
obscuren 已提交
38 39
	"github.com/ethereum/go-ethereum/core"
	"github.com/ethereum/go-ethereum/core/types"
40
	"github.com/ethereum/go-ethereum/eth"
41 42
	"github.com/ethereum/go-ethereum/ethdb"
	"github.com/ethereum/go-ethereum/ethutil"
O
obscuren 已提交
43
	"github.com/ethereum/go-ethereum/logger"
44
	"github.com/ethereum/go-ethereum/ui/qt/qwhisper"
O
obscuren 已提交
45
	"github.com/ethereum/go-ethereum/xeth"
O
obscuren 已提交
46
	"github.com/obscuren/qml"
O
obscuren 已提交
47 48
)

O
obscuren 已提交
49
var guilogger = logger.NewLogger("GUI")
50

51 52 53 54 55 56 57
type ServEv byte

const (
	setup ServEv = iota
	update
)

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

68
	// The public Ethereum library
69 70
	uiLib   *UiLib
	whisper *qwhisper.Whisper
O
obscuren 已提交
71 72 73

	txDb *ethdb.LDBDatabase

O
obscuren 已提交
74
	logLevel logger.LogLevel
Z
go fmt  
zelig 已提交
75
	open     bool
Z
zelig 已提交
76

O
obscuren 已提交
77
	xeth *xeth.XEth
O
obscuren 已提交
78

79 80
	Session string
	config  *ethutil.ConfigManager
M
Maran 已提交
81

O
obscuren 已提交
82
	plugins map[string]plugin
O
obscuren 已提交
83 84
}

85
// Create GUI, but doesn't start it
86
func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session string, logLevel int) *Gui {
O
obscuren 已提交
87 88 89 90
	db, err := ethdb.NewLDBDatabase("tx_database")
	if err != nil {
		panic(err)
	}
O
obscuren 已提交
91

O
obscuren 已提交
92
	xeth := xeth.New(ethereum)
93
	gui := &Gui{eth: ethereum,
94 95 96 97 98 99 100 101
		txDb:          db,
		xeth:          xeth,
		logLevel:      logger.LogLevel(logLevel),
		Session:       session,
		open:          false,
		config:        config,
		plugins:       make(map[string]plugin),
		serviceEvents: make(chan ServEv, 1),
102
	}
O
obscuren 已提交
103
	data, _ := ethutil.ReadAllFile(path.Join(ethutil.Config.ExecPath, "plugins.json"))
O
obscuren 已提交
104 105 106
	json.Unmarshal([]byte(data), &gui.plugins)

	return gui
O
obscuren 已提交
107 108
}

O
Cleanup  
obscuren 已提交
109 110
func (gui *Gui) Start(assetPath string) {
	defer gui.txDb.Close()
O
obscuren 已提交
111

O
obscuren 已提交
112 113
	guilogger.Infoln("Starting GUI")

114 115
	go gui.service()

116 117
	// Register ethereum functions
	qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{
O
obscuren 已提交
118
		Init: func(p *xeth.Block, obj qml.Object) { p.Number = 0; p.Hash = "" },
O
obscuren 已提交
119
	}, {
O
obscuren 已提交
120
		Init: func(p *xeth.Transaction, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" },
121
	}, {
O
obscuren 已提交
122
		Init: func(p *xeth.KeyVal, obj qml.Object) { p.Key = ""; p.Value = "" },
O
obscuren 已提交
123
	}})
124
	// Create a new QML engine
O
Cleanup  
obscuren 已提交
125 126
	gui.engine = qml.NewEngine()
	context := gui.engine.Context()
127
	gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath)
128
	gui.whisper = qwhisper.New(gui.eth.Whisper())
129 130

	// Expose the eth library and the ui library to QML
131 132
	context.SetVar("gui", gui)
	context.SetVar("eth", gui.uiLib)
O
obscuren 已提交
133
	context.SetVar("shh", gui.whisper)
O
obscuren 已提交
134
	//clipboard.SetQMLClipboard(context)
J
Jarrad Hope 已提交
135

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

O
obscuren 已提交
140 141 142
		panic(err)
	}

Z
zelig 已提交
143
	gui.open = true
144
	win.Show()
O
obscuren 已提交
145

O
obscuren 已提交
146
	// only add the gui guilogger after window is shown otherwise slider wont be shown
O
obscuren 已提交
147
	logger.AddLogSystem(gui)
O
obscuren 已提交
148
	win.Wait()
O
obscuren 已提交
149

O
obscuren 已提交
150 151
	// need to silence gui guilogger after window closed otherwise logsystem hangs (but do not save loglevel)
	gui.logLevel = logger.Silence
Z
zelig 已提交
152 153 154 155 156
	gui.open = false
}

func (gui *Gui) Stop() {
	if gui.open {
O
obscuren 已提交
157
		gui.logLevel = logger.Silence
Z
zelig 已提交
158 159 160
		gui.open = false
		gui.win.Hide()
	}
161

162
	gui.uiLib.jsEngine.Stop()
163

O
obscuren 已提交
164
	guilogger.Infoln("Stopped")
O
obscuren 已提交
165
}
O
obscuren 已提交
166

O
obscuren 已提交
167
func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
O
obscuren 已提交
168
	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/main.qml"))
O
obscuren 已提交
169 170
	if err != nil {
		return nil, err
171
	}
O
obscuren 已提交
172

173
	gui.createWindow(component)
O
obscuren 已提交
174

175 176 177
	return gui.win, nil
}

O
obscuren 已提交
178 179 180
func (gui *Gui) ImportKey(filePath string) {
}

O
obscuren 已提交
181
func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
Z
zelig 已提交
182
	context.SetVar("lib", gui)
O
obscuren 已提交
183 184 185 186 187 188 189 190
	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 {
191 192
	gui.win = comp.CreateWindow(nil)
	gui.uiLib.win = gui.win
O
obscuren 已提交
193 194

	return gui.win
O
obscuren 已提交
195
}
Z
zelig 已提交
196 197 198 199

func (gui *Gui) ImportAndSetPrivKey(secret string) bool {
	err := gui.eth.KeyManager().InitFromString(gui.Session, 0, secret)
	if err != nil {
O
obscuren 已提交
200
		guilogger.Errorln("unable to import: ", err)
Z
zelig 已提交
201 202
		return false
	}
O
obscuren 已提交
203
	guilogger.Errorln("successfully imported: ", err)
Z
zelig 已提交
204 205 206 207 208 209
	return true
}

func (gui *Gui) CreateAndSetPrivKey() (string, string, string, string) {
	err := gui.eth.KeyManager().Init(gui.Session, 0, true)
	if err != nil {
O
obscuren 已提交
210
		guilogger.Errorln("unable to create key: ", err)
Z
zelig 已提交
211 212 213 214 215
		return "", "", "", ""
	}
	return gui.eth.KeyManager().KeyPair().AsStrings()
}

O
obscuren 已提交
216
func (gui *Gui) setInitialChain(ancientBlocks bool) {
O
obscuren 已提交
217
	sBlk := gui.eth.ChainManager().LastBlockHash()
O
obscuren 已提交
218 219
	blk := gui.eth.ChainManager().GetBlock(sBlk)
	for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) {
O
obscuren 已提交
220
		sBlk = blk.ParentHash()
221

M
Maran 已提交
222
		gui.processBlock(blk, true)
223 224 225
	}
}

O
obscuren 已提交
226
func (gui *Gui) loadAddressBook() {
O
obscuren 已提交
227 228 229 230 231 232 233 234 235
	/*
		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 {
					view.Call("addAddress", struct{ Name, Address string }{string(it.Key), ethutil.Bytes2Hex(it.Value)})
				}
O
obscuren 已提交
236

O
obscuren 已提交
237
			}
O
obscuren 已提交
238
		}
O
obscuren 已提交
239
	*/
O
obscuren 已提交
240 241
}

242
func (self *Gui) loadMergedMiningOptions() {
O
obscuren 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
	/*
		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
				}{false, string(it.Key), ethutil.Bytes2Hex(it.Value), 0, i})

				i++
258

O
obscuren 已提交
259
			}
O
obscuren 已提交
260
		}
O
obscuren 已提交
261
	*/
262 263
}

264
func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
Z
zelig 已提交
265
	addr := gui.address()
O
obscuren 已提交
266

O
obscuren 已提交
267
	var inout string
268
	if bytes.Compare(tx.From(), addr) == 0 {
O
obscuren 已提交
269 270 271 272 273 274
		inout = "send"
	} else {
		inout = "recv"
	}

	var (
O
obscuren 已提交
275
		ptx  = xeth.NewTx(tx)
O
obscuren 已提交
276 277
		send = ethutil.Bytes2Hex(tx.From())
		rec  = ethutil.Bytes2Hex(tx.To())
O
obscuren 已提交
278
	)
O
obscuren 已提交
279 280
	ptx.Sender = send
	ptx.Address = rec
O
obscuren 已提交
281

282
	if window == "post" {
O
obscuren 已提交
283
		//gui.getObjectByName("transactionView").Call("addTx", ptx, inout)
284 285 286
	} else {
		gui.getObjectByName("pendingTxView").Call("addTx", ptx, inout)
	}
O
obscuren 已提交
287
}
O
obscuren 已提交
288

O
obscuren 已提交
289
func (gui *Gui) readPreviousTransactions() {
O
obscuren 已提交
290
	it := gui.txDb.NewIterator()
O
obscuren 已提交
291
	for it.Next() {
292
		tx := types.NewTransactionFromBytes(it.Value())
O
obscuren 已提交
293 294

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

O
obscuren 已提交
296 297 298 299
	}
	it.Release()
}

300
func (gui *Gui) processBlock(block *types.Block, initial bool) {
O
obscuren 已提交
301
	name := ethutil.Bytes2Hex(block.Coinbase())
O
obscuren 已提交
302
	b := xeth.NewBlock(block)
303 304
	b.Name = name

305
	gui.getObjectByName("chainView").Call("addBlock", b, initial)
O
obscuren 已提交
306 307
}

308 309 310 311
func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) {
	var str string
	if unconfirmedFunds != nil {
		pos := "+"
O
obscuren 已提交
312
		if unconfirmedFunds.Cmp(big.NewInt(0)) < 0 {
313 314 315 316 317 318 319 320 321 322 323
			pos = "-"
		}
		val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds)))
		str = fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(amount), pos, val)
	} else {
		str = fmt.Sprintf("%v", ethutil.CurrencyToString(amount))
	}

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

O
obscuren 已提交
324 325 326 327
func (self *Gui) getObjectByName(objectName string) qml.Object {
	return self.win.Root().ObjectByName(objectName)
}

328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
func loadJavascriptAssets(gui *Gui) (jsfiles string) {
	for _, fn := range []string{"ext/q.js", "ext/eth.js/main.js", "ext/eth.js/qt.js", "ext/setup.js"} {
		f, err := os.Open(gui.uiLib.AssetPath(fn))
		if err != nil {
			fmt.Println(err)
			continue
		}

		content, err := ioutil.ReadAll(f)
		if err != nil {
			fmt.Println(err)
			continue
		}
		jsfiles += string(content)
	}

	return
}

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, "")
370 371 372
	}

	go func() {
O
obscuren 已提交
373
		go gui.setInitialChain(false)
374
		gui.loadAddressBook()
375
		gui.loadMergedMiningOptions()
376 377
		gui.setPeerInfo()
	}()
378

379
	gui.whisper.SetView(gui.getObjectByName("whisperView"))
O
obscuren 已提交
380

381 382
	gui.SendCommand(update)
}
O
obscuren 已提交
383

384 385
// Simple go routine function that updates the list of peers in the GUI
func (gui *Gui) update() {
O
obscuren 已提交
386
	peerUpdateTicker := time.NewTicker(5 * time.Second)
O
obscuren 已提交
387
	generalUpdateTicker := time.NewTicker(500 * time.Millisecond)
O
obscuren 已提交
388
	statsUpdateTicker := time.NewTicker(5 * time.Second)
M
Maran 已提交
389

O
obscuren 已提交
390
	lastBlockLabel := gui.getObjectByName("lastBlockLabel")
O
obscuren 已提交
391
	miningLabel := gui.getObjectByName("miningLabel")
O
obscuren 已提交
392

F
Felix Lange 已提交
393
	events := gui.eth.EventMux().Subscribe(
394
		core.ChainEvent{},
O
obscuren 已提交
395 396
		core.TxPreEvent{},
		core.TxPostEvent{},
F
Felix Lange 已提交
397 398
	)

399 400 401 402 403 404 405 406
	defer events.Unsubscribe()
	for {
		select {
		case ev, isopen := <-events.Chan():
			if !isopen {
				return
			}
			switch ev := ev.(type) {
407 408
			case core.ChainEvent:
				gui.processBlock(ev.Block, false)
409
			case core.TxPreEvent:
410
				gui.insertTransaction("pre", ev.Tx)
O
obscuren 已提交
411

412
			case core.TxPostEvent:
413
				gui.getObjectByName("pendingTxView").Call("removeTx", xeth.NewTx(ev.Tx))
O
obscuren 已提交
414
			}
415 416 417

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

419 420 421
		case <-generalUpdateTicker.C:
			statusText := "#" + gui.eth.ChainManager().CurrentBlock().Number().String()
			lastBlockLabel.Set("text", statusText)
422
			//miningLabel.Set("text", strconv.FormatInt(gui.uiLib.Miner().HashRate(), 10))
423 424
		case <-statsUpdateTicker.C:
			gui.setStatsPane()
O
obscuren 已提交
425
		}
426
	}
O
obscuren 已提交
427 428
}

O
obscuren 已提交
429 430 431 432 433
func (gui *Gui) setStatsPane() {
	var memStats runtime.MemStats
	runtime.ReadMemStats(&memStats)

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

eth %d (p2p = %d)
O
obscuren 已提交
437 438 439 440 441 442 443 444 445 446

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

Alloc:      %d
Heap Alloc: %d

CGNext:     %x
NumGC:      %d
O
obscuren 已提交
447
`, Version, runtime.Version(),
448
		eth.ProtocolVersion, 2,
O
obscuren 已提交
449
		runtime.NumCPU, runtime.NumGoroutine(), runtime.NumCgoCall(),
O
obscuren 已提交
450 451 452 453 454
		memStats.Alloc, memStats.HeapAlloc,
		memStats.NextGC, memStats.NumGC,
	))
}

455
type qmlpeer struct{ Addr, NodeID, Name, Caps string }
F
Felix Lange 已提交
456 457 458 459 460 461 462

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 已提交
463
func (gui *Gui) setPeerInfo() {
F
Felix Lange 已提交
464 465 466 467 468 469
	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(),
470
			Name:   p.Name(),
F
Felix Lange 已提交
471 472 473 474 475 476 477 478 479 480 481 482 483
			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 已提交
484 485
}

Z
zelig 已提交
486 487 488 489 490 491 492
func (gui *Gui) privateKey() string {
	return ethutil.Bytes2Hex(gui.eth.KeyManager().PrivateKey())
}

func (gui *Gui) address() []byte {
	return gui.eth.KeyManager().Address()
}
O
obscuren 已提交
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524

/*
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))
	}
*/