github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/cmd/mist/gui.go (about)

     1  /*
     2  	This file is part of go-ethereum
     3  
     4  	go-ethereum is free software: you can redistribute it and/or modify
     5  	it under the terms of the GNU General Public License as published by
     6  	the Free Software Foundation, either version 3 of the License, or
     7  	(at your option) any later version.
     8  
     9  	go-ethereum is distributed in the hope that it will be useful,
    10  	but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  	GNU General Public License for more details.
    13  
    14  	You should have received a copy of the GNU General Public License
    15  	along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
    16  */
    17  /**
    18   * @authors
    19   * 	Jeffrey Wilcke <i@jev.io>
    20   */
    21  package main
    22  
    23  import "C"
    24  
    25  import (
    26  	"bytes"
    27  	"encoding/json"
    28  	"fmt"
    29  	"io/ioutil"
    30  	"math/big"
    31  	"os"
    32  	"path"
    33  	"runtime"
    34  	"sort"
    35  	"strconv"
    36  	"time"
    37  
    38  	"github.com/jonasnick/go-ethereum/core"
    39  	"github.com/jonasnick/go-ethereum/core/types"
    40  	"github.com/jonasnick/go-ethereum/eth"
    41  	"github.com/jonasnick/go-ethereum/ethdb"
    42  	"github.com/jonasnick/go-ethereum/ethutil"
    43  	"github.com/jonasnick/go-ethereum/logger"
    44  	"github.com/jonasnick/go-ethereum/miner"
    45  	"github.com/jonasnick/go-ethereum/ui/qt/qwhisper"
    46  	"github.com/jonasnick/go-ethereum/xeth"
    47  	"github.com/obscuren/qml"
    48  )
    49  
    50  var guilogger = logger.NewLogger("GUI")
    51  
    52  type ServEv byte
    53  
    54  const (
    55  	setup ServEv = iota
    56  	update
    57  )
    58  
    59  type Gui struct {
    60  	// The main application window
    61  	win *qml.Window
    62  	// QML Engine
    63  	engine    *qml.Engine
    64  	component *qml.Common
    65  	// The ethereum interface
    66  	eth           *eth.Ethereum
    67  	serviceEvents chan ServEv
    68  
    69  	// The public Ethereum library
    70  	uiLib   *UiLib
    71  	whisper *qwhisper.Whisper
    72  
    73  	txDb *ethdb.LDBDatabase
    74  
    75  	logLevel logger.LogLevel
    76  	open     bool
    77  
    78  	xeth *xeth.XEth
    79  
    80  	Session string
    81  	config  *ethutil.ConfigManager
    82  
    83  	plugins map[string]plugin
    84  
    85  	miner *miner.Miner
    86  }
    87  
    88  // Create GUI, but doesn't start it
    89  func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session string, logLevel int) *Gui {
    90  	db, err := ethdb.NewLDBDatabase("tx_database")
    91  	if err != nil {
    92  		panic(err)
    93  	}
    94  
    95  	xeth := xeth.New(ethereum)
    96  	gui := &Gui{eth: ethereum,
    97  		txDb:          db,
    98  		xeth:          xeth,
    99  		logLevel:      logger.LogLevel(logLevel),
   100  		Session:       session,
   101  		open:          false,
   102  		config:        config,
   103  		plugins:       make(map[string]plugin),
   104  		serviceEvents: make(chan ServEv, 1),
   105  	}
   106  	data, _ := ethutil.ReadAllFile(path.Join(ethutil.Config.ExecPath, "plugins.json"))
   107  	json.Unmarshal([]byte(data), &gui.plugins)
   108  
   109  	return gui
   110  }
   111  
   112  func (gui *Gui) Start(assetPath string) {
   113  	defer gui.txDb.Close()
   114  
   115  	guilogger.Infoln("Starting GUI")
   116  
   117  	go gui.service()
   118  
   119  	// Register ethereum functions
   120  	qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{
   121  		Init: func(p *xeth.Block, obj qml.Object) { p.Number = 0; p.Hash = "" },
   122  	}, {
   123  		Init: func(p *xeth.Transaction, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" },
   124  	}, {
   125  		Init: func(p *xeth.KeyVal, obj qml.Object) { p.Key = ""; p.Value = "" },
   126  	}})
   127  	// Create a new QML engine
   128  	gui.engine = qml.NewEngine()
   129  	context := gui.engine.Context()
   130  	gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath)
   131  	gui.whisper = qwhisper.New(gui.eth.Whisper())
   132  
   133  	// Expose the eth library and the ui library to QML
   134  	context.SetVar("gui", gui)
   135  	context.SetVar("eth", gui.uiLib)
   136  	context.SetVar("shh", gui.whisper)
   137  
   138  	win, err := gui.showWallet(context)
   139  	if err != nil {
   140  		guilogger.Errorln("asset not found: you can set an alternative asset path on the command line using option 'asset_path'", err)
   141  
   142  		panic(err)
   143  	}
   144  
   145  	gui.open = true
   146  	win.Show()
   147  
   148  	// only add the gui guilogger after window is shown otherwise slider wont be shown
   149  	logger.AddLogSystem(gui)
   150  	win.Wait()
   151  
   152  	// need to silence gui guilogger after window closed otherwise logsystem hangs (but do not save loglevel)
   153  	gui.logLevel = logger.Silence
   154  	gui.open = false
   155  }
   156  
   157  func (gui *Gui) Stop() {
   158  	if gui.open {
   159  		gui.logLevel = logger.Silence
   160  		gui.open = false
   161  		gui.win.Hide()
   162  	}
   163  
   164  	gui.uiLib.jsEngine.Stop()
   165  
   166  	guilogger.Infoln("Stopped")
   167  }
   168  
   169  func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
   170  	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/main.qml"))
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	gui.createWindow(component)
   176  
   177  	return gui.win, nil
   178  }
   179  
   180  func (gui *Gui) ImportKey(filePath string) {
   181  }
   182  
   183  func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
   184  	context.SetVar("lib", gui)
   185  	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml"))
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	return gui.createWindow(component), nil
   190  }
   191  
   192  func (gui *Gui) createWindow(comp qml.Object) *qml.Window {
   193  	gui.win = comp.CreateWindow(nil)
   194  	gui.uiLib.win = gui.win
   195  
   196  	return gui.win
   197  }
   198  
   199  func (gui *Gui) ImportAndSetPrivKey(secret string) bool {
   200  	err := gui.eth.KeyManager().InitFromString(gui.Session, 0, secret)
   201  	if err != nil {
   202  		guilogger.Errorln("unable to import: ", err)
   203  		return false
   204  	}
   205  	guilogger.Errorln("successfully imported: ", err)
   206  	return true
   207  }
   208  
   209  func (gui *Gui) CreateAndSetPrivKey() (string, string, string, string) {
   210  	err := gui.eth.KeyManager().Init(gui.Session, 0, true)
   211  	if err != nil {
   212  		guilogger.Errorln("unable to create key: ", err)
   213  		return "", "", "", ""
   214  	}
   215  	return gui.eth.KeyManager().KeyPair().AsStrings()
   216  }
   217  
   218  func (gui *Gui) setInitialChain(ancientBlocks bool) {
   219  	sBlk := gui.eth.ChainManager().LastBlockHash()
   220  	blk := gui.eth.ChainManager().GetBlock(sBlk)
   221  	for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) {
   222  		sBlk = blk.ParentHash()
   223  
   224  		gui.processBlock(blk, true)
   225  	}
   226  }
   227  
   228  func (gui *Gui) loadAddressBook() {
   229  	/*
   230  		view := gui.getObjectByName("infoView")
   231  		nameReg := gui.xeth.World().Config().Get("NameReg")
   232  		if nameReg != nil {
   233  			it := nameReg.Trie().Iterator()
   234  			for it.Next() {
   235  				if it.Key[0] != 0 {
   236  					view.Call("addAddress", struct{ Name, Address string }{string(it.Key), ethutil.Bytes2Hex(it.Value)})
   237  				}
   238  
   239  			}
   240  		}
   241  	*/
   242  }
   243  
   244  func (self *Gui) loadMergedMiningOptions() {
   245  	/*
   246  		view := self.getObjectByName("mergedMiningModel")
   247  
   248  		mergeMining := self.xeth.World().Config().Get("MergeMining")
   249  		if mergeMining != nil {
   250  			i := 0
   251  			it := mergeMining.Trie().Iterator()
   252  			for it.Next() {
   253  				view.Call("addMergedMiningOption", struct {
   254  					Checked       bool
   255  					Name, Address string
   256  					Id, ItemId    int
   257  				}{false, string(it.Key), ethutil.Bytes2Hex(it.Value), 0, i})
   258  
   259  				i++
   260  
   261  			}
   262  		}
   263  	*/
   264  }
   265  
   266  func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
   267  	addr := gui.address()
   268  
   269  	var inout string
   270  	if bytes.Compare(tx.From(), addr) == 0 {
   271  		inout = "send"
   272  	} else {
   273  		inout = "recv"
   274  	}
   275  
   276  	var (
   277  		ptx  = xeth.NewTx(tx)
   278  		send = ethutil.Bytes2Hex(tx.From())
   279  		rec  = ethutil.Bytes2Hex(tx.To())
   280  	)
   281  	ptx.Sender = send
   282  	ptx.Address = rec
   283  
   284  	if window == "post" {
   285  		//gui.getObjectByName("transactionView").Call("addTx", ptx, inout)
   286  	} else {
   287  		gui.getObjectByName("pendingTxView").Call("addTx", ptx, inout)
   288  	}
   289  }
   290  
   291  func (gui *Gui) readPreviousTransactions() {
   292  	it := gui.txDb.NewIterator()
   293  	for it.Next() {
   294  		tx := types.NewTransactionFromBytes(it.Value())
   295  
   296  		gui.insertTransaction("post", tx)
   297  
   298  	}
   299  	it.Release()
   300  }
   301  
   302  func (gui *Gui) processBlock(block *types.Block, initial bool) {
   303  	name := ethutil.Bytes2Hex(block.Coinbase())
   304  	b := xeth.NewBlock(block)
   305  	b.Name = name
   306  
   307  	gui.getObjectByName("chainView").Call("addBlock", b, initial)
   308  }
   309  
   310  func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) {
   311  	var str string
   312  	if unconfirmedFunds != nil {
   313  		pos := "+"
   314  		if unconfirmedFunds.Cmp(big.NewInt(0)) < 0 {
   315  			pos = "-"
   316  		}
   317  		val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds)))
   318  		str = fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(amount), pos, val)
   319  	} else {
   320  		str = fmt.Sprintf("%v", ethutil.CurrencyToString(amount))
   321  	}
   322  
   323  	gui.win.Root().Call("setWalletValue", str)
   324  }
   325  
   326  func (self *Gui) getObjectByName(objectName string) qml.Object {
   327  	return self.win.Root().ObjectByName(objectName)
   328  }
   329  
   330  func loadJavascriptAssets(gui *Gui) (jsfiles string) {
   331  	for _, fn := range []string{"ext/q.js", "ext/eth.js/main.js", "ext/eth.js/qt.js", "ext/setup.js"} {
   332  		f, err := os.Open(gui.uiLib.AssetPath(fn))
   333  		if err != nil {
   334  			fmt.Println(err)
   335  			continue
   336  		}
   337  
   338  		content, err := ioutil.ReadAll(f)
   339  		if err != nil {
   340  			fmt.Println(err)
   341  			continue
   342  		}
   343  		jsfiles += string(content)
   344  	}
   345  
   346  	return
   347  }
   348  
   349  func (gui *Gui) SendCommand(cmd ServEv) {
   350  	gui.serviceEvents <- cmd
   351  }
   352  
   353  func (gui *Gui) service() {
   354  	for ev := range gui.serviceEvents {
   355  		switch ev {
   356  		case setup:
   357  			go gui.setup()
   358  		case update:
   359  			go gui.update()
   360  		}
   361  	}
   362  }
   363  
   364  func (gui *Gui) setup() {
   365  	for gui.win == nil {
   366  		time.Sleep(time.Millisecond * 200)
   367  	}
   368  
   369  	for _, plugin := range gui.plugins {
   370  		guilogger.Infoln("Loading plugin ", plugin.Name)
   371  		gui.win.Root().Call("addPlugin", plugin.Path, "")
   372  	}
   373  
   374  	go func() {
   375  		go gui.setInitialChain(false)
   376  		gui.loadAddressBook()
   377  		gui.loadMergedMiningOptions()
   378  		gui.setPeerInfo()
   379  	}()
   380  
   381  	gui.whisper.SetView(gui.getObjectByName("whisperView"))
   382  
   383  	gui.SendCommand(update)
   384  }
   385  
   386  // Simple go routine function that updates the list of peers in the GUI
   387  func (gui *Gui) update() {
   388  	peerUpdateTicker := time.NewTicker(5 * time.Second)
   389  	generalUpdateTicker := time.NewTicker(500 * time.Millisecond)
   390  	statsUpdateTicker := time.NewTicker(5 * time.Second)
   391  
   392  	state := gui.eth.ChainManager().TransState()
   393  
   394  	gui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(state.GetAccount(gui.address()).Balance())))
   395  
   396  	lastBlockLabel := gui.getObjectByName("lastBlockLabel")
   397  	miningLabel := gui.getObjectByName("miningLabel")
   398  
   399  	events := gui.eth.EventMux().Subscribe(
   400  		//eth.PeerListEvent{},
   401  		core.NewBlockEvent{},
   402  		core.TxPreEvent{},
   403  		core.TxPostEvent{},
   404  	)
   405  
   406  	defer events.Unsubscribe()
   407  	for {
   408  		select {
   409  		case ev, isopen := <-events.Chan():
   410  			if !isopen {
   411  				return
   412  			}
   413  			switch ev := ev.(type) {
   414  			case core.NewBlockEvent:
   415  				gui.processBlock(ev.Block, false)
   416  				//gui.setWalletValue(gui.eth.ChainManager().State().GetBalance(gui.address()), nil)
   417  				balance := ethutil.CurrencyToString(gui.eth.ChainManager().State().GetBalance(gui.address()))
   418  				gui.getObjectByName("balanceLabel").Set("text", fmt.Sprintf("%v", balance))
   419  
   420  			case core.TxPreEvent:
   421  				tx := ev.Tx
   422  
   423  				tstate := gui.eth.ChainManager().TransState()
   424  				cstate := gui.eth.ChainManager().State()
   425  
   426  				taccount := tstate.GetAccount(gui.address())
   427  				caccount := cstate.GetAccount(gui.address())
   428  				unconfirmedFunds := new(big.Int).Sub(taccount.Balance(), caccount.Balance())
   429  
   430  				gui.setWalletValue(taccount.Balance(), unconfirmedFunds)
   431  				gui.insertTransaction("pre", tx)
   432  
   433  			case core.TxPostEvent:
   434  				tx := ev.Tx
   435  				object := state.GetAccount(gui.address())
   436  
   437  				if bytes.Compare(tx.From(), gui.address()) == 0 {
   438  					object.SubAmount(tx.Value())
   439  
   440  					gui.txDb.Put(tx.Hash(), tx.RlpEncode())
   441  				} else if bytes.Compare(tx.To(), gui.address()) == 0 {
   442  					object.AddAmount(tx.Value())
   443  
   444  					gui.txDb.Put(tx.Hash(), tx.RlpEncode())
   445  				}
   446  
   447  				gui.setWalletValue(object.Balance(), nil)
   448  				state.UpdateStateObject(object)
   449  			}
   450  
   451  		case <-peerUpdateTicker.C:
   452  			gui.setPeerInfo()
   453  
   454  		case <-generalUpdateTicker.C:
   455  			statusText := "#" + gui.eth.ChainManager().CurrentBlock().Number().String()
   456  			lastBlockLabel.Set("text", statusText)
   457  			miningLabel.Set("text", "Mining @ "+strconv.FormatInt(gui.uiLib.miner.HashRate(), 10)+"/Khash")
   458  
   459  			/*
   460  				blockLength := gui.eth.BlockPool().BlocksProcessed
   461  				chainLength := gui.eth.BlockPool().ChainLength
   462  
   463  				var (
   464  					pct      float64 = 1.0 / float64(chainLength) * float64(blockLength)
   465  					dlWidget         = gui.win.Root().ObjectByName("downloadIndicator")
   466  					dlLabel          = gui.win.Root().ObjectByName("downloadLabel")
   467  				)
   468  				dlWidget.Set("value", pct)
   469  				dlLabel.Set("text", fmt.Sprintf("%d / %d", blockLength, chainLength))
   470  			*/
   471  
   472  		case <-statsUpdateTicker.C:
   473  			gui.setStatsPane()
   474  		}
   475  	}
   476  }
   477  
   478  func (gui *Gui) setStatsPane() {
   479  	var memStats runtime.MemStats
   480  	runtime.ReadMemStats(&memStats)
   481  
   482  	statsPane := gui.getObjectByName("statsPane")
   483  	statsPane.Set("text", fmt.Sprintf(`###### Mist %s (%s) #######
   484  
   485  eth %d (p2p = %d)
   486  
   487  CPU:        # %d
   488  Goroutines: # %d
   489  CGoCalls:   # %d
   490  
   491  Alloc:      %d
   492  Heap Alloc: %d
   493  
   494  CGNext:     %x
   495  NumGC:      %d
   496  `, Version, runtime.Version(),
   497  		eth.ProtocolVersion, 2,
   498  		runtime.NumCPU, runtime.NumGoroutine(), runtime.NumCgoCall(),
   499  		memStats.Alloc, memStats.HeapAlloc,
   500  		memStats.NextGC, memStats.NumGC,
   501  	))
   502  }
   503  
   504  type qmlpeer struct{ Addr, NodeID, Caps string }
   505  
   506  type peersByID []*qmlpeer
   507  
   508  func (s peersByID) Len() int           { return len(s) }
   509  func (s peersByID) Less(i, j int) bool { return s[i].NodeID < s[j].NodeID }
   510  func (s peersByID) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   511  
   512  func (gui *Gui) setPeerInfo() {
   513  	peers := gui.eth.Peers()
   514  	qpeers := make(peersByID, len(peers))
   515  	for i, p := range peers {
   516  		qpeers[i] = &qmlpeer{
   517  			NodeID: p.ID().String(),
   518  			Addr:   p.RemoteAddr().String(),
   519  			Caps:   fmt.Sprint(p.Caps()),
   520  		}
   521  	}
   522  	// we need to sort the peers because they jump around randomly
   523  	// otherwise. order returned by eth.Peers is random because they
   524  	// are taken from a map.
   525  	sort.Sort(qpeers)
   526  
   527  	gui.win.Root().Call("setPeerCounters", fmt.Sprintf("%d / %d", len(peers), gui.eth.MaxPeers()))
   528  	gui.win.Root().Call("clearPeers")
   529  	for _, p := range qpeers {
   530  		gui.win.Root().Call("addPeer", p)
   531  	}
   532  }
   533  
   534  func (gui *Gui) privateKey() string {
   535  	return ethutil.Bytes2Hex(gui.eth.KeyManager().PrivateKey())
   536  }
   537  
   538  func (gui *Gui) address() []byte {
   539  	return gui.eth.KeyManager().Address()
   540  }
   541  
   542  /*
   543  func LoadExtension(path string) (uintptr, error) {
   544  	lib, err := ffi.NewLibrary(path)
   545  	if err != nil {
   546  		return 0, err
   547  	}
   548  
   549  	so, err := lib.Fct("sharedObject", ffi.Pointer, nil)
   550  	if err != nil {
   551  		return 0, err
   552  	}
   553  
   554  	ptr := so()
   555  
   556  		err = lib.Close()
   557  		if err != nil {
   558  			return 0, err
   559  		}
   560  
   561  	return ptr.Interface().(uintptr), nil
   562  }
   563  */
   564  /*
   565  	vec, errr := LoadExtension("/Users/jeffrey/Desktop/build-libqmltest-Desktop_Qt_5_2_1_clang_64bit-Debug/liblibqmltest_debug.dylib")
   566  	fmt.Printf("Fetched vec with addr: %#x\n", vec)
   567  	if errr != nil {
   568  		fmt.Println(errr)
   569  	} else {
   570  		context.SetVar("vec", (unsafe.Pointer)(vec))
   571  	}
   572  */