diff options
Diffstat (limited to 'ethereal/ui')
-rw-r--r-- | ethereal/ui/ext_app.go | 126 | ||||
-rw-r--r-- | ethereal/ui/gui.go | 92 | ||||
-rw-r--r-- | ethereal/ui/html_container.go | 73 | ||||
-rw-r--r-- | ethereal/ui/library.go | 102 | ||||
-rw-r--r-- | ethereal/ui/ui_lib.go | 130 |
5 files changed, 457 insertions, 66 deletions
diff --git a/ethereal/ui/ext_app.go b/ethereal/ui/ext_app.go new file mode 100644 index 000000000..93db0ade1 --- /dev/null +++ b/ethereal/ui/ext_app.go @@ -0,0 +1,126 @@ +package ethui + +import ( + "fmt" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" +) + +type AppContainer interface { + Create() error + Destroy() + + Window() *qml.Window + Engine() *qml.Engine + + NewBlock(*ethchain.Block) + ObjectChanged(*ethchain.StateObject) + StorageChanged(*ethchain.StorageState) +} + +type ExtApplication struct { + *ethpub.PEthereum + + blockChan chan ethutil.React + changeChan chan ethutil.React + quitChan chan bool + + container AppContainer + lib *UiLib + registeredEvents []string +} + +func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication { + app := &ExtApplication{ + ethpub.NewPEthereum(lib.eth.StateManager(), lib.eth.BlockChain(), lib.eth.TxPool()), + make(chan ethutil.React, 1), + make(chan ethutil.React, 1), + make(chan bool), + container, + lib, + nil, + } + + return app +} + +func (app *ExtApplication) run() { + // Set the "eth" api on to the containers context + context := app.container.Engine().Context() + context.SetVar("eth", app) + context.SetVar("ui", app.lib) + + err := app.container.Create() + if err != nil { + fmt.Println(err) + + return + } + + // Call the main loop + go app.mainLoop() + + // Subscribe to events + reactor := app.lib.eth.Reactor() + reactor.Subscribe("newBlock", app.blockChan) + + win := app.container.Window() + win.Show() + win.Wait() + + app.stop() +} + +func (app *ExtApplication) stop() { + // Clean up + reactor := app.lib.eth.Reactor() + reactor.Unsubscribe("newBlock", app.blockChan) + for _, event := range app.registeredEvents { + reactor.Unsubscribe(event, app.changeChan) + } + + // Kill the main loop + app.quitChan <- true + + close(app.blockChan) + close(app.quitChan) + close(app.changeChan) + + app.container.Destroy() +} + +func (app *ExtApplication) mainLoop() { +out: + for { + select { + case <-app.quitChan: + break out + case block := <-app.blockChan: + if block, ok := block.Resource.(*ethchain.Block); ok { + app.container.NewBlock(block) + } + case object := <-app.changeChan: + if stateObject, ok := object.Resource.(*ethchain.StateObject); ok { + app.container.ObjectChanged(stateObject) + } else if storageObject, ok := object.Resource.(*ethchain.StorageState); ok { + app.container.StorageChanged(storageObject) + } + } + } + +} + +func (app *ExtApplication) Watch(addr, storageAddr string) { + var event string + if len(storageAddr) == 0 { + event = "object:" + string(ethutil.FromHex(addr)) + app.lib.eth.Reactor().Subscribe(event, app.changeChan) + } else { + event = "storage:" + string(ethutil.FromHex(addr)) + ":" + string(ethutil.FromHex(storageAddr)) + app.lib.eth.Reactor().Subscribe(event, app.changeChan) + } + + app.registeredEvents = append(app.registeredEvents, event) +} diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 89736ac29..522c081a3 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -2,42 +2,17 @@ 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/ethpub" "github.com/ethereum/eth-go/ethutil" - "github.com/niemeyer/qml" + "github.com/go-qml/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 @@ -53,7 +28,6 @@ type Gui struct { txDb *ethdb.LDBDatabase addr []byte - } // Create GUI, but doesn't start it @@ -64,10 +38,16 @@ func New(ethereum *eth.Ethereum) *Gui { panic(err) } - key := ethutil.Config.Db.GetKeys()[0] - addr := key.Address() + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + // On first run we won't have any keys yet, so this would crash. + // Therefor we check if we are ready to actually start this process + var addr []byte + if len(data) > 0 { + key := ethutil.Config.Db.GetKeys()[0] + addr = key.Address() - ethereum.StateManager().WatchAddr(addr) + ethereum.StateManager().WatchAddr(addr) + } return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} } @@ -77,12 +57,12 @@ func (ui *Gui) Start(assetPath string) { // 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 *ethpub.PBlock, obj qml.Object) { p.Number = 0; p.Hash = "" }, }, { - Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, + Init: func(p *ethpub.PTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, }}) - ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.1")) + ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.2")) ethutil.Config.Log.Infoln("[GUI] Starting GUI") // Create a new QML engine ui.engine = qml.NewEngine() @@ -94,14 +74,27 @@ func (ui *Gui) Start(assetPath string) { context.SetVar("ui", uiLib) // Load the main QML interface - component, err := ui.engine.LoadFile(uiLib.AssetPath("qml/wallet.qml")) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + var err error + var component qml.Object + firstRun := len(data) == 0 + + if firstRun { + component, err = ui.engine.LoadFile(uiLib.AssetPath("qml/first_run.qml")) + } else { + component, err = ui.engine.LoadFile(uiLib.AssetPath("qml/wallet.qml")) + } if err != nil { - ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") + ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") + panic(err) } - ui.engine.LoadFile(uiLib.AssetPath("qml/transactions.qml")) ui.win = component.CreateWindow(nil) + uiLib.win = ui.win + db := &Debugger{ui.win, make(chan bool)} + ui.lib.Db = db + uiLib.Db = db // Register the ui as a block processor //ui.eth.BlockManager.SecondaryBlockProcessor = ui @@ -111,9 +104,11 @@ func (ui *Gui) Start(assetPath string) { ethutil.Config.Log.AddLogSystem(ui) // Loads previous blocks - go ui.setInitialBlockChain() - go ui.readPreviousTransactions() - go ui.update() + if firstRun == false { + go ui.setInitialBlockChain() + go ui.readPreviousTransactions() + go ui.update() + } ui.win.Show() ui.win.Wait() @@ -135,13 +130,13 @@ func (ui *Gui) readPreviousTransactions() { for it.Next() { tx := ethchain.NewTransactionFromBytes(it.Value()) - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.win.Root().Call("addTx", ethpub.NewPTx(tx)) } it.Release() } func (ui *Gui) ProcessBlock(block *ethchain.Block) { - ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) + ui.win.Root().Call("addBlock", ethpub.NewPBlock(block)) } // Simple go routine function that updates the list of peers in the GUI @@ -149,23 +144,26 @@ func (ui *Gui) update() { txChan := make(chan ethchain.TxMsg, 1) ui.eth.TxPool().Subscribe(txChan) - account := ui.eth.StateManager().GetAddrState(ui.addr).Account + account := ui.eth.StateManager().GetAddrState(ui.addr).Object unconfirmedFunds := new(big.Int) ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) + + addrState := ui.eth.StateManager().GetAddrState(ui.addr) + 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)) + if bytes.Compare(tx.Sender(), ui.addr) == 0 && addrState.Nonce <= tx.Nonce { + ui.win.Root().Call("addTx", ethpub.NewPTx(tx)) ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - ui.eth.StateManager().GetAddrState(ui.addr).Nonce += 1 + addrState.Nonce += 1 unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.win.Root().Call("addTx", ethpub.NewPTx(tx)) ui.txDb.Put(tx.Hash(), tx.RlpEncode()) unconfirmedFunds.Add(unconfirmedFunds, tx.Value) diff --git a/ethereal/ui/html_container.go b/ethereal/ui/html_container.go new file mode 100644 index 000000000..4f12aaaf6 --- /dev/null +++ b/ethereal/ui/html_container.go @@ -0,0 +1,73 @@ +package ethui + +import ( + "errors" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" + "path/filepath" +) + +type HtmlApplication struct { + win *qml.Window + webView qml.Object + engine *qml.Engine + lib *UiLib + path string +} + +func NewHtmlApplication(path string, lib *UiLib) *HtmlApplication { + engine := qml.NewEngine() + + return &HtmlApplication{engine: engine, lib: lib, path: path} + +} + +func (app *HtmlApplication) Create() error { + component, err := app.engine.LoadFile(app.lib.AssetPath("qml/webapp.qml")) + if err != nil { + return err + } + + if filepath.Ext(app.path) == "eth" { + return errors.New("Ethereum package not yet supported") + + // TODO + ethutil.OpenPackage(app.path) + } + + win := component.CreateWindow(nil) + win.Set("url", app.path) + webView := win.ObjectByName("webView") + + app.win = win + app.webView = webView + + return nil +} + +func (app *HtmlApplication) Engine() *qml.Engine { + return app.engine +} + +func (app *HtmlApplication) Window() *qml.Window { + return app.win +} + +func (app *HtmlApplication) NewBlock(block *ethchain.Block) { + b := ðpub.PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} + app.webView.Call("onNewBlockCb", b) +} + +func (app *HtmlApplication) ObjectChanged(stateObject *ethchain.StateObject) { + app.webView.Call("onObjectChangeCb", ethpub.NewPStateObject(stateObject)) +} + +func (app *HtmlApplication) StorageChanged(storageObject *ethchain.StorageState) { + app.webView.Call("onStorageChangeCb", ethpub.NewPStorageState(storageObject)) +} + +func (app *HtmlApplication) Destroy() { + app.engine.Destroy() +} diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 05fffd579..1328cd6b7 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -4,7 +4,10 @@ import ( "encoding/hex" "fmt" "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethpub" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" + "github.com/obscuren/secp256k1-go" "strings" ) @@ -12,49 +15,114 @@ type EthLib struct { stateManager *ethchain.StateManager blockChain *ethchain.BlockChain txPool *ethchain.TxPool + Db *Debugger } -func (lib *EthLib) CreateTx(receiver, a, data string) string { +func (lib *EthLib) ImportAndSetPrivKey(privKey string) bool { + fmt.Println(privKey) + mnemonic := strings.Split(privKey, " ") + if len(mnemonic) == 24 { + fmt.Println("Got mnemonic key, importing.") + key := ethutil.MnemonicDecode(mnemonic) + utils.ImportPrivateKey(key) + } else if len(mnemonic) == 1 { + fmt.Println("Got hex key, importing.") + utils.ImportPrivateKey(privKey) + } else { + fmt.Println("Did not recognise format, exiting.") + return false + } + return true +} + +func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { + pub, prv := secp256k1.GenerateKeyPair() + pair := ðutil.Key{PrivateKey: prv, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) + mne := ethutil.MnemonicEncode(ethutil.Hex(prv)) + mnemonicString := strings.Join(mne, " ") + return mnemonicString, fmt.Sprintf("%x", pair.Address()), fmt.Sprintf("%x", prv), fmt.Sprintf("%x", pub) +} + +func (lib *EthLib) GetKey() string { + return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) +} + +func (lib *EthLib) GetStateObject(address string) *ethpub.PStateObject { + stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) + if stateObject != nil { + return ethpub.NewPStateObject(stateObject) + } + + // See GetStorage for explanation on "nil" + return ethpub.NewPStateObject(nil) +} + +func (lib *EthLib) Watch(addr, storageAddr string) { + // lib.stateManager.Watch(ethutil.FromHex(addr), ethutil.FromHex(storageAddr)) +} + +func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + return lib.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr) +} + +func (lib *EthLib) Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { var hash []byte - if len(receiver) == 0 { - hash = ethchain.ContractAddr + var contractCreation bool + if len(recipient) == 0 { + contractCreation = true } else { var err error - hash, err = hex.DecodeString(receiver) + hash, err = hex.DecodeString(recipient) if err != nil { - return err.Error() + return "", err } } - k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyPair := ethutil.NewKeyFromBytes(k) + keyPair := ethutil.Config.Db.GetKeys()[0] + value := ethutil.Big(valueStr) + gas := ethutil.Big(gasStr) + gasPrice := ethutil.Big(gasPriceStr) + var tx *ethchain.Transaction + // Compile and assemble the given data + if contractCreation { + // Compile script + mainScript, initScript, err := utils.CompileScript(dataStr) + if err != nil { + return "", err + } - amount := ethutil.Big(a) - code := ethchain.Compile(strings.Split(data, "\n")) - tx := ethchain.NewTransaction(hash, amount, code) - tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce + tx = ethchain.NewContractCreationTx(value, gas, gasPrice, mainScript, initScript) + } else { + lines := strings.Split(dataStr, "\n") + var data []byte + for _, line := range lines { + data = append(data, ethutil.BigToBytes(ethutil.Big(line), 256)...) + } + tx = ethchain.NewTransactionMessage(hash, value, gas, gasPrice, data) + } + acc := lib.stateManager.GetAddrState(keyPair.Address()) + tx.Nonce = acc.Nonce tx.Sign(keyPair.PrivateKey) - lib.txPool.QueueTransaction(tx) - if len(receiver) == 0 { + if contractCreation { 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()) + return ethutil.Hex(tx.Hash()), nil } -func (lib *EthLib) GetBlock(hexHash string) *Block { +func (lib *EthLib) GetBlock(hexHash string) *ethpub.PBlock { 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())} + return ðpub.PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} } diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 4441a7238..0feb522bc 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -2,21 +2,33 @@ package ethui import ( "bitbucket.org/kardianos/osext" + "fmt" "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" - "github.com/niemeyer/qml" + "github.com/ethereum/go-ethereum/utils" + "github.com/go-qml/qml" + "github.com/obscuren/mutan" "os" "path" "path/filepath" "runtime" ) +type memAddr struct { + Num string + Value string +} + // UI Library that has some basic functionality exposed type UiLib struct { engine *qml.Engine eth *eth.Ethereum connected bool assetPath string + // The main application window + win *qml.Window + Db *Debugger } func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { @@ -40,6 +52,40 @@ func (ui *UiLib) Open(path string) { }() } +func (ui *UiLib) OpenHtml(path string) { + container := NewHtmlApplication(path, ui) + app := NewExtApplication(container, ui) + + go app.run() +} + +func (ui *UiLib) Watch(addr, storageAddr string) { + if len(storageAddr) == 0 { + ui.eth.Reactor().Subscribe("storage:"+string(ethutil.FromHex(addr))+":"+string(ethutil.FromHex(storageAddr)), nil) + } else { + ui.eth.Reactor().Subscribe("object:"+string(ethutil.FromHex(addr)), nil) + } +} + +func (ui *UiLib) Muted(content string) { + component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml")) + if err != nil { + ethutil.Config.Log.Debugln(err) + + return + } + win := component.CreateWindow(nil) + go func() { + path := "file://" + ui.AssetPath("muted/index.html") + win.Set("url", path) + //debuggerPath := "file://" + ui.AssetPath("muted/debugger.html") + //win.Set("debugUrl", debuggerPath) + + win.Show() + win.Wait() + }() +} + func (ui *UiLib) Connect(button qml.Object) { if !ui.connected { ui.eth.Start() @@ -58,7 +104,6 @@ func (ui *UiLib) AssetPath(p string) string { func DefaultAssetPath() string { var base string - // If the current working directory is the go-ethereum dir // assume a debug build and use the source directory as // asset directory. @@ -82,3 +127,84 @@ func DefaultAssetPath() string { return base } + +func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) { + state := ui.eth.BlockChain().CurrentBlock.State() + + mainInput, _ := mutan.PreProcess(data) + callerScript, err := utils.Compile(mainInput) + if err != nil { + ethutil.Config.Log.Debugln(err) + + return + } + + dis := ethchain.Disassemble(callerScript) + ui.win.Root().Call("clearAsm") + + for _, str := range dis { + ui.win.Root().Call("setAsm", str) + } + callerTx := ethchain.NewContractCreationTx(ethutil.Big(valueStr), ethutil.Big(gasStr), ethutil.Big(gasPriceStr), callerScript, nil) + + // Contract addr as test address + keyPair := ethutil.Config.Db.GetKeys()[0] + account := ui.eth.StateManager().GetAddrState(keyPair.Address()).Object + c := ethchain.MakeContract(callerTx, state) + callerClosure := ethchain.NewClosure(account, c, c.Script(), state, ethutil.Big(gasStr), ethutil.Big(gasPriceStr), ethutil.Big(valueStr)) + + block := ui.eth.BlockChain().CurrentBlock + vm := ethchain.NewVm(state, ui.eth.StateManager(), ethchain.RuntimeVars{ + Origin: account.Address(), + BlockNumber: block.BlockInfo().Number, + PrevHash: block.PrevHash, + Coinbase: block.Coinbase, + Time: block.Time, + Diff: block.Difficulty, + TxData: nil, + }) + + go func() { + callerClosure.Call(vm, nil, ui.Db.halting) + + state.Reset() + }() +} + +func (ui *UiLib) Next() { + ui.Db.Next() +} + +type Debugger struct { + win *qml.Window + N chan bool +} + +func (d *Debugger) halting(pc int, op ethchain.OpCode, mem *ethchain.Memory, stack *ethchain.Stack) { + d.win.Root().Call("setInstruction", pc) + d.win.Root().Call("clearMem") + d.win.Root().Call("clearStack") + + addr := 0 + for i := 0; i+32 <= mem.Len(); i += 32 { + d.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("% x", mem.Data()[i:i+32])}) + addr++ + } + + for _, val := range stack.Data() { + d.win.Root().Call("setStack", val.String()) + } + +out: + for { + select { + case <-d.N: + break out + default: + } + } +} + +func (d *Debugger) Next() { + d.N <- true +} |