Merge branch 'develop' into jsonrpc

pull/263/head
Taylor Gerring 10 years ago
commit d92fde6980
  1. 5
      .gitignore
  2. 12
      .mailmap
  3. 35
      cmd/ethereum/cmd.go
  4. 37
      cmd/ethereum/flags.go
  5. 56
      cmd/ethereum/main.go
  6. 5
      cmd/ethereum/repl/repl.go
  7. 29
      cmd/ethtest/main.go
  8. 11
      cmd/evm/main.go
  9. 3
      cmd/mist/assets/debugger/debugger.qml
  10. 18
      cmd/mist/assets/ext/eth.js/README.md
  11. 70
      cmd/mist/assets/ext/eth.js/httprpc.js
  12. 33
      cmd/mist/assets/ext/eth.js/index.html
  13. 432
      cmd/mist/assets/ext/eth.js/main.js
  14. 27
      cmd/mist/assets/ext/eth.js/qt.js
  15. 51
      cmd/mist/assets/ext/eth.js/websocket.js
  16. 312
      cmd/mist/assets/ext/ethereum.js
  17. 5
      cmd/mist/assets/ext/ethereum.js/.bowerrc
  18. 12
      cmd/mist/assets/ext/ethereum.js/.editorconfig
  19. 6
      cmd/mist/assets/ext/ethereum.js/.gitignore
  20. 50
      cmd/mist/assets/ext/ethereum.js/.jshintrc
  21. 9
      cmd/mist/assets/ext/ethereum.js/.npmignore
  22. 11
      cmd/mist/assets/ext/ethereum.js/.travis.yml
  23. 14
      cmd/mist/assets/ext/ethereum.js/LICENSE
  24. 79
      cmd/mist/assets/ext/ethereum.js/README.md
  25. 51
      cmd/mist/assets/ext/ethereum.js/bower.json
  26. 1184
      cmd/mist/assets/ext/ethereum.js/dist/ethereum.js
  27. 29
      cmd/mist/assets/ext/ethereum.js/dist/ethereum.js.map
  28. 1
      cmd/mist/assets/ext/ethereum.js/dist/ethereum.min.js
  29. 41
      cmd/mist/assets/ext/ethereum.js/example/balance.html
  30. 75
      cmd/mist/assets/ext/ethereum.js/example/contract.html
  31. 16
      cmd/mist/assets/ext/ethereum.js/example/node-app.js
  32. 104
      cmd/mist/assets/ext/ethereum.js/gulpfile.js
  33. 8
      cmd/mist/assets/ext/ethereum.js/index.js
  34. 267
      cmd/mist/assets/ext/ethereum.js/lib/abi.js
  35. 103
      cmd/mist/assets/ext/ethereum.js/lib/autoprovider.js
  36. 66
      cmd/mist/assets/ext/ethereum.js/lib/contract.js
  37. 95
      cmd/mist/assets/ext/ethereum.js/lib/httprpc.js
  38. 46
      cmd/mist/assets/ext/ethereum.js/lib/qt.js
  39. 509
      cmd/mist/assets/ext/ethereum.js/lib/web3.js
  40. 78
      cmd/mist/assets/ext/ethereum.js/lib/websocket.js
  41. 67
      cmd/mist/assets/ext/ethereum.js/package.json
  42. 755
      cmd/mist/assets/qml/browser.qml
  43. 47
      cmd/mist/assets/qml/main.qml
  44. 19
      cmd/mist/assets/qml/views/miner.qml
  45. 40
      cmd/mist/bindings.go
  46. 44
      cmd/mist/debugger.go
  47. 35
      cmd/mist/errors.go
  48. 37
      cmd/mist/ext_app.go
  49. 37
      cmd/mist/flags.go
  50. 274
      cmd/mist/gui.go
  51. 37
      cmd/mist/html_container.go
  52. 37
      cmd/mist/main.go
  53. 36
      cmd/mist/qml_container.go
  54. 48
      cmd/mist/ui_lib.go
  55. 50
      cmd/peerserver/main.go
  56. 7
      cmd/rlpdump/main.go
  57. 25
      cmd/utils/cmd.go
  58. 20
      cmd/utils/vm_env.go
  59. 138
      cmd/utils/websockets.go
  60. 104
      core/block_processor.go
  61. 84
      core/chain_manager.go
  62. 46
      core/chain_manager_test.go
  63. 28
      core/execution.go
  64. 6
      core/genesis.go
  65. 1
      core/helper_test.go
  66. 3
      core/state_transition.go
  67. 10
      core/transaction_pool.go
  68. 20
      core/transaction_pool_test.go
  69. 22
      core/types/block.go
  70. 6
      core/types/derive_sha.go
  71. 26
      crypto/crypto_test.go
  72. 107
      crypto/key.go
  73. 245
      crypto/key_store_passphrase.go
  74. 114
      crypto/key_store_plain.go
  75. 85
      crypto/key_store_test.go
  76. 22
      eth/backend.go
  77. 467
      eth/block_pool.go
  78. 192
      eth/block_pool_test.go
  79. 2
      eth/error.go
  80. 27
      eth/protocol.go
  81. 4
      eth/protocol_test.go
  82. 6
      eth/test/tests/02.sh
  83. 4
      eth/test/tests/03.sh
  84. 6
      eth/test/tests/common.sh
  85. 1
      ethutil/common.go
  86. 2
      ethutil/config.go
  87. 2
      ethutil/path.go
  88. 2
      ethutil/rlp.go
  89. 8
      event/filter/filter.go
  90. 9
      javascript/javascript_runtime.go
  91. 8
      miner/miner.go
  92. 20
      p2p/message.go
  93. 6
      p2p/message_test.go
  94. 22
      p2p/peer.go
  95. 4
      p2p/peer_test.go
  96. 34
      p2p/protocol.go
  97. 68
      p2p/protocol_test.go
  98. 4
      p2p/server.go
  99. 12
      pow/ar/block.go
  100. 54
      pow/ar/ops.go
  101. Some files were not shown because too many files have changed in this diff Show More

5
.gitignore vendored

@ -10,3 +10,8 @@
.DS_Store .DS_Store
*/**/.DS_Store */**/.DS_Store
.ethtest .ethtest
#*
.#*
*#
*~

@ -0,0 +1,12 @@
Jeffrey Wilcke <jeffrey@ethereum.org>
Jeffrey Wilcke <jeffrey@ethereum.org> <geffobscura@gmail.com>
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@obscura.com>
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@users.noreply.github.com>
Viktor Trón <viktor.tron@gmail.com>
Joseph Goulden <joegoulden@gmail.com>
Nick Savers <nicksavers@gmail.com>
Maran Hidskes <maran.hidskes@gmail.com>

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library 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 2.1 of the License, or (at your option) any later version.
//
// This library 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 this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA
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>
*/
package main package main
import ( import (

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library 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 2.1 of the License, or (at your option) any later version.
//
// This library 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 this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA
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>
*/
package main package main
import ( import (
@ -61,6 +64,7 @@ var (
ImportChain string ImportChain string
SHH bool SHH bool
Dial bool Dial bool
PrintVersion bool
) )
// flags specific to cli client // flags specific to cli client
@ -117,6 +121,7 @@ func Init() {
flag.BoolVar(&StartMining, "mine", false, "start dagger mining") flag.BoolVar(&StartMining, "mine", false, "start dagger mining")
flag.BoolVar(&StartJsConsole, "js", false, "launches javascript console") flag.BoolVar(&StartJsConsole, "js", false, "launches javascript console")
flag.BoolVar(&PrintVersion, "version", false, "prints version number")
flag.Parse() flag.Parse()

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of go-ethereum is distributed in the hope that it will be useful,
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU but WITHOUT ANY WARRANTY; without even the implied warranty of
// General Public License for more details. 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 this library; if not, write to the Free Software You should have received a copy of the GNU General Public License
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// MA 02110-1301 USA */
/**
* @authors
* Jeffrey Wilcke <i@jev.io>
*/
package main package main
import ( import (
@ -28,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state"
) )
const ( const (
@ -49,6 +53,11 @@ func main() {
// precedence: code-internal flag default < config file < environment variables < command line // precedence: code-internal flag default < config file < environment variables < command line
Init() // parsing command line Init() // parsing command line
if PrintVersion {
printVersion()
return
}
utils.InitConfig(VmType, ConfigFile, Datadir, "ETH") utils.InitConfig(VmType, ConfigFile, Datadir, "ETH")
ethereum, err := eth.New(&eth.Config{ ethereum, err := eth.New(&eth.Config{
@ -95,7 +104,8 @@ func main() {
} }
// Leave the Println. This needs clean output for piping // Leave the Println. This needs clean output for piping
fmt.Printf("%s\n", block.State().Dump()) statedb := state.New(block.Root(), ethereum.Db())
fmt.Printf("%s\n", statedb.Dump())
fmt.Println(block) fmt.Println(block)
@ -134,3 +144,13 @@ func main() {
// this blocks the thread // this blocks the thread
ethereum.WaitForShutdown() ethereum.WaitForShutdown()
} }
func printVersion() {
fmt.Printf(`%v %v
PV=%d
GOOS=%s
GO=%s
GOPATH=%s
GOROOT=%s
`, ClientIdentifier, Version, eth.ProtocolVersion, runtime.GOOS, runtime.Version(), os.Getenv("GOPATH"), runtime.GOROOT())
}

@ -86,6 +86,11 @@ func (self *JSRepl) Stop() {
} }
func (self *JSRepl) parseInput(code string) { func (self *JSRepl) parseInput(code string) {
defer func() {
if r := recover(); r != nil {
fmt.Println("[native] error", r)
}
}()
value, err := self.re.Run(code) value, err := self.re.Run(code)
if err != nil { if err != nil {

@ -17,8 +17,6 @@
/** /**
* @authors: * @authors:
* Jeffrey Wilcke <i@jev.io> * Jeffrey Wilcke <i@jev.io>
* @date 2014
*
*/ */
package main package main
@ -26,12 +24,15 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/tests/helper" "github.com/ethereum/go-ethereum/tests/helper"
) )
@ -43,8 +44,8 @@ type Account struct {
Storage map[string]string Storage map[string]string
} }
func StateObjectFromAccount(addr string, account Account) *state.StateObject { func StateObjectFromAccount(db ethutil.Database, addr string, account Account) *state.StateObject {
obj := state.NewStateObject(ethutil.Hex2Bytes(addr)) obj := state.NewStateObject(ethutil.Hex2Bytes(addr), db)
obj.SetBalance(ethutil.Big(account.Balance)) obj.SetBalance(ethutil.Big(account.Balance))
if ethutil.IsHex(account.Code) { if ethutil.IsHex(account.Code) {
@ -66,19 +67,20 @@ type VmTest struct {
Pre map[string]Account Pre map[string]Account
} }
func RunVmTest(js string) (failed int) { func RunVmTest(r io.Reader) (failed int) {
tests := make(map[string]VmTest) tests := make(map[string]VmTest)
data, _ := ioutil.ReadAll(strings.NewReader(js)) data, _ := ioutil.ReadAll(r)
err := json.Unmarshal(data, &tests) err := json.Unmarshal(data, &tests)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
for name, test := range tests { for name, test := range tests {
state := state.New(helper.NewTrie()) db, _ := ethdb.NewMemDatabase()
state := state.New(nil, db)
for addr, account := range test.Pre { for addr, account := range test.Pre {
obj := StateObjectFromAccount(addr, account) obj := StateObjectFromAccount(db, addr, account)
state.SetStateObject(obj) state.SetStateObject(obj)
} }
@ -118,6 +120,8 @@ func RunVmTest(js string) (failed int) {
} }
} }
} }
logger.Flush()
} }
return return
@ -125,9 +129,10 @@ func RunVmTest(js string) (failed int) {
func main() { func main() {
helper.Logger.SetLogLevel(5) helper.Logger.SetLogLevel(5)
if len(os.Args) == 1 {
log.Fatalln("no json supplied")
}
os.Exit(RunVmTest(os.Args[1])) if len(os.Args) > 1 {
os.Exit(RunVmTest(strings.NewReader(os.Args[1])))
} else {
os.Exit(RunVmTest(os.Stdin))
}
} }

@ -17,8 +17,6 @@
/** /**
* @authors * @authors
* Jeffrey Wilcke <i@jev.io> * Jeffrey Wilcke <i@jev.io>
* @date 2014
*
*/ */
package main package main
@ -37,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/ptrie"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/vm" "github.com/ethereum/go-ethereum/vm"
) )
@ -65,7 +62,7 @@ func main() {
ethutil.ReadConfig("/tmp/evmtest", "/tmp/evm", "") ethutil.ReadConfig("/tmp/evmtest", "/tmp/evm", "")
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
statedb := state.New(ptrie.New(nil, db)) statedb := state.New(nil, db)
sender := statedb.NewStateObject([]byte("sender")) sender := statedb.NewStateObject([]byte("sender"))
receiver := statedb.NewStateObject([]byte("receiver")) receiver := statedb.NewStateObject([]byte("receiver"))
//receiver.SetCode([]byte(*code)) //receiver.SetCode([]byte(*code))
@ -133,6 +130,12 @@ func (self *VMEnv) Value() *big.Int { return self.value }
func (self *VMEnv) GasLimit() *big.Int { return big.NewInt(1000000000) } func (self *VMEnv) GasLimit() *big.Int { return big.NewInt(1000000000) }
func (self *VMEnv) Depth() int { return 0 } func (self *VMEnv) Depth() int { return 0 }
func (self *VMEnv) SetDepth(i int) { self.depth = i } func (self *VMEnv) SetDepth(i int) { self.depth = i }
func (self *VMEnv) GetHash(n uint64) []byte {
if self.block.Number().Cmp(big.NewInt(int64(n))) == 0 {
return self.block.Hash()
}
return nil
}
func (self *VMEnv) AddLog(log state.Log) { func (self *VMEnv) AddLog(log state.Log) {
self.state.AddLog(log) self.state.AddLog(log)
} }

@ -19,7 +19,7 @@ ApplicationWindow {
property alias dataText: rawDataField.text property alias dataText: rawDataField.text
onClosing: { onClosing: {
dbg.Stop() //dbg.Stop()
} }
menuBar: MenuBar { menuBar: MenuBar {
@ -353,6 +353,7 @@ ApplicationWindow {
ComboBox { ComboBox {
visible: false
id: snippets id: snippets
anchors.right: parent.right anchors.right: parent.right
model: ListModel { model: ListModel {

@ -1,18 +0,0 @@
# Ethereum JavaScript API
This is the Ethereum compatible JavaScript API using `Promise`s
which implements the [Generic JSON RPC](https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC) spec.
For an example see `index.html`.
**Please note this repo is in it's early stage.**
If you'd like to run a WebSocket ethereum node check out
[go-ethereum](https://github.com/ethereum/go-ethereum).
To install ethereum and spawn a node:
```
go get github.com/ethereum/go-ethereum/ethereum
ethereum -ws -loglevel=4
```

@ -1,70 +0,0 @@
(function () {
var HttpRpcProvider = function (host) {
this.handlers = [];
this.host = host;
};
function formatJsonRpcObject(object) {
return {
jsonrpc: '2.0',
method: object.call,
params: object.args,
id: object._id
}
};
function formatJsonRpcMessage(message) {
var object = JSON.parse(message);
return {
_id: object.id,
data: object.result
};
};
HttpRpcProvider.prototype.sendRequest = function (payload, cb) {
var data = formatJsonRpcObject(payload);
var request = new XMLHttpRequest();
request.open("POST", this.host, true);
request.send(JSON.stringify(data));
request.onreadystatechange = function () {
if (request.readyState === 4 && cb) {
cb(request);
}
}
};
HttpRpcProvider.prototype.send = function (payload) {
var self = this;
this.sendRequest(payload, function (request) {
self.handlers.forEach(function (handler) {
handler.call(self, formatJsonRpcMessage(request.responseText));
});
});
};
HttpRpcProvider.prototype.poll = function (payload, id) {
var self = this;
this.sendRequest(payload, function (request) {
var parsed = JSON.parse(request.responseText);
if (parsed.result instanceof Array ? parsed.result.length === 0 : !parsed.result) {
return;
}
self.handlers.forEach(function (handler) {
handler.call(self, {_event: payload.call, _id: id, data: parsed.result});
});
});
};
Object.defineProperty(HttpRpcProvider.prototype, "onmessage", {
set: function (handler) {
this.handlers.push(handler);
}
});
if (typeof(web3) !== "undefined" && web3.providers !== undefined) {
web3.providers.HttpRpcProvider = HttpRpcProvider;
}
})();

@ -1,33 +0,0 @@
<!doctype>
<html>
<head>
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript" src="websocket.js"></script>
<script type="text/javascript" src="qt.js"></script>
<script type="text/javascript" src="httprpc.js"></script>
<script type="text/javascript">
function registerName() {
var name = document.querySelector("#name").value;
name = web3.fromAscii(name);
var eth = web3.eth;
eth.transact({to: "NameReg", gas: "10000", gasPrice: eth.gasPrice, data: [web3.fromAscii("register"), name]}).then(function(tx) {
document.querySelector("#result").innerHTML = "Registered name. Please wait for the next block to come through.";
}, function(err) {
console.log(err);
});
}
</script>
</head>
<body>
<h1>std::name_reg</h1>
<input type="text" id="name"></input>
<input type="submit" onClick="registerName();"></input>
<div id="result"></div>
</body>
</html>

@ -1,432 +0,0 @@
(function(window) {
function isPromise(o) {
return o instanceof Promise
}
function flattenPromise (obj) {
if (obj instanceof Promise) {
return Promise.resolve(obj);
}
if (obj instanceof Array) {
return new Promise(function (resolve) {
var promises = obj.map(function (o) {
return flattenPromise(o);
});
return Promise.all(promises).then(function (res) {
for (var i = 0; i < obj.length; i++) {
obj[i] = res[i];
}
resolve(obj);
});
});
}
if (obj instanceof Object) {
return new Promise(function (resolve) {
var keys = Object.keys(obj);
var promises = keys.map(function (key) {
return flattenPromise(obj[key]);
});
return Promise.all(promises).then(function (res) {
for (var i = 0; i < keys.length; i++) {
obj[keys[i]] = res[i];
}
resolve(obj);
});
});
}
return Promise.resolve(obj);
};
var ethMethods = function () {
var blockCall = function (args) {
return typeof args[0] === "string" ? "blockByHash" : "blockByNumber";
};
var transactionCall = function (args) {
return typeof args[0] === "string" ? 'transactionByHash' : 'transactionByNumber';
};
var uncleCall = function (args) {
return typeof args[0] === "string" ? 'uncleByHash' : 'uncleByNumber';
};
var methods = [
{ name: 'balanceAt', call: 'balanceAt' },
{ name: 'stateAt', call: 'stateAt' },
{ name: 'countAt', call: 'countAt'},
{ name: 'codeAt', call: 'codeAt' },
{ name: 'transact', call: 'transact' },
{ name: 'call', call: 'call' },
{ name: 'block', call: blockCall },
{ name: 'transaction', call: transactionCall },
{ name: 'uncle', call: uncleCall },
{ name: 'compile', call: 'compile' }
];
return methods;
};
var ethProperties = function () {
return [
{ name: 'coinbase', getter: 'coinbase', setter: 'setCoinbase' },
{ name: 'listening', getter: 'listening', setter: 'setListening' },
{ name: 'mining', getter: 'mining', setter: 'setMining' },
{ name: 'gasPrice', getter: 'gasPrice' },
{ name: 'account', getter: 'account' },
{ name: 'accounts', getter: 'accounts' },
{ name: 'peerCount', getter: 'peerCount' },
{ name: 'defaultBlock', getter: 'defaultBlock', setter: 'setDefaultBlock' },
{ name: 'number', getter: 'number'}
];
};
var dbMethods = function () {
return [
{ name: 'put', call: 'put' },
{ name: 'get', call: 'get' },
{ name: 'putString', call: 'putString' },
{ name: 'getString', call: 'getString' }
];
};
var shhMethods = function () {
return [
{ name: 'post', call: 'post' },
{ name: 'newIdentity', call: 'newIdentity' },
{ name: 'haveIdentity', call: 'haveIdentity' },
{ name: 'newGroup', call: 'newGroup' },
{ name: 'addToGroup', call: 'addToGroup' }
];
};
var ethWatchMethods = function () {
var newFilter = function (args) {
return typeof args[0] === 'string' ? 'newFilterString' : 'newFilter';
};
return [
{ name: 'newFilter', call: newFilter },
{ name: 'uninstallFilter', call: 'uninstallFilter' },
{ name: 'getMessages', call: 'getMessages' }
];
};
var shhWatchMethods = function () {
return [
{ name: 'newFilter', call: 'shhNewFilter' },
{ name: 'uninstallFilter', call: 'shhUninstallFilter' },
{ name: 'getMessage', call: 'shhGetMessages' }
];
};
var setupMethods = function (obj, methods) {
methods.forEach(function (method) {
obj[method.name] = function () {
return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) {
var call = typeof method.call === "function" ? method.call(args) : method.call;
return {call: call, args: args};
}).then(function (request) {
return new Promise(function (resolve, reject) {
web3.provider.send(request, function (result) {
//if (result || typeof result === "boolean") {
resolve(result);
return;
//}
//reject(result);
});
});
}).catch(function( err) {
console.error(err);
});
};
});
};
var setupProperties = function (obj, properties) {
properties.forEach(function (property) {
var proto = {};
proto.get = function () {
return new Promise(function(resolve, reject) {
web3.provider.send({call: property.getter}, function(result) {
resolve(result);
});
});
};
if (property.setter) {
proto.set = function (val) {
return flattenPromise([val]).then(function (args) {
return new Promise(function (resolve) {
web3.provider.send({call: property.setter, args: args}, function (result) {
resolve(result);
});
});
}).catch(function (err) {
console.error(err);
});
}
}
Object.defineProperty(obj, property.name, proto);
});
};
var web3 = {
_callbacks: {},
_events: {},
providers: {},
toHex: function(str) {
var hex = "";
for(var i = 0; i < str.length; i++) {
var n = str.charCodeAt(i).toString(16);
hex += n.length < 2 ? '0' + n : n;
}
return hex;
},
toAscii: function(hex) {
// Find termination
var str = "";
var i = 0, l = hex.length;
for(; i < l; i+=2) {
var code = hex.charCodeAt(i)
if(code == 0) {
break;
}
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
},
toDecimal: function (val) {
return parseInt(val, 16);
},
fromAscii: function(str, pad) {
pad = pad === undefined ? 32 : pad;
var hex = this.toHex(str);
while(hex.length < pad*2)
hex += "00";
return hex
},
eth: {
prototype: Object(),
watch: function (params) {
return new Filter(params, ethWatch);
},
},
db: {
prototype: Object()
},
shh: {
prototype: Object(),
watch: function (params) {
return new Filter(params, shhWatch);
}
},
on: function(event, id, cb) {
if(web3._events[event] === undefined) {
web3._events[event] = {};
}
web3._events[event][id] = cb;
return this
},
off: function(event, id) {
if(web3._events[event] !== undefined) {
delete web3._events[event][id];
}
return this
},
trigger: function(event, id, data) {
var callbacks = web3._events[event];
if (!callbacks || !callbacks[id]) {
return;
}
var cb = callbacks[id];
cb(data);
},
};
var eth = web3.eth;
setupMethods(eth, ethMethods());
setupProperties(eth, ethProperties());
setupMethods(web3.db, dbMethods());
setupMethods(web3.shh, shhMethods());
var ethWatch = {
changed: 'changed'
};
setupMethods(ethWatch, ethWatchMethods());
var shhWatch = {
changed: 'shhChanged'
};
setupMethods(shhWatch, shhWatchMethods());
var ProviderManager = function() {
this.queued = [];
this.polls = [];
this.ready = false;
this.provider = undefined;
this.id = 1;
var self = this;
var poll = function () {
if (self.provider && self.provider.poll) {
self.polls.forEach(function (data) {
data.data._id = self.id;
self.id++;
self.provider.poll(data.data, data.id);
});
}
setTimeout(poll, 12000);
};
poll();
};
ProviderManager.prototype.send = function(data, cb) {
data._id = this.id;
if (cb) {
web3._callbacks[data._id] = cb;
}
data.args = data.args || [];
this.id++;
if(this.provider !== undefined) {
this.provider.send(data);
} else {
console.warn("provider is not set");
this.queued.push(data);
}
};
ProviderManager.prototype.set = function(provider) {
if(this.provider !== undefined && this.provider.unload !== undefined) {
this.provider.unload();
}
this.provider = provider;
this.ready = true;
};
ProviderManager.prototype.sendQueued = function() {
for(var i = 0; this.queued.length; i++) {
// Resend
this.send(this.queued[i]);
}
};
ProviderManager.prototype.installed = function() {
return this.provider !== undefined;
};
ProviderManager.prototype.startPolling = function (data, pollId) {
if (!this.provider || !this.provider.poll) {
return;
}
this.polls.push({data: data, id: pollId});
};
ProviderManager.prototype.stopPolling = function (pollId) {
for (var i = this.polls.length; i--;) {
var poll = this.polls[i];
if (poll.id === pollId) {
this.polls.splice(i, 1);
}
}
};
web3.provider = new ProviderManager();
web3.setProvider = function(provider) {
provider.onmessage = messageHandler;
web3.provider.set(provider);
web3.provider.sendQueued();
};
var Filter = function(options, impl) {
this.impl = impl;
this.callbacks = [];
var self = this;
this.promise = impl.newFilter(options);
this.promise.then(function (id) {
self.id = id;
web3.on(impl.changed, id, self.trigger.bind(self));
web3.provider.startPolling({call: impl.changed, args: [id]}, id);
});
};
Filter.prototype.arrived = function(callback) {
this.changed(callback);
}
Filter.prototype.changed = function(callback) {
var self = this;
this.promise.then(function(id) {
self.callbacks.push(callback);
});
};
Filter.prototype.trigger = function(messages) {
for(var i = 0; i < this.callbacks.length; i++) {
this.callbacks[i].call(this, messages);
}
};
Filter.prototype.uninstall = function() {
var self = this;
this.promise.then(function (id) {
self.impl.uninstallFilter(id);
web3.provider.stopPolling(id);
web3.off(impl.changed, id);
});
};
Filter.prototype.messages = function() {
var self = this;
return this.promise.then(function (id) {
return self.impl.getMessages(id);
});
};
function messageHandler(data) {
if(data._event !== undefined) {
web3.trigger(data._event, data._id, data.data);
return;
}
if(data._id) {
var cb = web3._callbacks[data._id];
if (cb) {
cb.call(this, data.data)
delete web3._callbacks[data._id];
}
}
}
/*
// Install default provider
if(!web3.provider.installed()) {
var sock = new web3.WebSocket("ws://localhost:40404/eth");
web3.setProvider(sock);
}
*/
window.web3 = web3;
})(this);

@ -1,27 +0,0 @@
(function() {
var QtProvider = function() {
this.handlers = [];
var self = this;
navigator.qt.onmessage = function (message) {
self.handlers.forEach(function (handler) {
handler.call(self, JSON.parse(message.data));
});
}
};
QtProvider.prototype.send = function(payload) {
navigator.qt.postMessage(JSON.stringify(payload));
};
Object.defineProperty(QtProvider.prototype, "onmessage", {
set: function(handler) {
this.handlers.push(handler);
},
});
if(typeof(web3) !== "undefined" && web3.providers !== undefined) {
web3.providers.QtProvider = QtProvider;
}
})();

@ -1,51 +0,0 @@
(function() {
var WebSocketProvider = function(host) {
// onmessage handlers
this.handlers = [];
// queue will be filled with messages if send is invoked before the ws is ready
this.queued = [];
this.ready = false;
this.ws = new WebSocket(host);
var self = this;
this.ws.onmessage = function(event) {
for(var i = 0; i < self.handlers.length; i++) {
self.handlers[i].call(self, JSON.parse(event.data), event)
}
};
this.ws.onopen = function() {
self.ready = true;
for(var i = 0; i < self.queued.length; i++) {
// Resend
self.send(self.queued[i]);
}
};
};
WebSocketProvider.prototype.send = function(payload) {
if(this.ready) {
var data = JSON.stringify(payload);
this.ws.send(data);
} else {
this.queued.push(payload);
}
};
WebSocketProvider.prototype.onMessage = function(handler) {
this.handlers.push(handler);
};
WebSocketProvider.prototype.unload = function() {
this.ws.close();
};
Object.defineProperty(WebSocketProvider.prototype, "onmessage", {
set: function(provider) { this.onMessage(provider); }
});
if(typeof(web3) !== "undefined" && web3.providers !== undefined) {
web3.providers.WebSocketProvider = WebSocketProvider;
}
})();

@ -1,312 +0,0 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved.
//
// This library 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 2.1 of the License, or (at your option) any later version.
//
// This library 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 this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA
// Main Ethereum library
window.eth = {
prototype: Object(),
_callbacks: {},
_onCallbacks: {},
test: function() {
var t = undefined;
postData({call: "test"})
navigator.qt.onmessage = function(d) {console.log("onmessage called"); t = d; }
for(;;) {
if(t !== undefined) {
return t
}
}
},
mutan: function(code, cb) {
postData({call: "mutan", args: [code]}, cb)
},
toHex: function(str) {
var hex = "";
for(var i = 0; i < str.length; i++) {
var n = str.charCodeAt(i).toString(16);
hex += n.length < 2 ? '0' + n : n;
}
return hex;
},
toAscii: function(hex) {
// Find termination
var str = "";
var i = 0, l = hex.length;
for(; i < l; i+=2) {
var code = hex.charCodeAt(i)
if(code == 0) {
break;
}
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
},
fromAscii: function(str, pad) {
if(pad === undefined) {
pad = 32
}
var hex = this.toHex(str);
while(hex.length < pad*2)
hex += "00";
return hex
},
// Retrieve block
//
// Either supply a number or a string. Type is determent for the lookup method
// string - Retrieves the block by looking up the hash
// number - Retrieves the block by looking up the block number
getBlock: function(numberOrHash, cb) {
var func;
if(typeof numberOrHash == "string") {
func = "getBlockByHash";
} else {
func = "getBlockByNumber";
}
postData({call: func, args: [numberOrHash]}, cb);
},
// Create transaction
//
// Transact between two state objects
transact: function(params, cb) {
if(params === undefined) {
params = {};
}
if(params.endowment !== undefined)
params.value = params.endowment;
if(params.code !== undefined)
params.data = params.code;
// Make sure everything is string
var fields = ["to", "from", "value", "gas", "gasPrice"];
for(var i = 0; i < fields.length; i++) {
if(params[fields[i]] === undefined) {
params[fields[i]] = "";
}
params[fields[i]] = params[fields[i]].toString();
}
var data;
if(typeof params.data === "object") {
data = "";
for(var i = 0; i < params.data.length; i++) {
data += params.data[i]
}
} else {
data = params.data;
}
postData({call: "transact", args: [params.from, params.to, params.value, params.gas, params.gasPrice, "0x"+data]}, cb);
},
getMessages: function(filter, cb) {
postData({call: "messages", args: [filter]}, cb);
},
getStorageAt: function(address, storageAddress, cb) {
postData({call: "getStorage", args: [address, storageAddress]}, cb);
},
getEachStorageAt: function(address, cb){
postData({call: "getEachStorage", args: [address]}, cb);
},
getKey: function(cb) {
postData({call: "getKey"}, cb);
},
getTxCountAt: function(address, cb) {
postData({call: "getTxCountAt", args: [address]}, cb);
},
getIsMining: function(cb){
postData({call: "getIsMining"}, cb)
},
getIsListening: function(cb){
postData({call: "getIsListening"}, cb)
},
getCoinBase: function(cb){
postData({call: "getCoinBase"}, cb);
},
getPeerCount: function(cb){
postData({call: "getPeerCount"}, cb);
},
getBalanceAt: function(address, cb) {
postData({call: "getBalance", args: [address]}, cb);
},
getTransactionsFor: function(address, cb) {
postData({call: "getTransactionsFor", args: [address]}, cb);
},
getSecretToAddress: function(sec, cb) {
postData({call: "getSecretToAddress", args: [sec]}, cb);
},
/*
watch: function(address, storageAddrOrCb, cb) {
var ev;
if(cb === undefined) {
cb = storageAddrOrCb;
storageAddrOrCb = "";
ev = "object:"+address;
} else {
ev = "storage:"+address+":"+storageAddrOrCb;
}
eth.on(ev, cb)
postData({call: "watch", args: [address, storageAddrOrCb]});
},
disconnect: function(address, storageAddrOrCb, cb) {
var ev;
if(cb === undefined) {
cb = storageAddrOrCb;
storageAddrOrCb = "";
ev = "object:"+address;
} else {
ev = "storage:"+address+":"+storageAddrOrCb;
}
eth.off(ev, cb)
postData({call: "disconnect", args: [address, storageAddrOrCb]});
},
*/
watch: function(options) {
var filter = new Filter(options);
filter.number = newWatchNum().toString()
postData({call: "watch", args: [options, filter.number]})
return filter;
},
set: function(props) {
postData({call: "set", args: props});
},
on: function(event, cb) {
if(eth._onCallbacks[event] === undefined) {
eth._onCallbacks[event] = [];
}
eth._onCallbacks[event].push(cb);
return this
},
off: function(event, cb) {
if(eth._onCallbacks[event] !== undefined) {
var callbacks = eth._onCallbacks[event];
for(var i = 0; i < callbacks.length; i++) {
if(callbacks[i] === cb) {
delete callbacks[i];
}
}
}
return this
},
trigger: function(event, data) {
var callbacks = eth._onCallbacks[event];
if(callbacks !== undefined) {
for(var i = 0; i < callbacks.length; i++) {
// Figure out whether the returned data was an array
// array means multiple return arguments (multiple params)
if(data instanceof Array) {
callbacks[i].apply(this, data);
} else {
callbacks[i].call(this, data);
}
}
}
},
}
var Filter = function(options) {
this.options = options;
};
Filter.prototype.changed = function(callback) {
// Register the watched:<number>. Qml will call the appropriate event if anything
// interesting happens in the land of Go.
eth.on("watched:"+this.number, callback)
}
Filter.prototype.getMessages = function(cb) {
return eth.getMessages(this.options, cb)
}
var watchNum = 0;
function newWatchNum() {
return watchNum++;
}
function postData(data, cb) {
data._seed = Math.floor(Math.random() * 1000000)
if(cb) {
eth._callbacks[data._seed] = cb;
}
if(data.args === undefined) {
data.args = [];
}
navigator.qt.postMessage(JSON.stringify(data));
}
navigator.qt.onmessage = function(ev) {
var data = JSON.parse(ev.data)
if(data._event !== undefined) {
eth.trigger(data._event, data.data);
} else {
if(data._seed) {
var cb = eth._callbacks[data._seed];
if(cb) {
cb.call(this, data.data)
// Remove the "trigger" callback
delete eth._callbacks[ev._seed];
}
}
}
}
eth.on("chain:changed", function() {
})
eth.on("messages", { /* filters */}, function(messages){
})
eth.on("pending:changed", function() {
})

@ -0,0 +1,5 @@
{
"directory": "example/js/",
"cwd": "./",
"analytics": false
}

@ -0,0 +1,12 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

@ -4,6 +4,7 @@
# or operating system, you probably want to add a global ignore instead: # or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile ~/.gitignore_global # git config --global core.excludesfile ~/.gitignore_global
*.swp
/tmp /tmp
*/**/*un~ */**/*un~
*un~ *un~
@ -11,4 +12,7 @@
*/**/.DS_Store */**/.DS_Store
ethereum/ethereum ethereum/ethereum
ethereal/ethereal ethereal/ethereal
example/js
node_modules
bower_components
npm-debug.log

@ -0,0 +1,50 @@
{
"predef": [
"console",
"require",
"equal",
"test",
"testBoth",
"testWithDefault",
"raises",
"deepEqual",
"start",
"stop",
"ok",
"strictEqual",
"module",
"expect",
"reject",
"impl"
],
"esnext": true,
"proto": true,
"node" : true,
"browser" : true,
"browserify" : true,
"boss" : true,
"curly": false,
"debug": true,
"devel": true,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false,
"shadow": true,
"eqnull": true
}

@ -0,0 +1,9 @@
example/js
node_modules
test
.gitignore
.editorconfig
.travis.yml
.npmignore
component.json
testling.html

@ -0,0 +1,11 @@
language: node_js
node_js:
- "0.11"
- "0.10"
before_script:
- npm install
- npm install jshint
script:
- "jshint *.js lib"
after_script:
- npm run-script gulp

@ -0,0 +1,14 @@
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.

@ -0,0 +1,79 @@
# Ethereum JavaScript API
This is the Ethereum compatible JavaScript API using `Promise`s
which implements the [Generic JSON RPC](https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC) spec. It's available on npm as a node module and also for bower and component as an embeddable js
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![dependency status][dep-image]][dep-url] [![dev dependency status][dep-dev-image]][dep-dev-url]
<!-- [![browser support](https://ci.testling.com/ethereum/ethereum.js.png)](https://ci.testling.com/ethereum/ethereum.js) -->
## Installation
### Node.js
npm install ethereum.js
### For browser
Bower
bower install ethereum.js
Component
component install ethereum/ethereum.js
* Include `ethereum.min.js` in your html file.
* Include [es6-promise](https://github.com/jakearchibald/es6-promise) or another ES6-Shim if your browser doesn't support ECMAScript 6.
## Usage
Require the library:
var web3 = require('web3');
Set a provider (QtProvider, WebSocketProvider, HttpRpcProvider)
var web3.setProvider(new web3.providers.WebSocketProvider('ws://localhost:40404/eth'));
There you go, now you can use it:
```
web3.eth.coinbase.then(function(result){
console.log(result);
return web3.eth.balanceAt(result);
}).then(function(balance){
console.log(web3.toDecimal(balance));
}).catch(function(err){
console.log(err);
});
```
For another example see `example/index.html`.
## Building
* `gulp build`
### Testing
**Please note this repo is in it's early stage.**
If you'd like to run a WebSocket ethereum node check out
[go-ethereum](https://github.com/ethereum/go-ethereum).
To install ethereum and spawn a node:
```
go get github.com/ethereum/go-ethereum/ethereum
ethereum -ws -loglevel=4
```
[npm-image]: https://badge.fury.io/js/ethereum.js.png
[npm-url]: https://npmjs.org/package/ethereum.js
[travis-image]: https://travis-ci.org/ethereum/ethereum.js.svg
[travis-url]: https://travis-ci.org/ethereum/ethereum.js
[dep-image]: https://david-dm.org/ethereum/ethereum.js.svg
[dep-url]: https://david-dm.org/ethereum/ethereum.js
[dep-dev-image]: https://david-dm.org/ethereum/ethereum.js/dev-status.svg
[dep-dev-url]: https://david-dm.org/ethereum/ethereum.js#info=devDependencies

@ -0,0 +1,51 @@
{
"name": "ethereum.js",
"namespace": "ethereum",
"version": "0.0.3",
"description": "Ethereum Compatible JavaScript API",
"main": ["./dist/ethereum.js", "./dist/ethereum.min.js"],
"dependencies": {
"es6-promise": "#master"
},
"repository": {
"type": "git",
"url": "https://github.com/ethereum/ethereum.js.git"
},
"homepage": "https://github.com/ethereum/ethereum.js",
"bugs": {
"url": "https://github.com/ethereum/ethereum.js/issues"
},
"keywords": [
"ethereum",
"javascript",
"API"
],
"authors": [
{
"name": "Marek Kotewicz",
"email": "marek@ethdev.com",
"homepage": "https://github.com/debris"
},
{
"name": "Marian Oancea",
"email": "marian@ethdev.com",
"homepage": "https://github.com/cubedro"
}
],
"license": "LGPL-3.0",
"ignore": [
"example",
"lib",
"node_modules",
"package.json",
".bowerrc",
".editorconfig",
".gitignore",
".jshintrc",
".npmignore",
".travis.yml",
"gulpfile.js",
"index.js",
"**/*.txt"
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,41 @@
<!doctype>
<html>
<head>
<script type="text/javascript" src="js/es6-promise/promise.min.js"></script>
<script type="text/javascript" src="../dist/ethereum.js"></script>
<script type="text/javascript">
var web3 = require('web3');
web3.setProvider(new web3.providers.AutoProvider());
function watchBalance() {
var coinbase = web3.eth.coinbase;
var originalBalance = 0;
web3.eth.balanceAt(coinbase).then(function (balance) {
originalBalance = web3.toDecimal(balance);
document.getElementById('original').innerText = 'original balance: ' + originalBalance + ' watching...';
});
web3.eth.watch({altered: coinbase}).changed(function() {
web3.eth.balanceAt(coinbase).then(function (balance) {
var currentBalance = web3.toDecimal(balance);
document.getElementById("current").innerText = 'current: ' + currentBalance;
document.getElementById("diff").innerText = 'diff: ' + (currentBalance - originalBalance);
});
});
}
</script>
</head>
<body>
<h1>coinbase balance</h1>
<button type="button" onClick="watchBalance();">watch balance</button>
<div></div>
<div id="original"></div>
<div id="current"></div>
<div id="diff"></div>
</body>
</html>

@ -0,0 +1,75 @@
<!doctype>
<html>
<head>
<script type="text/javascript" src="js/es6-promise/promise.min.js"></script>
<script type="text/javascript" src="../dist/ethereum.js"></script>
<script type="text/javascript">
var web3 = require('web3');
web3.setProvider(new web3.providers.AutoProvider());
// solidity source code
var source = "" +
"contract test {\n" +
" function multiply(uint a) returns(uint d) {\n" +
" return a * 7;\n" +
" }\n" +
"}\n";
// contract description, this will be autogenerated somehow
var desc = [{
"name": "multiply",
"inputs": [
{
"name": "a",
"type": "uint256"
}
],
"outputs": [
{
"name": "d",
"type": "uint256"
}
]
}];
var contract;
function createExampleContract() {
// hide create button
document.getElementById('create').style.visibility = 'hidden';
document.getElementById('source').innerText = source;
// create contract
web3.eth.transact({code: web3.eth.solidity(source)}).then(function (address) {
contract = web3.contract(address, desc);
document.getElementById('call').style.visibility = 'visible';
});
}
function callExampleContract() {
// this should be generated by ethereum
var param = parseInt(document.getElementById('value').value);
// call the contract
contract.multiply(param).call().then(function(res) {
document.getElementById('result').innerText = res[0];
});
}
</script>
</head>
<body>
<h1>contract</h1>
<div id="source"></div>
<div id='create'>
<button type="button" onClick="createExampleContract();">create example contract</button>
</div>
<div id='call' style='visibility: hidden;'>
<input type="number" id="value" onkeyup='callExampleContract()'></input>
</div>
<div id="result"></div>
</body>
</html>

@ -0,0 +1,16 @@
#!/usr/bin/env node
require('es6-promise').polyfill();
var web3 = require("../index.js");
web3.setProvider(new web3.providers.HttpRpcProvider('http://localhost:8080'));
web3.eth.coinbase.then(function(result){
console.log(result);
return web3.eth.balanceAt(result);
}).then(function(balance){
console.log(web3.toDecimal(balance));
}).catch(function(err){
console.log(err);
});

@ -0,0 +1,104 @@
#!/usr/bin/env node
'use strict';
var path = require('path');
var del = require('del');
var gulp = require('gulp');
var browserify = require('browserify');
var jshint = require('gulp-jshint');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var envify = require('envify/custom');
var unreach = require('unreachable-branch-transform');
var source = require('vinyl-source-stream');
var exorcist = require('exorcist');
var bower = require('bower');
var DEST = './dist/';
var build = function(src, dst, ugly) {
var result = browserify({
debug: true,
insert_global_vars: false,
detectGlobals: false,
bundleExternal: false
})
.require('./' + src + '.js', {expose: 'web3'})
.add('./' + src + '.js')
.transform('envify', {
NODE_ENV: 'build'
})
.transform('unreachable-branch-transform');
if (ugly) {
result = result.transform('uglifyify', {
mangle: false,
compress: {
dead_code: false,
conditionals: true,
unused: false,
hoist_funs: true,
hoist_vars: true,
negate_iife: false
},
beautify: true,
warnings: true
});
}
return result.bundle()
.pipe(exorcist(path.join( DEST, dst + '.js.map')))
.pipe(source(dst + '.js'))
.pipe(gulp.dest( DEST ));
};
var uglifyFile = function(file) {
return gulp.src( DEST + file + '.js')
.pipe(uglify())
.pipe(rename(file + '.min.js'))
.pipe(gulp.dest( DEST ));
};
gulp.task('bower', function(cb){
bower.commands.install().on('end', function (installed){
console.log(installed);
cb();
});
});
gulp.task('clean', ['lint'], function(cb) {
del([ DEST ], cb);
});
gulp.task('lint', function(){
return gulp.src(['./*.js', './lib/*.js'])
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
gulp.task('build', ['clean'], function () {
return build('index', 'ethereum', true);
});
gulp.task('buildDev', ['clean'], function () {
return build('index', 'ethereum', false);
});
gulp.task('uglify', ['build'], function(){
return uglifyFile('ethereum');
});
gulp.task('uglify', ['buildDev'], function(){
return uglifyFile('ethereum');
});
gulp.task('watch', function() {
gulp.watch(['./lib/*.js'], ['lint', 'prepare', 'build']);
});
gulp.task('release', ['bower', 'lint', 'build', 'uglify']);
gulp.task('dev', ['bower', 'lint', 'buildDev', 'uglify']);
gulp.task('default', ['dev']);

@ -0,0 +1,8 @@
var web3 = require('./lib/web3');
web3.providers.WebSocketProvider = require('./lib/websocket');
web3.providers.HttpRpcProvider = require('./lib/httprpc');
web3.providers.QtProvider = require('./lib/qt');
web3.providers.AutoProvider = require('./lib/autoprovider');
web3.contract = require('./lib/contract');
module.exports = web3;

@ -0,0 +1,267 @@
/*
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file abi.js
* @authors:
* Marek Kotewicz <marek@ethdev.com>
* Gav Wood <g@ethdev.com>
* @date 2014
*/
// TODO: is these line is supposed to be here?
if (process.env.NODE_ENV !== 'build') {
var web3 = require('./web3'); // jshint ignore:line
}
// TODO: make these be actually accurate instead of falling back onto JS's doubles.
var hexToDec = function (hex) {
return parseInt(hex, 16).toString();
};
var decToHex = function (dec) {
return parseInt(dec).toString(16);
};
var findIndex = function (array, callback) {
var end = false;
var i = 0;
for (; i < array.length && !end; i++) {
end = callback(array[i]);
}
return end ? i - 1 : -1;
};
var findMethodIndex = function (json, methodName) {
return findIndex(json, function (method) {
return method.name === methodName;
});
};
var padLeft = function (string, chars) {
return new Array(chars - string.length + 1).join("0") + string;
};
var calcBitPadding = function (type, expected) {
var value = type.slice(expected.length);
if (value === "") {
return 32;
}
return parseInt(value) / 8;
};
var calcBytePadding = function (type, expected) {
var value = type.slice(expected.length);
if (value === "") {
return 32;
}
return parseInt(value);
};
var calcRealPadding = function (type, expected) {
var value = type.slice(expected.length);
if (value === "") {
return 32;
}
var sizes = value.split('x');
for (var padding = 0, i = 0; i < sizes; i++) {
padding += (sizes[i] / 8);
}
return padding;
};
var setupInputTypes = function () {
var prefixedType = function (prefix, calcPadding) {
return function (type, value) {
var expected = prefix;
if (type.indexOf(expected) !== 0) {
return false;
}
var padding = calcPadding(type, expected);
if (typeof value === "number")
value = value.toString(16);
else if (typeof value === "string")
value = web3.toHex(value);
else if (value.indexOf('0x') === 0)
value = value.substr(2);
else
value = (+value).toString(16);
return padLeft(value, padding * 2);
};
};
var namedType = function (name, padding, formatter) {
return function (type, value) {
if (type !== name) {
return false;
}
return padLeft(formatter ? formatter(value) : value, padding * 2);
};
};
var formatBool = function (value) {
return value ? '0x1' : '0x0';
};
return [
prefixedType('uint', calcBitPadding),
prefixedType('int', calcBitPadding),
prefixedType('hash', calcBitPadding),
prefixedType('string', calcBytePadding),
prefixedType('real', calcRealPadding),
prefixedType('ureal', calcRealPadding),
namedType('address', 20),
namedType('bool', 1, formatBool),
];
};
var inputTypes = setupInputTypes();
var toAbiInput = function (json, methodName, params) {
var bytes = "";
var index = findMethodIndex(json, methodName);
if (index === -1) {
return;
}
bytes = "0x" + padLeft(index.toString(16), 2);
var method = json[index];
for (var i = 0; i < method.inputs.length; i++) {
var found = false;
for (var j = 0; j < inputTypes.length && !found; j++) {
found = inputTypes[j](method.inputs[i].type, params[i]);
}
if (!found) {
console.error('unsupported json type: ' + method.inputs[i].type);
}
bytes += found;
}
return bytes;
};
var setupOutputTypes = function () {
var prefixedType = function (prefix, calcPadding) {
return function (type) {
var expected = prefix;
if (type.indexOf(expected) !== 0) {
return -1;
}
var padding = calcPadding(type, expected);
return padding * 2;
};
};
var namedType = function (name, padding) {
return function (type) {
return name === type ? padding * 2 : -1;
};
};
var formatInt = function (value) {
return value.length <= 8 ? +parseInt(value, 16) : hexToDec(value);
};
var formatHash = function (value) {
return "0x" + value;
};
var formatBool = function (value) {
return value === '1' ? true : false;
};
var formatString = function (value) {
return web3.toAscii(value);
};
return [
{ padding: prefixedType('uint', calcBitPadding), format: formatInt },
{ padding: prefixedType('int', calcBitPadding), format: formatInt },
{ padding: prefixedType('hash', calcBitPadding), format: formatHash },
{ padding: prefixedType('string', calcBytePadding), format: formatString },
{ padding: prefixedType('real', calcRealPadding), format: formatInt },
{ padding: prefixedType('ureal', calcRealPadding), format: formatInt },
{ padding: namedType('address', 20) },
{ padding: namedType('bool', 1), format: formatBool }
];
};
var outputTypes = setupOutputTypes();
var fromAbiOutput = function (json, methodName, output) {
var index = findMethodIndex(json, methodName);
if (index === -1) {
return;
}
output = output.slice(2);
var result = [];
var method = json[index];
for (var i = 0; i < method.outputs.length; i++) {
var padding = -1;
for (var j = 0; j < outputTypes.length && padding === -1; j++) {
padding = outputTypes[j].padding(method.outputs[i].type);
}
if (padding === -1) {
// not found output parsing
continue;
}
var res = output.slice(0, padding);
var formatter = outputTypes[j - 1].format;
result.push(formatter ? formatter(res) : ("0x" + res));
output = output.slice(padding);
}
return result;
};
var inputParser = function (json) {
var parser = {};
json.forEach(function (method) {
parser[method.name] = function () {
var params = Array.prototype.slice.call(arguments);
return toAbiInput(json, method.name, params);
};
});
return parser;
};
var outputParser = function (json) {
var parser = {};
json.forEach(function (method) {
parser[method.name] = function (output) {
return fromAbiOutput(json, method.name, output);
};
});
return parser;
};
if (typeof(module) !== "undefined") {
module.exports = {
inputParser: inputParser,
outputParser: outputParser
};
}

@ -0,0 +1,103 @@
/*
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file autoprovider.js
* @authors:
* Marek Kotewicz <marek@ethdev.com>
* Marian Oancea <marian@ethdev.com>
* @date 2014
*/
/*
* @brief if qt object is available, uses QtProvider,
* if not tries to connect over websockets
* if it fails, it uses HttpRpcProvider
*/
// TODO: is these line is supposed to be here?
if (process.env.NODE_ENV !== 'build') {
var WebSocket = require('ws'); // jshint ignore:line
var web3 = require('./web3'); // jshint ignore:line
}
var AutoProvider = function (userOptions) {
if (web3.haveProvider()) {
return;
}
// before we determine what provider we are, we have to cache request
this.sendQueue = [];
this.onmessageQueue = [];
if (navigator.qt) {
this.provider = new web3.providers.QtProvider();
return;
}
userOptions = userOptions || {};
var options = {
httprpc: userOptions.httprpc || 'http://localhost:8080',
websockets: userOptions.websockets || 'ws://localhost:40404/eth'
};
var self = this;
var closeWithSuccess = function (success) {
ws.close();
if (success) {
self.provider = new web3.providers.WebSocketProvider(options.websockets);
} else {
self.provider = new web3.providers.HttpRpcProvider(options.httprpc);
self.poll = self.provider.poll.bind(self.provider);
}
self.sendQueue.forEach(function (payload) {
self.provider(payload);
});
self.onmessageQueue.forEach(function (handler) {
self.provider.onmessage = handler;
});
};
var ws = new WebSocket(options.websockets);
ws.onopen = function() {
closeWithSuccess(true);
};
ws.onerror = function() {
closeWithSuccess(false);
};
};
AutoProvider.prototype.send = function (payload) {
if (this.provider) {
this.provider.send(payload);
return;
}
this.sendQueue.push(payload);
};
Object.defineProperty(AutoProvider.prototype, 'onmessage', {
set: function (handler) {
if (this.provider) {
this.provider.onmessage = handler;
return;
}
this.onmessageQueue.push(handler);
}
});
if (typeof(module) !== "undefined")
module.exports = AutoProvider;

@ -0,0 +1,66 @@
/*
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file contract.js
* @authors:
* Marek Kotewicz <marek@ethdev.com>
* @date 2014
*/
// TODO: is these line is supposed to be here?
if (process.env.NODE_ENV !== 'build') {
var web3 = require('./web3'); // jshint ignore:line
}
var abi = require('./abi');
var contract = function (address, desc) {
var inputParser = abi.inputParser(desc);
var outputParser = abi.outputParser(desc);
var contract = {};
desc.forEach(function (method) {
contract[method.name] = function () {
var params = Array.prototype.slice.call(arguments);
var parsed = inputParser[method.name].apply(null, params);
var onSuccess = function (result) {
return outputParser[method.name](result);
};
return {
call: function (extra) {
extra = extra || {};
extra.to = address;
extra.data = parsed;
return web3.eth.call(extra).then(onSuccess);
},
transact: function (extra) {
extra = extra || {};
extra.to = address;
extra.data = parsed;
return web3.eth.transact(extra).then(onSuccess);
}
};
};
});
return contract;
};
if (typeof(module) !== "undefined")
module.exports = contract;

@ -0,0 +1,95 @@
/*
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file httprpc.js
* @authors:
* Marek Kotewicz <marek@ethdev.com>
* Marian Oancea <marian@ethdev.com>
* @date 2014
*/
// TODO: is these line is supposed to be here?
if (process.env.NODE_ENV !== 'build') {
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line
}
var HttpRpcProvider = function (host) {
this.handlers = [];
this.host = host;
};
function formatJsonRpcObject(object) {
return {
jsonrpc: '2.0',
method: object.call,
params: object.args,
id: object._id
};
}
function formatJsonRpcMessage(message) {
var object = JSON.parse(message);
return {
_id: object.id,
data: object.result,
error: object.error
};
}
HttpRpcProvider.prototype.sendRequest = function (payload, cb) {
var data = formatJsonRpcObject(payload);
var request = new XMLHttpRequest();
request.open("POST", this.host, true);
request.send(JSON.stringify(data));
request.onreadystatechange = function () {
if (request.readyState === 4 && cb) {
cb(request);
}
};
};
HttpRpcProvider.prototype.send = function (payload) {
var self = this;
this.sendRequest(payload, function (request) {
self.handlers.forEach(function (handler) {
handler.call(self, formatJsonRpcMessage(request.responseText));
});
});
};
HttpRpcProvider.prototype.poll = function (payload, id) {
var self = this;
this.sendRequest(payload, function (request) {
var parsed = JSON.parse(request.responseText);
if (parsed.error || (parsed.result instanceof Array ? parsed.result.length === 0 : !parsed.result)) {
return;
}
self.handlers.forEach(function (handler) {
handler.call(self, {_event: payload.call, _id: id, data: parsed.result});
});
});
};
Object.defineProperty(HttpRpcProvider.prototype, "onmessage", {
set: function (handler) {
this.handlers.push(handler);
}
});
if (typeof(module) !== "undefined")
module.exports = HttpRpcProvider;

@ -0,0 +1,46 @@
/*
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file qt.js
* @authors:
* Jeffrey Wilcke <jeff@ethdev.com>
* Marek Kotewicz <marek@ethdev.com>
* @date 2014
*/
var QtProvider = function() {
this.handlers = [];
var self = this;
navigator.qt.onmessage = function (message) {
self.handlers.forEach(function (handler) {
handler.call(self, JSON.parse(message.data));
});
};
};
QtProvider.prototype.send = function(payload) {
navigator.qt.postMessage(JSON.stringify(payload));
};
Object.defineProperty(QtProvider.prototype, "onmessage", {
set: function(handler) {
this.handlers.push(handler);
}
});
if (typeof(module) !== "undefined")
module.exports = QtProvider;

@ -0,0 +1,509 @@
/*
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file main.js
* @authors:
* Jeffrey Wilcke <jeff@ethdev.com>
* Marek Kotewicz <marek@ethdev.com>
* Marian Oancea <marian@ethdev.com>
* Gav Wood <g@ethdev.com>
* @date 2014
*/
function flattenPromise (obj) {
if (obj instanceof Promise) {
return Promise.resolve(obj);
}
if (obj instanceof Array) {
return new Promise(function (resolve) {
var promises = obj.map(function (o) {
return flattenPromise(o);
});
return Promise.all(promises).then(function (res) {
for (var i = 0; i < obj.length; i++) {
obj[i] = res[i];
}
resolve(obj);
});
});
}
if (obj instanceof Object) {
return new Promise(function (resolve) {
var keys = Object.keys(obj);
var promises = keys.map(function (key) {
return flattenPromise(obj[key]);
});
return Promise.all(promises).then(function (res) {
for (var i = 0; i < keys.length; i++) {
obj[keys[i]] = res[i];
}
resolve(obj);
});
});
}
return Promise.resolve(obj);
}
var web3Methods = function () {
return [
{ name: 'sha3', call: 'web3_sha3' }
];
};
var ethMethods = function () {
var blockCall = function (args) {
return typeof args[0] === "string" ? "eth_blockByHash" : "eth_blockByNumber";
};
var transactionCall = function (args) {
return typeof args[0] === "string" ? 'eth_transactionByHash' : 'eth_transactionByNumber';
};
var uncleCall = function (args) {
return typeof args[0] === "string" ? 'eth_uncleByHash' : 'eth_uncleByNumber';
};
var methods = [
{ name: 'balanceAt', call: 'eth_balanceAt' },
{ name: 'stateAt', call: 'eth_stateAt' },
{ name: 'storageAt', call: 'eth_storageAt' },
{ name: 'countAt', call: 'eth_countAt'},
{ name: 'codeAt', call: 'eth_codeAt' },
{ name: 'transact', call: 'eth_transact' },
{ name: 'call', call: 'eth_call' },
{ name: 'block', call: blockCall },
{ name: 'transaction', call: transactionCall },
{ name: 'uncle', call: uncleCall },
{ name: 'compilers', call: 'eth_compilers' },
{ name: 'lll', call: 'eth_lll' },
{ name: 'solidity', call: 'eth_solidity' },
{ name: 'serpent', call: 'eth_serpent' },
{ name: 'logs', call: 'eth_logs' }
];
return methods;
};
var ethProperties = function () {
return [
{ name: 'coinbase', getter: 'eth_coinbase', setter: 'eth_setCoinbase' },
{ name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' },
{ name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' },
{ name: 'gasPrice', getter: 'eth_gasPrice' },
{ name: 'account', getter: 'eth_account' },
{ name: 'accounts', getter: 'eth_accounts' },
{ name: 'peerCount', getter: 'eth_peerCount' },
{ name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' },
{ name: 'number', getter: 'eth_number'}
];
};
var dbMethods = function () {
return [
{ name: 'put', call: 'db_put' },
{ name: 'get', call: 'db_get' },
{ name: 'putString', call: 'db_putString' },
{ name: 'getString', call: 'db_getString' }
];
};
var shhMethods = function () {
return [
{ name: 'post', call: 'shh_post' },
{ name: 'newIdentity', call: 'shh_newIdentity' },
{ name: 'haveIdentity', call: 'shh_haveIdentity' },
{ name: 'newGroup', call: 'shh_newGroup' },
{ name: 'addToGroup', call: 'shh_addToGroup' }
];
};
var ethWatchMethods = function () {
var newFilter = function (args) {
return typeof args[0] === 'string' ? 'eth_newFilterString' : 'eth_newFilter';
};
return [
{ name: 'newFilter', call: newFilter },
{ name: 'uninstallFilter', call: 'eth_uninstallFilter' },
{ name: 'getMessages', call: 'eth_filterLogs' }
];
};
var shhWatchMethods = function () {
return [
{ name: 'newFilter', call: 'shh_newFilter' },
{ name: 'uninstallFilter', call: 'shh_uninstallFilter' },
{ name: 'getMessages', call: 'shh_getMessages' }
];
};
var setupMethods = function (obj, methods) {
methods.forEach(function (method) {
obj[method.name] = function () {
return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) {
var call = typeof method.call === "function" ? method.call(args) : method.call;
return {call: call, args: args};
}).then(function (request) {
return new Promise(function (resolve, reject) {
web3.provider.send(request, function (err, result) {
if (!err) {
resolve(result);
return;
}
reject(err);
});
});
}).catch(function(err) {
console.error(err);
});
};
});
};
var setupProperties = function (obj, properties) {
properties.forEach(function (property) {
var proto = {};
proto.get = function () {
return new Promise(function(resolve, reject) {
web3.provider.send({call: property.getter}, function(err, result) {
if (!err) {
resolve(result);
return;
}
reject(err);
});
});
};
if (property.setter) {
proto.set = function (val) {
return flattenPromise([val]).then(function (args) {
return new Promise(function (resolve) {
web3.provider.send({call: property.setter, args: args}, function (err, result) {
if (!err) {
resolve(result);
return;
}
reject(err);
});
});
}).catch(function (err) {
console.error(err);
});
};
}
Object.defineProperty(obj, property.name, proto);
});
};
// TODO: import from a dependency, don't duplicate.
var hexToDec = function (hex) {
return parseInt(hex, 16).toString();
};
var decToHex = function (dec) {
return parseInt(dec).toString(16);
};
var web3 = {
_callbacks: {},
_events: {},
providers: {},
toHex: function(str) {
var hex = "";
for(var i = 0; i < str.length; i++) {
var n = str.charCodeAt(i).toString(16);
hex += n.length < 2 ? '0' + n : n;
}
return hex;
},
toAscii: function(hex) {
// Find termination
var str = "";
var i = 0, l = hex.length;
if (hex.substring(0, 2) === '0x')
i = 2;
for(; i < l; i+=2) {
var code = hex.charCodeAt(i);
if(code === 0) {
break;
}
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
},
fromAscii: function(str, pad) {
pad = pad === undefined ? 32 : pad;
var hex = this.toHex(str);
while(hex.length < pad*2)
hex += "00";
return "0x" + hex;
},
toDecimal: function (val) {
return hexToDec(val.substring(2));
},
fromDecimal: function (val) {
return "0x" + decToHex(val);
},
toEth: function(str) {
var val = typeof str === "string" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str;
var unit = 0;
var units = [ 'wei', 'Kwei', 'Mwei', 'Gwei', 'szabo', 'finney', 'ether', 'grand', 'Mether', 'Gether', 'Tether', 'Pether', 'Eether', 'Zether', 'Yether', 'Nether', 'Dether', 'Vether', 'Uether' ];
while (val > 3000 && unit < units.length - 1)
{
val /= 1000;
unit++;
}
var s = val.toString().length < val.toFixed(2).length ? val.toString() : val.toFixed(2);
var replaceFunction = function($0, $1, $2) {
return $1 + ',' + $2;
};
while (true) {
var o = s;
s = s.replace(/(\d)(\d\d\d[\.\,])/, replaceFunction);
if (o === s)
break;
}
return s + ' ' + units[unit];
},
eth: {
prototype: Object(), // jshint ignore:line
watch: function (params) {
return new Filter(params, ethWatch);
}
},
db: {
prototype: Object() // jshint ignore:line
},
shh: {
prototype: Object(), // jshint ignore:line
watch: function (params) {
return new Filter(params, shhWatch);
}
},
on: function(event, id, cb) {
if(web3._events[event] === undefined) {
web3._events[event] = {};
}
web3._events[event][id] = cb;
return this;
},
off: function(event, id) {
if(web3._events[event] !== undefined) {
delete web3._events[event][id];
}
return this;
},
trigger: function(event, id, data) {
var callbacks = web3._events[event];
if (!callbacks || !callbacks[id]) {
return;
}
var cb = callbacks[id];
cb(data);
}
};
setupMethods(web3, web3Methods());
setupMethods(web3.eth, ethMethods());
setupProperties(web3.eth, ethProperties());
setupMethods(web3.db, dbMethods());
setupMethods(web3.shh, shhMethods());
var ethWatch = {
changed: 'eth_changed'
};
setupMethods(ethWatch, ethWatchMethods());
var shhWatch = {
changed: 'shh_changed'
};
setupMethods(shhWatch, shhWatchMethods());
var ProviderManager = function() {
this.queued = [];
this.polls = [];
this.ready = false;
this.provider = undefined;
this.id = 1;
var self = this;
var poll = function () {
if (self.provider && self.provider.poll) {
self.polls.forEach(function (data) {
data.data._id = self.id;
self.id++;
self.provider.poll(data.data, data.id);
});
}
setTimeout(poll, 12000);
};
poll();
};
ProviderManager.prototype.send = function(data, cb) {
data._id = this.id;
if (cb) {
web3._callbacks[data._id] = cb;
}
data.args = data.args || [];
this.id++;
if(this.provider !== undefined) {
this.provider.send(data);
} else {
console.warn("provider is not set");
this.queued.push(data);
}
};
ProviderManager.prototype.set = function(provider) {
if(this.provider !== undefined && this.provider.unload !== undefined) {
this.provider.unload();
}
this.provider = provider;
this.ready = true;
};
ProviderManager.prototype.sendQueued = function() {
for(var i = 0; this.queued.length; i++) {
// Resend
this.send(this.queued[i]);
}
};
ProviderManager.prototype.installed = function() {
return this.provider !== undefined;
};
ProviderManager.prototype.startPolling = function (data, pollId) {
if (!this.provider || !this.provider.poll) {
return;
}
this.polls.push({data: data, id: pollId});
};
ProviderManager.prototype.stopPolling = function (pollId) {
for (var i = this.polls.length; i--;) {
var poll = this.polls[i];
if (poll.id === pollId) {
this.polls.splice(i, 1);
}
}
};
web3.provider = new ProviderManager();
web3.setProvider = function(provider) {
provider.onmessage = messageHandler;
web3.provider.set(provider);
web3.provider.sendQueued();
};
web3.haveProvider = function() {
return !!web3.provider.provider;
};
var Filter = function(options, impl) {
this.impl = impl;
this.callbacks = [];
var self = this;
this.promise = impl.newFilter(options);
this.promise.then(function (id) {
self.id = id;
web3.on(impl.changed, id, self.trigger.bind(self));
web3.provider.startPolling({call: impl.changed, args: [id]}, id);
});
};
Filter.prototype.arrived = function(callback) {
this.changed(callback);
};
Filter.prototype.changed = function(callback) {
var self = this;
this.promise.then(function(id) {
self.callbacks.push(callback);
});
};
Filter.prototype.trigger = function(messages) {
for(var i = 0; i < this.callbacks.length; i++) {
this.callbacks[i].call(this, messages);
}
};
Filter.prototype.uninstall = function() {
var self = this;
this.promise.then(function (id) {
self.impl.uninstallFilter(id);
web3.provider.stopPolling(id);
web3.off(impl.changed, id);
});
};
Filter.prototype.messages = function() {
var self = this;
return this.promise.then(function (id) {
return self.impl.getMessages(id);
});
};
Filter.prototype.logs = function () {
return this.messages();
};
function messageHandler(data) {
if(data._event !== undefined) {
web3.trigger(data._event, data._id, data.data);
return;
}
if(data._id) {
var cb = web3._callbacks[data._id];
if (cb) {
cb.call(this, data.error, data.data);
delete web3._callbacks[data._id];
}
}
}
if (typeof(module) !== "undefined")
module.exports = web3;

@ -0,0 +1,78 @@
/*
This file is part of ethereum.js.
ethereum.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ethereum.js 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ethereum.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file websocket.js
* @authors:
* Jeffrey Wilcke <jeff@ethdev.com>
* Marek Kotewicz <marek@ethdev.com>
* Marian Oancea <marian@ethdev.com>
* @date 2014
*/
// TODO: is these line is supposed to be here?
if (process.env.NODE_ENV !== 'build') {
var WebSocket = require('ws'); // jshint ignore:line
}
var WebSocketProvider = function(host) {
// onmessage handlers
this.handlers = [];
// queue will be filled with messages if send is invoked before the ws is ready
this.queued = [];
this.ready = false;
this.ws = new WebSocket(host);
var self = this;
this.ws.onmessage = function(event) {
for(var i = 0; i < self.handlers.length; i++) {
self.handlers[i].call(self, JSON.parse(event.data), event);
}
};
this.ws.onopen = function() {
self.ready = true;
for(var i = 0; i < self.queued.length; i++) {
// Resend
self.send(self.queued[i]);
}
};
};
WebSocketProvider.prototype.send = function(payload) {
if(this.ready) {
var data = JSON.stringify(payload);
this.ws.send(data);
} else {
this.queued.push(payload);
}
};
WebSocketProvider.prototype.onMessage = function(handler) {
this.handlers.push(handler);
};
WebSocketProvider.prototype.unload = function() {
this.ws.close();
};
Object.defineProperty(WebSocketProvider.prototype, "onmessage", {
set: function(provider) { this.onMessage(provider); }
});
if (typeof(module) !== "undefined")
module.exports = WebSocketProvider;

@ -0,0 +1,67 @@
{
"name": "ethereum.js",
"namespace": "ethereum",
"version": "0.0.6",
"description": "Ethereum Compatible JavaScript API",
"main": "./index.js",
"directories": {
"lib": "./lib"
},
"dependencies": {
"es6-promise": "*",
"ws": "*",
"xmlhttprequest": "*"
},
"devDependencies": {
"bower": ">=1.3.0",
"browserify": ">=6.0",
"del": ">=0.1.1",
"envify": "^3.0.0",
"exorcist": "^0.1.6",
"gulp": ">=3.4.0",
"gulp-jshint": ">=1.5.0",
"gulp-rename": ">=1.2.0",
"gulp-uglify": ">=1.0.0",
"jshint": ">=2.5.0",
"uglifyify": "^2.6.0",
"unreachable-branch-transform": "^0.1.0",
"vinyl-source-stream": "^1.0.0"
},
"scripts": {
"build": "gulp",
"watch": "gulp watch",
"lint": "gulp lint"
},
"repository": {
"type": "git",
"url": "https://github.com/ethereum/ethereum.js.git"
},
"homepage": "https://github.com/ethereum/ethereum.js",
"bugs": {
"url": "https://github.com/ethereum/ethereum.js/issues"
},
"keywords": [
"ethereum",
"javascript",
"API"
],
"author": "ethdev.com",
"authors": [
{
"name": "Jeffery Wilcke",
"email": "jeff@ethdev.com",
"url": "https://github.com/obscuren"
},
{
"name": "Marek Kotewicz",
"email": "marek@ethdev.com",
"url": "https://github.com/debris"
},
{
"name": "Marian Oancea",
"email": "marian@ethdev.com",
"url": "https://github.com/cubedro"
}
],
"license": "LGPL-3.0"
}

@ -1,4 +1,4 @@
import QtQuick 2.0 import QtQuick 2.1
import QtWebKit 3.0 import QtWebKit 3.0
import QtWebKit.experimental 1.0 import QtWebKit.experimental 1.0
import QtQuick.Controls 1.0; import QtQuick.Controls 1.0;
@ -8,437 +8,484 @@ import QtQuick.Window 2.1;
import Ethereum 1.0 import Ethereum 1.0
Rectangle { Rectangle {
id: window id: window
property var title: "Browser" objectName: "browserView"
property var iconSource: "../browser.png" anchors.fill: parent
property var menuItem color: "#00000000"
property alias url: webview.url property var title: "Browser"
property alias webView: webview property var iconSource: "../browser.png"
property var menuItem
property var cleanPath: false
property var open: function(url) { property alias url: webview.url
if(!window.cleanPath) { property alias webView: webview
var uri = url;
if(!/.*\:\/\/.*/.test(uri)) { property var cleanPath: false
uri = "http://" + uri; property var open: function(url) {
} if(!window.cleanPath) {
var uri = url;
if(!/.*\:\/\/.*/.test(uri)) {
uri = "http://" + uri;
}
var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/ var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/
if(reg.test(uri)) { if(reg.test(uri)) {
uri.replace(reg, function(match, pre, domain, path) { uri.replace(reg, function(match, pre, domain, path) {
uri = pre; uri = pre;
var lookup = eth.lookupDomain(domain.substring(0, domain.length - 4)); var lookup = eth.lookupDomain(domain.substring(0, domain.length - 4));
var ip = []; var ip = [];
for(var i = 0, l = lookup.length; i < l; i++) { for(var i = 0, l = lookup.length; i < l; i++) {
ip.push(lookup.charCodeAt(i)) ip.push(lookup.charCodeAt(i))
} }
if(ip.length != 0) { if(ip.length != 0) {
uri += lookup; uri += lookup;
} else { } else {
uri += domain; uri += domain;
} }
uri += path; uri += path;
}); });
} }
window.cleanPath = true; window.cleanPath = true;
webview.url = uri;
//uriNav.text = uri.text.replace(/(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.\w{2,3})(.*)/, "$1$2<span style='color:#CCC'>$3</span>");
uriNav.text = uri;
} else {
// Prevent inf loop.
window.cleanPath = false;
}
}
Component.onCompleted: {
webview.url = "http://etherian.io"
}
signal messages(var messages, int id);
onMessages: {
// Bit of a cheat to get proper JSON
var m = JSON.parse(JSON.parse(JSON.stringify(messages)))
webview.postEvent("eth_changed", id, m);
}
function onShhMessage(message, id) {
webview.postEvent("shh_changed", id, message)
}
Item {
objectName: "root"
id: root
anchors.fill: parent
state: "inspectorShown"
RowLayout {
id: navBar
height: 40
anchors {
left: parent.left
right: parent.right
leftMargin: 7
}
webview.url = uri; Button {
id: back
onClicked: {
webview.goBack()
}
style: ButtonStyle {
background: Image {
source: "../back.png"
width: 30
height: 30
}
}
}
//uriNav.text = uri.text.replace(/(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.\w{2,3})(.*)/, "$1$2<span style='color:#CCC'>$3</span>"); TextField {
uriNav.text = uri; anchors {
} else { left: back.right
// Prevent inf loop. right: toggleInspector.left
window.cleanPath = false; leftMargin: 10
} rightMargin: 10
} }
text: webview.url;
Component.onCompleted: { id: uriNav
webview.url = "http://etherian.io" y: parent.height / 2 - this.height / 2
}
Keys.onReturnPressed: {
signal messages(var messages, int id); webview.url = this.text;
onMessages: { }
// Bit of a cheat to get proper JSON }
var m = JSON.parse(JSON.parse(JSON.stringify(messages)))
webview.postEvent("messages", id, m); Button {
} id: toggleInspector
anchors {
function onShhMessage(message, id) { right: parent.right
webview.postEvent("shhChanged", id, message) }
} iconSource: "../bug.png"
onClicked: {
Item { if(inspector.visible == true){
objectName: "root" inspector.visible = false
id: root }else{
anchors.fill: parent inspector.visible = true
state: "inspectorShown" inspector.url = webview.experimental.remoteInspectorUrl
}
RowLayout { }
id: navBar }
height: 40 }
// Border
Rectangle {
id: divider
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
leftMargin: 7 top: navBar.bottom
}
Button {
id: back
onClicked: {
webview.goBack()
}
style: ButtonStyle {
background: Image {
source: "../back.png"
width: 30
height: 30
}
}
} }
z: -1
height: 1
color: "#CCCCCC"
}
TextField { WebView {
anchors { objectName: "webView"
left: back.right id: webview
right: toggleInspector.left anchors {
leftMargin: 5 left: parent.left
rightMargin: 5 right: parent.right
} bottom: parent.bottom
text: "http://etherian.io" top: divider.bottom
id: uriNav }
y: parent.height / 2 - this.height / 2
Keys.onReturnPressed: {
webview.url = this.text;
}
}
Button { function injectJs(js) {
id: toggleInspector webview.experimental.navigatorQtObjectEnabled = true;
anchors { webview.experimental.evaluateJavaScript(js)
right: parent.right webview.experimental.javascriptEnabled = true;
} }
iconSource: "../bug.png"
onClicked: {
if(inspector.visible == true){
inspector.visible = false
}else{
inspector.visible = true
inspector.url = webview.experimental.remoteInspectorUrl
}
}
}
}
function sendMessage(data) {
webview.experimental.postMessage(JSON.stringify(data))
}
WebView {
objectName: "webView"
id: webview
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
top: navBar.bottom
}
//property var cleanPath: false experimental.preferences.javascriptEnabled: true
onNavigationRequested: { experimental.preferences.webGLEnabled: true
window.open(request.url.toString()); experimental.itemSelector: MouseArea {
} // To avoid conflicting with ListView.model when inside Initiator context.
property QtObject selectorModel: model
anchors.fill: parent
onClicked: selectorModel.reject()
Menu {
visible: true
id: itemSelector
Instantiator {
model: selectorModel.items
delegate: MenuItem {
text: model.text
onTriggered: {
selectorModel.accept(index)
}
}
onObjectAdded: itemSelector.insertItem(index, object)
onObjectRemoved: itemSelector.removeItem(object)
}
}
Component.onCompleted: {
itemSelector.popup()
}
}
experimental.preferences.webAudioEnabled: true
experimental.preferences.navigatorQtObjectEnabled: true
experimental.preferences.developerExtrasEnabled: true
experimental.userScripts: ["../ext/q.js", "../ext/ethereum.js/lib/web3.js", "../ext/ethereum.js/lib/qt.js", "../ext/setup.js"]
experimental.onMessageReceived: {
console.log("[onMessageReceived]: ", message.data)
// TODO move to messaging.js
var data = JSON.parse(message.data)
function sendMessage(data) { try {
webview.experimental.postMessage(JSON.stringify(data)) switch(data.call) {
} case "eth_compile":
postData(data._id, eth.compile(data.args[0]))
break
case "eth_coinbase":
postData(data._id, eth.coinBase())
experimental.preferences.javascriptEnabled: true case "eth_account":
experimental.preferences.navigatorQtObjectEnabled: true postData(data._id, eth.key().address);
experimental.preferences.developerExtrasEnabled: true
//experimental.userScripts: ["../ext/qt_messaging_adapter.js", "../ext/q.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"]
experimental.userScripts: ["../ext/q.js", "../ext/eth.js/main.js", "../ext/eth.js/qt.js", "../ext/setup.js"]
experimental.onMessageReceived: {
console.log("[onMessageReceived]: ", message.data)
// TODO move to messaging.js
var data = JSON.parse(message.data)
try { case "eth_istening":
switch(data.call) { postData(data._id, eth.isListening())
case "compile":
postData(data._id, eth.compile(data.args[0]))
break
case "coinbase": break
postData(data._id, eth.coinBase())
case "account": case "eth_mining":
postData(data._id, eth.key().address); postData(data._id, eth.isMining())
case "isListening": break
postData(data._id, eth.isListening())
break case "eth_peerCount":
postData(data._id, eth.peerCount())
case "isMining": break
postData(data._id, eth.isMining())
break case "eth_countAt":
require(1)
postData(data._id, eth.txCountAt(data.args[0]))
case "peerCount": break
postData(data._id, eth.peerCount())
break case "eth_codeAt":
require(1)
var code = eth.codeAt(data.args[0])
postData(data._id, code);
case "countAt": break
require(1)
postData(data._id, eth.txCountAt(data.args[0]))
break case "eth_blockByNumber":
require(1)
var block = eth.blockByNumber(data.args[0])
postData(data._id, block)
break
case "codeAt": case "eth_blockByHash":
require(1) require(1)
var code = eth.codeAt(data.args[0]) var block = eth.blockByHash(data.args[0])
postData(data._id, code); postData(data._id, block)
break
break require(2)
var block = eth.blockByHash(data.args[0])
postData(data._id, block.transactions[data.args[1]])
break
case "blockByNumber": case "eth_transactionByHash":
require(1) case "eth_transactionByNumber":
var block = eth.blockByNumber(data.args[0]) require(2)
postData(data._id, block)
break
case "blockByHash": var block;
require(1) if (data.call === "transactionByHash")
var block = eth.blockByHash(data.args[0]) block = eth.blockByHash(data.args[0])
postData(data._id, block) else
break block = eth.blockByNumber(data.args[0])
require(2) var tx = block.transactions.get(data.args[1])
var block = eth.blockByHash(data.args[0])
postData(data._id, block.transactions[data.args[1]])
break
case "transactionByHash": postData(data._id, tx)
case "transactionByNumber": break
require(2)
var block; case "eth_uncleByHash":
if (data.call === "transactionByHash") case "eth_uncleByNumber":
block = eth.blockByHash(data.args[0]) require(2)
else
block = eth.blockByNumber(data.args[0])
var tx = block.transactions.get(data.args[1]) var block;
if (data.call === "uncleByHash")
block = eth.blockByHash(data.args[0])
else
block = eth.blockByNumber(data.args[0])
postData(data._id, tx) var uncle = block.uncles.get(data.args[1])
break
case "uncleByHash": postData(data._id, uncle)
case "uncleByNumber":
require(2)
var block; break
if (data.call === "uncleByHash")
block = eth.blockByHash(data.args[0])
else
block = eth.blockByNumber(data.args[0])
var uncle = block.uncles.get(data.args[1]) case "transact":
require(5)
postData(data._id, uncle) var tx = eth.transact(data.args)
postData(data._id, tx)
break break
case "transact": case "eth_stateAt":
require(5) require(2);
var tx = eth.transact(data.args) var storage = eth.storageAt(data.args[0], data.args[1]);
postData(data._id, tx) postData(data._id, storage)
break break
case "stateAt": case "eth_call":
require(2); require(1);
var ret = eth.call(data.args)
postData(data._id, ret)
break
var storage = eth.storageAt(data.args[0], data.args[1]); case "eth_balanceAt":
postData(data._id, storage) require(1);
break postData(data._id, eth.balanceAt(data.args[0]));
break
case "call": case "eth_watch":
require(1); require(2)
var ret = eth.call(data.args) eth.watch(data.args[0], data.args[1])
postData(data._id, ret)
break
case "balanceAt": case "eth_disconnect":
require(1); require(1)
postData(data._id, null)
break;
postData(data._id, eth.balanceAt(data.args[0])); case "eth_newFilterString":
break require(1)
var id = eth.newFilterString(data.args[0])
postData(data._id, id);
break;
case "watch": case "eth_newFilter":
require(2) require(1)
eth.watch(data.args[0], data.args[1]) var id = eth.newFilter(data.args[0])
case "disconnect": postData(data._id, id);
require(1) break;
postData(data._id, null)
break;
case "messages": case "eth_filterLogs":
require(1); require(1);
var messages = JSON.parse(eth.getMessages(data.args[0])) var messages = eth.messages(data.args[0]);
postData(data._id, messages) var m = JSON.parse(JSON.parse(JSON.stringify(messages)))
break postData(data._id, m);
case "mutan": break;
require(1)
var code = eth.compileMutan(data.args[0]) case "eth_deleteFilter":
postData(data._id, "0x"+code) require(1);
break; eth.uninstallFilter(data.args[0])
break;
case "newFilterString":
require(1)
var id = eth.newFilterString(data.args[0])
postData(data._id, id);
break;
case "newFilter": case "shh_newFilter":
require(1) require(1);
var id = eth.newFilter(data.args[0]) var id = shh.watch(data.args[0], window);
postData(data._id, id);
break;
postData(data._id, id); case "shh_newIdentity":
break; var id = shh.newIdentity()
postData(data._id, id)
case "getMessages": break
require(1);
var messages = eth.messages(data.args[0]); case "shh_post":
var m = JSON.parse(JSON.parse(JSON.stringify(messages))) require(1);
postData(data._id, m);
break; var params = data.args[0];
var fields = ["payload", "to", "from"];
for(var i = 0; i < fields.length; i++) {
params[fields[i]] = params[fields[i]] || "";
}
if(typeof params.payload !== "object") { params.payload = [params.payload]; } //params.payload = params.payload.join(""); }
params.topics = params.topics || [];
params.priority = params.priority || 1000;
params.ttl = params.ttl || 100;
case "deleteFilter": shh.post(params.payload, params.to, params.from, params.topics, params.priority, params.ttl);
require(1);
eth.uninstallFilter(data.args[0])
break;
break;
case "shhNewFilter": case "shh_getMessages":
require(1); require(1);
var id = shh.watch(data.args[0], window);
postData(data._id, id);
break;
case "newIdentity": var m = shh.messages(data.args[0]);
postData(data._id, shh.newIdentity()) var messages = JSON.parse(JSON.parse(JSON.stringify(m)));
break postData(data._id, messages);
case "post": break;
require(1);
var params = data.args[0];
var fields = ["payload", "to", "from"];
for(var i = 0; i < fields.length; i++) {
params[fields[i]] = params[fields[i]] || "";
}
if(typeof params.payload !== "object") { params.payload = [params.payload]; } //params.payload = params.payload.join(""); }
params.topics = params.topics || [];
params.priority = params.priority || 1000;
params.ttl = params.ttl || 100;
console.log(JSON.stringify(params))
shh.post(params.payload, params.to, params.from, params.topics, params.priority, params.ttl);
break;
}
} catch(e) {
console.log(data.call + ": " + e)
postData(data._id, null);
}
}
case "ssh_newGroup":
postData(data._id, "");
break;
}
} catch(e) {
console.log(data.call + ": " + e)
function post(seed, data) { postData(data._id, null);
postData(data._id, data) }
} }
function require(args, num) {
if(args.length < num) {
throw("required argument count of "+num+" got "+args.length);
}
}
function postData(seed, data) {
webview.experimental.postMessage(JSON.stringify({data: data, _id: seed}))
}
function postEvent(event, id, data) {
webview.experimental.postMessage(JSON.stringify({data: data, _id: id, _event: event}))
}
function onWatchedCb(data, id) { function post(seed, data) {
var messages = JSON.parse(data) postData(data._id, data)
postEvent("watched:"+id, messages) }
}
function onNewBlockCb(block) { function require(args, num) {
postEvent("block:new", block) if(args.length < num) {
} throw("required argument count of "+num+" got "+args.length);
function onObjectChangeCb(stateObject) { }
postEvent("object:"+stateObject.address(), stateObject) }
} function postData(seed, data) {
function onStorageChangeCb(storageObject) { webview.experimental.postMessage(JSON.stringify({data: data, _id: seed}))
var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":"); }
postEvent(ev, [storageObject.address, storageObject.value]) function postEvent(event, id, data) {
} webview.experimental.postMessage(JSON.stringify({data: data, _id: id, _event: event}))
} }
function onWatchedCb(data, id) {
var messages = JSON.parse(data)
postEvent("watched:"+id, messages)
}
Rectangle { function onNewBlockCb(block) {
id: sizeGrip postEvent("block:new", block)
color: "gray" }
visible: false function onObjectChangeCb(stateObject) {
height: 10 postEvent("object:"+stateObject.address(), stateObject)
anchors { }
left: root.left function onStorageChangeCb(storageObject) {
right: root.right var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":");
} postEvent(ev, [storageObject.address, storageObject.value])
y: Math.round(root.height * 2 / 3) }
}
MouseArea {
anchors.fill: parent
drag.target: sizeGrip
drag.minimumY: 0
drag.maximumY: root.height
drag.axis: Drag.YAxis
}
}
WebView {
id: inspector
visible: false
anchors {
left: root.left
right: root.right
top: sizeGrip.bottom
bottom: root.bottom
}
}
states: [ Rectangle {
State { id: sizeGrip
name: "inspectorShown" color: "gray"
PropertyChanges { visible: false
target: inspector height: 10
} anchors {
} left: root.left
] right: root.right
} }
y: Math.round(root.height * 2 / 3)
MouseArea {
anchors.fill: parent
drag.target: sizeGrip
drag.minimumY: 0
drag.maximumY: root.height
drag.axis: Drag.YAxis
}
}
WebView {
id: inspector
visible: false
anchors {
left: root.left
right: root.right
top: sizeGrip.bottom
bottom: root.bottom
}
}
states: [
State {
name: "inspectorShown"
PropertyChanges {
target: inspector
}
}
]
}
} }

@ -59,8 +59,19 @@ ApplicationWindow {
mainSplit.setView(wallet.view, wallet.menuItem); mainSplit.setView(wallet.view, wallet.menuItem);
// Call the ready handler // Command setup
gui.done(); gui.sendCommand(0)
}
function activeView(view, menuItem) {
mainSplit.setView(view, menuItem)
if (view.objectName === "browserView") {
urlPane.visible = false;
mainView.anchors.top = rootView.top
} else {
urlPane.visible = true;
mainView.anchors.top = divider.bottom
}
} }
function addViews(view, path, options) { function addViews(view, path, options) {
@ -284,6 +295,7 @@ ApplicationWindow {
} }
ProgressBar { ProgressBar {
visible: false
id: downloadIndicator id: downloadIndicator
value: 0 value: 0
objectName: "downloadIndicator" objectName: "downloadIndicator"
@ -293,6 +305,7 @@ ApplicationWindow {
} }
Label { Label {
visible: false
objectName: "downloadLabel" objectName: "downloadLabel"
//y: 7 //y: 7
anchors.left: downloadIndicator.right anchors.left: downloadIndicator.right
@ -445,7 +458,7 @@ ApplicationWindow {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
mainSplit.setView(view, menuItem) activeView(view, menuItem);
} }
} }
@ -512,14 +525,14 @@ ApplicationWindow {
var section; var section;
switch(options.section) { switch(options.section) {
case "ethereum": case "ethereum":
section = menuDefault; section = menuDefault;
break; break;
case "legacy": case "legacy":
section = menuLegacy; section = menuLegacy;
break; break;
default: default:
section = menuApps; section = menuApps;
break; break;
} }
var comp = menuItemTemplate.createObject(section) var comp = menuItemTemplate.createObject(section)
@ -606,6 +619,7 @@ ApplicationWindow {
* Main view * Main view
********************/ ********************/
Rectangle { Rectangle {
id: rootView
anchors.right: parent.right anchors.right: parent.right
anchors.left: menu.right anchors.left: menu.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@ -639,8 +653,7 @@ ApplicationWindow {
Keys.onReturnPressed: { Keys.onReturnPressed: {
if(/^https?/.test(this.text)) { if(/^https?/.test(this.text)) {
root.browser.view.open(this.text); activeView(root.browser.view, root.browser.menuItem);
mainSplit.setView(root.browser.view, root.browser.menuItem);
} else { } else {
addPlugin(this.text, {close: true, section: "apps"}) addPlugin(this.text, {close: true, section: "apps"})
} }
@ -864,13 +877,13 @@ ApplicationWindow {
Component.onCompleted: { Component.onCompleted: {
pastPeers.insert(0, {text: "poc-8.ethdev.com:30303"}) pastPeers.insert(0, {text: "poc-8.ethdev.com:30303"})
/* /*
var ips = eth.pastPeers() var ips = eth.pastPeers()
for(var i = 0; i < ips.length; i++) { for(var i = 0; i < ips.length; i++) {
pastPeers.append({text: ips.get(i)}) pastPeers.append({text: ips.get(i)})
} }
pastPeers.insert(0, {text: "poc-7.ethdev.com:30303"}) pastPeers.insert(0, {text: "poc-7.ethdev.com:30303"})
*/ */
} }
} }

@ -46,6 +46,7 @@ Rectangle {
text: "Start" text: "Start"
onClicked: { onClicked: {
eth.setGasPrice(minGasPrice.text || "10000000000000"); eth.setGasPrice(minGasPrice.text || "10000000000000");
eth.setExtra(blockExtra.text)
if (eth.toggleMining()) { if (eth.toggleMining()) {
this.text = "Stop"; this.text = "Stop";
} else { } else {
@ -55,6 +56,7 @@ Rectangle {
} }
Rectangle { Rectangle {
id: minGasPriceRect
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 2 anchors.topMargin: 2
width: 200 width: 200
@ -65,6 +67,23 @@ Rectangle {
validator: RegExpValidator { regExp: /\d*/ } validator: RegExpValidator { regExp: /\d*/ }
} }
} }
Rectangle {
width: 300
anchors {
left: minGasPriceRect.right
leftMargin: 5
top: parent.top
topMargin: 2
}
TextField {
id: blockExtra
placeholderText: "Extra"
width: parent.width
maximumLength: 1024
}
}
} }
} }

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of go-ethereum is distributed in the hope that it will be useful,
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU but WITHOUT ANY WARRANTY; without even the implied warranty of
// General Public License for more details. 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 this library; if not, write to the Free Software You should have received a copy of the GNU General Public License
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// MA 02110-1301 USA */
/**
* @authors
* Jeffrey Wilcke <i@jev.io>
*/
package main package main
import ( import (
@ -26,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state"
) )
type plugin struct { type plugin struct {
@ -118,7 +122,7 @@ func (self *Gui) DumpState(hash, path string) {
return return
} }
stateDump = block.State().Dump() stateDump = state.New(block.Root(), self.eth.Db()).Dump()
} }
file, err := os.OpenFile(path[7:], os.O_CREATE|os.O_RDWR, os.ModePerm) file, err := os.OpenFile(path[7:], os.O_CREATE|os.O_RDWR, os.ModePerm)

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of go-ethereum is distributed in the hope that it will be useful,
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU but WITHOUT ANY WARRANTY; without even the implied warranty of
// General Public License for more details. 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 this library; if not, write to the Free Software You should have received a copy of the GNU General Public License
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// MA 02110-1301 USA */
/**
* @authors
* Jeffrey Wilcke <i@jev.io>
*/
package main package main
import ( import (
@ -37,7 +40,7 @@ type DebuggerWindow struct {
engine *qml.Engine engine *qml.Engine
lib *UiLib lib *UiLib
vm *vm.DebugVm vm *vm.Vm
Db *Debugger Db *Debugger
state *state.StateDB state *state.StateDB
@ -54,7 +57,7 @@ func NewDebuggerWindow(lib *UiLib) *DebuggerWindow {
win := component.CreateWindow(nil) win := component.CreateWindow(nil)
w := &DebuggerWindow{engine: engine, win: win, lib: lib, vm: &vm.DebugVm{}} w := &DebuggerWindow{engine: engine, win: win, lib: lib, vm: &vm.Vm{}}
w.Db = NewDebugger(w) w.Db = NewDebugger(w)
return w return w
@ -264,6 +267,9 @@ type storeVal struct {
Key, Value string Key, Value string
} }
func (self *Debugger) Step(evm vm.VirtualMachine, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, context *vm.Context) {
}
func (self *Debugger) BreakHook(pc int, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, stateObject *state.StateObject) bool { func (self *Debugger) BreakHook(pc int, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, stateObject *state.StateObject) bool {
self.main.Logln("break on instr:", pc) self.main.Logln("break on instr:", pc)

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library 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 2.1 of the License, or (at your option) any later version.
//
// This library 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 this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA
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>
*/
package main package main
import ( import (

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of go-ethereum is distributed in the hope that it will be useful,
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU but WITHOUT ANY WARRANTY; without even the implied warranty of
// General Public License for more details. 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 this library; if not, write to the Free Software You should have received a copy of the GNU General Public License
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// MA 02110-1301 USA */
/**
* @authors
* Jeffrey Wilcke <i@jev.io>
*/
package main package main
import ( import (

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library 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 2.1 of the License, or (at your option) any later version.
//
// This library 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 this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA
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>
*/
package main package main
import ( import (
@ -89,7 +92,7 @@ func defaultAssetPath() string {
} }
func defaultDataDir() string { func defaultDataDir() string {
usr, _ := user.Current() usr, _ := user.Current()
return path.Join(usr.HomeDir, ".mist") return path.Join(usr.HomeDir, ".ethereum")
} }
var defaultConfigFile = path.Join(defaultDataDir(), "conf.ini") var defaultConfigFile = path.Join(defaultDataDir(), "conf.ini")

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library 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 this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA
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>
*/
package main package main
import "C" import "C"
@ -23,7 +26,9 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"math/big" "math/big"
"os"
"path" "path"
"runtime" "runtime"
"strconv" "strconv"
@ -45,15 +50,22 @@ import (
var guilogger = logger.NewLogger("GUI") var guilogger = logger.NewLogger("GUI")
type ServEv byte
const (
setup ServEv = iota
update
)
type Gui struct { type Gui struct {
// The main application window // The main application window
win *qml.Window win *qml.Window
// QML Engine // QML Engine
engine *qml.Engine engine *qml.Engine
component *qml.Common component *qml.Common
qmlDone bool
// The ethereum interface // The ethereum interface
eth *eth.Ethereum eth *eth.Ethereum
serviceEvents chan ServEv
// The public Ethereum library // The public Ethereum library
uiLib *UiLib uiLib *UiLib
@ -83,7 +95,17 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, clientIden
} }
xeth := xeth.NewJSXEth(ethereum) xeth := xeth.NewJSXEth(ethereum)
gui := &Gui{eth: ethereum, txDb: db, xeth: xeth, logLevel: logger.LogLevel(logLevel), Session: session, open: false, clientIdentity: clientIdentity, config: config, plugins: make(map[string]plugin)} gui := &Gui{eth: ethereum,
txDb: db,
xeth: xeth,
logLevel: logger.LogLevel(logLevel),
Session: session,
open: false,
clientIdentity: clientIdentity,
config: config,
plugins: make(map[string]plugin),
serviceEvents: make(chan ServEv, 1),
}
data, _ := ethutil.ReadAllFile(path.Join(ethutil.Config.ExecPath, "plugins.json")) data, _ := ethutil.ReadAllFile(path.Join(ethutil.Config.ExecPath, "plugins.json"))
json.Unmarshal([]byte(data), &gui.plugins) json.Unmarshal([]byte(data), &gui.plugins)
@ -95,6 +117,8 @@ func (gui *Gui) Start(assetPath string) {
guilogger.Infoln("Starting GUI") guilogger.Infoln("Starting GUI")
go gui.service()
// Register ethereum functions // Register ethereum functions
qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{
Init: func(p *xeth.JSBlock, obj qml.Object) { p.Number = 0; p.Hash = "" }, Init: func(p *xeth.JSBlock, obj qml.Object) { p.Number = 0; p.Hash = "" },
@ -114,18 +138,7 @@ func (gui *Gui) Start(assetPath string) {
context.SetVar("eth", gui.uiLib) context.SetVar("eth", gui.uiLib)
context.SetVar("shh", gui.whisper) context.SetVar("shh", gui.whisper)
// Load the main QML interface win, err := gui.showWallet(context)
data, _ := ethutil.Config.Db.Get([]byte("KeyRing"))
var win *qml.Window
var err error
var addlog = false
if len(data) == 0 {
win, err = gui.showKeyImport(context)
} else {
win, err = gui.showWallet(context)
addlog = true
}
if err != nil { if err != nil {
guilogger.Errorln("asset not found: you can set an alternative asset path on the command line using option 'asset_path'", err) guilogger.Errorln("asset not found: you can set an alternative asset path on the command line using option 'asset_path'", err)
@ -136,9 +149,7 @@ func (gui *Gui) Start(assetPath string) {
win.Show() win.Show()
// only add the gui guilogger after window is shown otherwise slider wont be shown // only add the gui guilogger after window is shown otherwise slider wont be shown
if addlog { logger.AddLogSystem(gui)
logger.AddLogSystem(gui)
}
win.Wait() win.Wait()
// need to silence gui guilogger after window closed otherwise logsystem hangs (but do not save loglevel) // need to silence gui guilogger after window closed otherwise logsystem hangs (but do not save loglevel)
@ -164,18 +175,11 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
return nil, err return nil, err
} }
gui.win = gui.createWindow(component) gui.createWindow(component)
gui.update()
return gui.win, nil return gui.win, nil
} }
// The done handler will be called by QML when all views have been loaded
func (gui *Gui) Done() {
gui.qmlDone = true
}
func (gui *Gui) ImportKey(filePath string) { func (gui *Gui) ImportKey(filePath string) {
} }
@ -189,10 +193,8 @@ func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
} }
func (gui *Gui) createWindow(comp qml.Object) *qml.Window { func (gui *Gui) createWindow(comp qml.Object) *qml.Window {
win := comp.CreateWindow(nil) gui.win = comp.CreateWindow(nil)
gui.uiLib.win = gui.win
gui.win = win
gui.uiLib.win = win
return gui.win return gui.win
} }
@ -272,7 +274,7 @@ func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
} }
var ( var (
ptx = xeth.NewJSTx(tx, gui.xeth.World().State()) ptx = xeth.NewJSTx(tx)
send = nameReg.Storage(tx.From()) send = nameReg.Storage(tx.From())
rec = nameReg.Storage(tx.To()) rec = nameReg.Storage(tx.To())
s, r string s, r string
@ -345,11 +347,48 @@ func (self *Gui) getObjectByName(objectName string) qml.Object {
return self.win.Root().ObjectByName(objectName) return self.win.Root().ObjectByName(objectName)
} }
// Simple go routine function that updates the list of peers in the GUI func loadJavascriptAssets(gui *Gui) (jsfiles string) {
func (gui *Gui) update() { for _, fn := range []string{"ext/q.js", "ext/eth.js/main.js", "ext/eth.js/qt.js", "ext/setup.js"} {
// We have to wait for qml to be done loading all the windows. f, err := os.Open(gui.uiLib.AssetPath(fn))
for !gui.qmlDone { if err != nil {
time.Sleep(300 * time.Millisecond) 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, "")
} }
go func() { go func() {
@ -359,14 +398,21 @@ func (gui *Gui) update() {
gui.setPeerInfo() gui.setPeerInfo()
}() }()
gui.whisper.SetView(gui.win.Root().ObjectByName("whisperView")) // Inject javascript files each time navigation is requested.
// Unfortunately webview.experimental.userScripts injects _after_
// the page has loaded which kind of renders it useless...
//jsfiles := loadJavascriptAssets(gui)
gui.getObjectByName("webView").On("navigationRequested", func() {
//gui.getObjectByName("webView").Call("injectJs", jsfiles)
})
for _, plugin := range gui.plugins { gui.whisper.SetView(gui.getObjectByName("whisperView"))
guilogger.Infoln("Loading plugin ", plugin.Name)
gui.win.Root().Call("addPlugin", plugin.Path, "") gui.SendCommand(update)
} }
// Simple go routine function that updates the list of peers in the GUI
func (gui *Gui) update() {
peerUpdateTicker := time.NewTicker(5 * time.Second) peerUpdateTicker := time.NewTicker(5 * time.Second)
generalUpdateTicker := time.NewTicker(500 * time.Millisecond) generalUpdateTicker := time.NewTicker(500 * time.Millisecond)
statsUpdateTicker := time.NewTicker(5 * time.Second) statsUpdateTicker := time.NewTicker(5 * time.Second)
@ -385,77 +431,75 @@ func (gui *Gui) update() {
core.TxPostEvent{}, core.TxPostEvent{},
) )
go func() { defer events.Unsubscribe()
defer events.Unsubscribe() for {
for { select {
select { case ev, isopen := <-events.Chan():
case ev, isopen := <-events.Chan(): if !isopen {
if !isopen { return
return }
switch ev := ev.(type) {
case core.NewBlockEvent:
gui.processBlock(ev.Block, false)
if bytes.Compare(ev.Block.Coinbase(), gui.address()) == 0 {
gui.setWalletValue(gui.eth.ChainManager().State().GetBalance(gui.address()), nil)
} }
switch ev := ev.(type) {
case core.NewBlockEvent:
gui.processBlock(ev.Block, false)
if bytes.Compare(ev.Block.Coinbase(), gui.address()) == 0 {
gui.setWalletValue(gui.eth.ChainManager().State().GetBalance(gui.address()), nil)
}
case core.TxPreEvent:
tx := ev.Tx
tstate := gui.eth.ChainManager().TransState() case core.TxPreEvent:
cstate := gui.eth.ChainManager().State() tx := ev.Tx
taccount := tstate.GetAccount(gui.address()) tstate := gui.eth.ChainManager().TransState()
caccount := cstate.GetAccount(gui.address()) cstate := gui.eth.ChainManager().State()
unconfirmedFunds := new(big.Int).Sub(taccount.Balance(), caccount.Balance())
gui.setWalletValue(taccount.Balance(), unconfirmedFunds) taccount := tstate.GetAccount(gui.address())
gui.insertTransaction("pre", tx) caccount := cstate.GetAccount(gui.address())
unconfirmedFunds := new(big.Int).Sub(taccount.Balance(), caccount.Balance())
case core.TxPostEvent: gui.setWalletValue(taccount.Balance(), unconfirmedFunds)
tx := ev.Tx gui.insertTransaction("pre", tx)
object := state.GetAccount(gui.address())
if bytes.Compare(tx.From(), gui.address()) == 0 { case core.TxPostEvent:
object.SubAmount(tx.Value()) tx := ev.Tx
object := state.GetAccount(gui.address())
gui.txDb.Put(tx.Hash(), tx.RlpEncode()) if bytes.Compare(tx.From(), gui.address()) == 0 {
} else if bytes.Compare(tx.To(), gui.address()) == 0 { object.SubAmount(tx.Value())
object.AddAmount(tx.Value())
gui.txDb.Put(tx.Hash(), tx.RlpEncode()) gui.txDb.Put(tx.Hash(), tx.RlpEncode())
} } else if bytes.Compare(tx.To(), gui.address()) == 0 {
object.AddAmount(tx.Value())
gui.setWalletValue(object.Balance(), nil) gui.txDb.Put(tx.Hash(), tx.RlpEncode())
state.UpdateStateObject(object)
} }
case <-peerUpdateTicker.C: gui.setWalletValue(object.Balance(), nil)
gui.setPeerInfo() state.UpdateStateObject(object)
case <-generalUpdateTicker.C:
statusText := "#" + gui.eth.ChainManager().CurrentBlock().Number().String()
lastBlockLabel.Set("text", statusText)
miningLabel.Set("text", "Mining @ "+strconv.FormatInt(gui.uiLib.miner.GetPow().GetHashrate(), 10)+"Khash")
/*
blockLength := gui.eth.BlockPool().BlocksProcessed
chainLength := gui.eth.BlockPool().ChainLength
var (
pct float64 = 1.0 / float64(chainLength) * float64(blockLength)
dlWidget = gui.win.Root().ObjectByName("downloadIndicator")
dlLabel = gui.win.Root().ObjectByName("downloadLabel")
)
dlWidget.Set("value", pct)
dlLabel.Set("text", fmt.Sprintf("%d / %d", blockLength, chainLength))
*/
case <-statsUpdateTicker.C:
gui.setStatsPane()
} }
case <-peerUpdateTicker.C:
gui.setPeerInfo()
case <-generalUpdateTicker.C:
statusText := "#" + gui.eth.ChainManager().CurrentBlock().Number().String()
lastBlockLabel.Set("text", statusText)
miningLabel.Set("text", "Mining @ "+strconv.FormatInt(gui.uiLib.miner.GetPow().GetHashrate(), 10)+"Khash")
/*
blockLength := gui.eth.BlockPool().BlocksProcessed
chainLength := gui.eth.BlockPool().ChainLength
var (
pct float64 = 1.0 / float64(chainLength) * float64(blockLength)
dlWidget = gui.win.Root().ObjectByName("downloadIndicator")
dlLabel = gui.win.Root().ObjectByName("downloadLabel")
)
dlWidget.Set("value", pct)
dlLabel.Set("text", fmt.Sprintf("%d / %d", blockLength, chainLength))
*/
case <-statsUpdateTicker.C:
gui.setStatsPane()
} }
}() }
} }
func (gui *Gui) setStatsPane() { func (gui *Gui) setStatsPane() {

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of go-ethereum is distributed in the hope that it will be useful,
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU but WITHOUT ANY WARRANTY; without even the implied warranty of
// General Public License for more details. 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 this library; if not, write to the Free Software You should have received a copy of the GNU General Public License
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// MA 02110-1301 USA */
/**
* @authors
* Jeffrey Wilcke <i@jev.io>
*/
package main package main
import ( import (

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of go-ethereum is distributed in the hope that it will be useful,
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU but WITHOUT ANY WARRANTY; without even the implied warranty of
// General Public License for more details. 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 this library; if not, write to the Free Software You should have received a copy of the GNU General Public License
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// MA 02110-1301 USA */
/**
* @authors
* Jeffrey Wilcke <i@jev.io>
*/
package main package main
import ( import (

@ -1,19 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of go-ethereum is distributed in the hope that it will be useful,
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU but WITHOUT ANY WARRANTY; without even the implied warranty of
// General Public License for more details. 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 this library; if not, write to the Free Software You should have received a copy of the GNU General Public License
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// MA 02110-1301 USA */
/**
* @authors
* Jeffrey Wilcke <i@jev.io>
*/
package main package main

@ -1,20 +1,23 @@
// Copyright (c) 2013-2014, Jeffrey Wilcke. All rights reserved. /*
// This file is part of go-ethereum
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public go-ethereum is free software: you can redistribute it and/or modify
// License as published by the Free Software Foundation; either it under the terms of the GNU General Public License as published by
// version 2.1 of the License, or (at your option) any later version. the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of go-ethereum is distributed in the hope that it will be useful,
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU but WITHOUT ANY WARRANTY; without even the implied warranty of
// General Public License for more details. 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 this library; if not, write to the Free Software You should have received a copy of the GNU General Public License
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// MA 02110-1301 USA */
/**
* @authors
* Jeffrey Wilcke <i@jev.io>
*/
package main package main
import ( import (
@ -67,6 +70,7 @@ func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib {
lib := &UiLib{JSXEth: xeth.NewJSXEth(eth), engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(eth), filterCallbacks: make(map[int][]int)} //, filters: make(map[int]*xeth.JSFilter)} lib := &UiLib{JSXEth: xeth.NewJSXEth(eth), engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(eth), filterCallbacks: make(map[int][]int)} //, filters: make(map[int]*xeth.JSFilter)}
lib.miner = miner.New(eth.KeyManager().Address(), eth) lib.miner = miner.New(eth.KeyManager().Address(), eth)
lib.filterManager = filter.NewFilterManager(eth.EventMux()) lib.filterManager = filter.NewFilterManager(eth.EventMux())
go lib.filterManager.Start()
return lib return lib
} }
@ -208,16 +212,16 @@ func (self *UiLib) StartDbWithContractAndData(contractHash, data string) {
dbWindow := NewDebuggerWindow(self) dbWindow := NewDebuggerWindow(self)
object := self.eth.ChainManager().State().GetStateObject(ethutil.Hex2Bytes(contractHash)) object := self.eth.ChainManager().State().GetStateObject(ethutil.Hex2Bytes(contractHash))
if len(object.Code) > 0 { if len(object.Code) > 0 {
dbWindow.SetCode("0x" + ethutil.Bytes2Hex(object.Code)) dbWindow.SetCode(ethutil.Bytes2Hex(object.Code))
} }
dbWindow.SetData("0x" + data) dbWindow.SetData(data)
dbWindow.Show() dbWindow.Show()
} }
func (self *UiLib) StartDbWithCode(code string) { func (self *UiLib) StartDbWithCode(code string) {
dbWindow := NewDebuggerWindow(self) dbWindow := NewDebuggerWindow(self)
dbWindow.SetCode("0x" + code) dbWindow.SetCode(code)
dbWindow.Show() dbWindow.Show()
} }
@ -279,6 +283,10 @@ func (self *UiLib) SetGasPrice(price string) {
self.miner.MinAcceptedGasPrice = ethutil.Big(price) self.miner.MinAcceptedGasPrice = ethutil.Big(price)
} }
func (self *UiLib) SetExtra(extra string) {
self.miner.Extra = extra
}
func (self *UiLib) ToggleMining() bool { func (self *UiLib) ToggleMining() bool {
if !self.miner.Mining() { if !self.miner.Mining() {
self.miner.Start() self.miner.Start()

@ -1,10 +1,25 @@
/*
This file is part of go-ethereum
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/>.
*/
package main package main
import ( import (
"crypto/elliptic" "crypto/elliptic"
"fmt" "flag"
"log" "log"
"net"
"os" "os"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -12,29 +27,32 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
) )
var (
natType = flag.String("nat", "", "NAT traversal implementation")
pmpGateway = flag.String("gateway", "", "gateway address for NAT-PMP")
listenAddr = flag.String("addr", ":30301", "listen address")
)
func main() { func main() {
flag.Parse()
nat, err := p2p.ParseNAT(*natType, *pmpGateway)
if err != nil {
log.Fatal("invalid nat:", err)
}
logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.InfoLevel)) logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.InfoLevel))
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
marshaled := elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y) marshaled := elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y)
srv := p2p.Server{ srv := p2p.Server{
MaxPeers: 100, MaxPeers: 100,
Identity: p2p.NewSimpleClientIdentity("Ethereum(G)", "0.1", "Peer Server Two", string(marshaled)), Identity: p2p.NewSimpleClientIdentity("Ethereum(G)", "0.1", "Peer Server Two", marshaled),
ListenAddr: ":30301", ListenAddr: *listenAddr,
NAT: p2p.UPNP(), NAT: nat,
NoDial: true,
} }
if err := srv.Start(); err != nil { if err := srv.Start(); err != nil {
fmt.Println("could not start server:", err) log.Fatal("could not start server:", err)
os.Exit(1)
}
// add seed peers
seed, err := net.ResolveTCPAddr("tcp", "poc-8.ethdev.com:30303")
if err != nil {
fmt.Println("couldn't resolve:", err)
} else {
srv.SuggestPeer(seed.IP, seed.Port, nil)
} }
select {} select {}
} }

@ -14,6 +14,10 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/ */
/**
* @authors
* Felix Lange <felix@ethdev.com>
*/
// rlpdump is a pretty-printer for RLP data. // rlpdump is a pretty-printer for RLP data.
package main package main
@ -106,8 +110,7 @@ func dump(s *rlp.Stream, depth int) error {
s.List() s.List()
defer s.ListEnd() defer s.ListEnd()
if size == 0 { if size == 0 {
fmt.Printf(ws(depth) + "[]") fmt.Print(ws(depth) + "[]")
return nil
} else { } else {
fmt.Println(ws(depth) + "[") fmt.Println(ws(depth) + "[")
for i := 0; ; i++ { for i := 0; ; i++ {

@ -1,3 +1,24 @@
/*
This file is part of go-ethereum
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>
* Viktor Tron <viktor@ethdev.com>
*/
package utils package utils
import ( import (
@ -18,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
) )
@ -238,7 +260,8 @@ func BlockDo(ethereum *eth.Ethereum, hash []byte) error {
parent := ethereum.ChainManager().GetBlock(block.ParentHash()) parent := ethereum.ChainManager().GetBlock(block.ParentHash())
_, err := ethereum.BlockProcessor().TransitionState(parent.State(), parent, block) statedb := state.New(parent.Root(), ethereum.Db())
_, err := ethereum.BlockProcessor().TransitionState(statedb, parent, block)
if err != nil { if err != nil {
return err return err
} }

@ -1,3 +1,23 @@
/*
This file is part of go-ethereum
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>
*/
package utils package utils
import ( import (

@ -1,9 +1,34 @@
/*
This file is part of go-ethereum
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>
*/
package utils package utils
import ( import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/event/filter"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/ui"
"github.com/ethereum/go-ethereum/websocket" "github.com/ethereum/go-ethereum/websocket"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
) )
@ -15,16 +40,19 @@ func args(v ...interface{}) []interface{} {
} }
type WebSocketServer struct { type WebSocketServer struct {
ethereum *eth.Ethereum eth *eth.Ethereum
filterCallbacks map[int][]int filterManager *filter.FilterManager
} }
func NewWebSocketServer(eth *eth.Ethereum) *WebSocketServer { func NewWebSocketServer(eth *eth.Ethereum) *WebSocketServer {
return &WebSocketServer{eth, make(map[int][]int)} filterManager := filter.NewFilterManager(eth.EventMux())
go filterManager.Start()
return &WebSocketServer{eth, filterManager}
} }
func (self *WebSocketServer) Serv() { func (self *WebSocketServer) Serv() {
pipe := xeth.NewJSXEth(self.ethereum) pipe := xeth.NewJSXEth(self.eth)
wsServ := websocket.NewServer("/eth", ":40404") wsServ := websocket.NewServer("/eth", ":40404")
wsServ.MessageFunc(func(c *websocket.Client, msg *websocket.Message) { wsServ.MessageFunc(func(c *websocket.Client, msg *websocket.Message) {
@ -33,74 +61,91 @@ func (self *WebSocketServer) Serv() {
data := ethutil.NewValue(msg.Args) data := ethutil.NewValue(msg.Args)
bcode, err := ethutil.Compile(data.Get(0).Str(), false) bcode, err := ethutil.Compile(data.Get(0).Str(), false)
if err != nil { if err != nil {
c.Write(args(nil, err.Error()), msg.Seed) c.Write(args(nil, err.Error()), msg.Id)
} }
code := ethutil.Bytes2Hex(bcode) code := ethutil.Bytes2Hex(bcode)
c.Write(args(code, nil), msg.Seed) c.Write(args(code, nil), msg.Id)
case "getBlockByNumber": case "eth_blockByNumber":
args := msg.Arguments() args := msg.Arguments()
block := pipe.BlockByNumber(int32(args.Get(0).Uint())) block := pipe.BlockByNumber(int32(args.Get(0).Uint()))
c.Write(block, msg.Seed) c.Write(block, msg.Id)
case "eth_blockByHash":
args := msg.Arguments()
case "getKey": c.Write(pipe.BlockByHash(args.Get(0).Str()), msg.Id)
c.Write(pipe.Key().PrivateKey, msg.Seed)
case "transact": case "eth_transact":
if mp, ok := msg.Args[0].(map[string]interface{}); ok { if mp, ok := msg.Args[0].(map[string]interface{}); ok {
object := mapToTxParams(mp) object := mapToTxParams(mp)
c.Write( c.Write(
args(pipe.Transact(object["from"], object["to"], object["value"], object["gas"], object["gasPrice"], object["data"])), args(pipe.Transact(pipe.Key().PrivateKey, object["to"], object["value"], object["gas"], object["gasPrice"], object["data"])),
msg.Seed, msg.Id,
) )
} }
case "getCoinBase": case "eth_gasPrice":
c.Write(pipe.CoinBase(), msg.Seed) c.Write("10000000000000", msg.Id)
case "eth_coinbase":
c.Write(pipe.CoinBase(), msg.Id)
case "getIsListening": case "eth_listening":
c.Write(pipe.IsListening(), msg.Seed) c.Write(pipe.IsListening(), msg.Id)
case "getIsMining": case "eth_mining":
c.Write(pipe.IsMining(), msg.Seed) c.Write(pipe.IsMining(), msg.Id)
case "getPeerCoint": case "eth_peerCount":
c.Write(pipe.PeerCount(), msg.Seed) c.Write(pipe.PeerCount(), msg.Id)
case "getCountAt": case "eth_countAt":
args := msg.Arguments() args := msg.Arguments()
c.Write(pipe.TxCountAt(args.Get(0).Str()), msg.Seed) c.Write(pipe.TxCountAt(args.Get(0).Str()), msg.Id)
case "getCodeAt": case "eth_codeAt":
args := msg.Arguments() args := msg.Arguments()
c.Write(len(pipe.CodeAt(args.Get(0).Str())), msg.Seed) c.Write(len(pipe.CodeAt(args.Get(0).Str())), msg.Id)
case "getBlockByHash": case "eth_storageAt":
args := msg.Arguments() args := msg.Arguments()
c.Write(pipe.BlockByHash(args.Get(0).Str()), msg.Seed) c.Write(pipe.StorageAt(args.Get(0).Str(), args.Get(1).Str()), msg.Id)
case "getStorageAt": case "eth_balanceAt":
args := msg.Arguments() args := msg.Arguments()
c.Write(pipe.StorageAt(args.Get(0).Str(), args.Get(1).Str()), msg.Seed) c.Write(pipe.BalanceAt(args.Get(0).Str()), msg.Id)
case "getBalanceAt":
args := msg.Arguments()
c.Write(pipe.BalanceAt(args.Get(0).Str()), msg.Seed)
case "getSecretToAddress":
args := msg.Arguments()
c.Write(pipe.SecretToAddress(args.Get(0).Str()), msg.Seed) case "eth_accounts":
c.Write(pipe.Accounts(), msg.Id)
case "newFilter": case "eth_newFilter":
case "newFilterString": if mp, ok := msg.Args[0].(map[string]interface{}); ok {
case "messages": var id int
// TODO filter := ui.NewFilterFromMap(mp, self.eth)
filter.MessageCallback = func(messages state.Messages) {
c.Event(toMessages(messages), "eth_changed", id)
}
id = self.filterManager.InstallFilter(filter)
c.Write(id, msg.Id)
}
case "eth_newFilterString":
var id int
filter := core.NewFilter(self.eth)
filter.BlockCallback = func(block *types.Block) {
c.Event(nil, "eth_changed", id)
}
id = self.filterManager.InstallFilter(filter)
c.Write(id, msg.Id)
case "eth_filterLogs":
filter := self.filterManager.GetFilter(int(msg.Arguments().Get(0).Uint()))
if filter != nil {
c.Write(toMessages(filter.Find()), msg.Id)
}
} }
}) })
@ -108,6 +153,15 @@ func (self *WebSocketServer) Serv() {
wsServ.Listen() wsServ.Listen()
} }
func toMessages(messages state.Messages) (msgs []xeth.JSMessage) {
msgs = make([]xeth.JSMessage, len(messages))
for i, msg := range messages {
msgs[i] = xeth.NewJSMessage(msg)
}
return
}
func StartWebSockets(eth *eth.Ethereum) { func StartWebSockets(eth *eth.Ethereum) {
wslogger.Infoln("Starting WebSockets") wslogger.Infoln("Starting WebSockets")

@ -2,7 +2,6 @@ package core
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"sync" "sync"
@ -36,6 +35,7 @@ type EthManager interface {
} }
type BlockProcessor struct { type BlockProcessor struct {
db ethutil.Database
// Mutex for locking the block processor. Blocks can only be handled one at a time // Mutex for locking the block processor. Blocks can only be handled one at a time
mutex sync.Mutex mutex sync.Mutex
// Canonical block chain // Canonical block chain
@ -57,8 +57,9 @@ type BlockProcessor struct {
eventMux *event.TypeMux eventMux *event.TypeMux
} }
func NewBlockProcessor(txpool *TxPool, chainManager *ChainManager, eventMux *event.TypeMux) *BlockProcessor { func NewBlockProcessor(db ethutil.Database, txpool *TxPool, chainManager *ChainManager, eventMux *event.TypeMux) *BlockProcessor {
sm := &BlockProcessor{ sm := &BlockProcessor{
db: db,
mem: make(map[string]*big.Int), mem: make(map[string]*big.Int),
Pow: ezp.New(), Pow: ezp.New(),
bc: chainManager, bc: chainManager,
@ -170,7 +171,8 @@ func (sm *BlockProcessor) Process(block *types.Block) (td *big.Int, msgs state.M
func (sm *BlockProcessor) ProcessWithParent(block, parent *types.Block) (td *big.Int, messages state.Messages, err error) { func (sm *BlockProcessor) ProcessWithParent(block, parent *types.Block) (td *big.Int, messages state.Messages, err error) {
sm.lastAttemptedBlock = block sm.lastAttemptedBlock = block
state := state.New(parent.Trie().Copy()) state := state.New(parent.Root(), sm.db)
//state := state.New(parent.Trie().Copy())
// Block validation // Block validation
if err = sm.ValidateBlock(block, parent); err != nil { if err = sm.ValidateBlock(block, parent); err != nil {
@ -214,52 +216,33 @@ func (sm *BlockProcessor) ProcessWithParent(block, parent *types.Block) (td *big
return return
} }
// Calculate the new total difficulty and sync back to the db // Calculate the td for this block
if td, ok := sm.CalculateTD(block); ok { td = CalculateTD(block, parent)
// Sync the current block's state to the database and cancelling out the deferred Undo // Sync the current block's state to the database and cancelling out the deferred Undo
state.Sync() state.Sync()
// Set the block hashes for the current messages
state.Manifest().SetHash(block.Hash()) state.Manifest().SetHash(block.Hash())
messages = state.Manifest().Messages
messages := state.Manifest().Messages // Reset the manifest XXX We need this?
state.Manifest().Reset() state.Manifest().Reset()
// Remove transactions from the pool
chainlogger.Infof("processed block #%d (%x...)\n", header.Number, block.Hash()[0:4]) sm.txpool.RemoveSet(block.Transactions())
sm.txpool.RemoveSet(block.Transactions()) chainlogger.Infof("processed block #%d (%x...)\n", header.Number, block.Hash()[0:4])
return td, messages, nil return td, messages, nil
} else {
return nil, nil, errors.New("total diff failed")
}
}
func (sm *BlockProcessor) CalculateTD(block *types.Block) (*big.Int, bool) {
uncleDiff := new(big.Int)
for _, uncle := range block.Uncles() {
uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty)
}
// TD(genesis_block) = 0 and TD(B) = TD(B.parent) + sum(u.difficulty for u in B.uncles) + B.difficulty
td := new(big.Int)
td = td.Add(sm.bc.Td(), uncleDiff)
td = td.Add(td, block.Header().Difficulty)
// The new TD will only be accepted if the new difficulty is
// is greater than the previous.
if td.Cmp(sm.bc.Td()) > 0 {
return td, true
}
return nil, false
} }
// Validates the current block. Returns an error if the block was invalid, // Validates the current block. Returns an error if the block was invalid,
// an uncle or anything that isn't on the current block chain. // an uncle or anything that isn't on the current block chain.
// Validation validates easy over difficult (dagger takes longer time = difficult) // Validation validates easy over difficult (dagger takes longer time = difficult)
func (sm *BlockProcessor) ValidateBlock(block, parent *types.Block) error { func (sm *BlockProcessor) ValidateBlock(block, parent *types.Block) error {
if len(block.Header().Extra) > 1024 {
return fmt.Errorf("Block extra data too long (%d)", len(block.Header().Extra))
}
expd := CalcDifficulty(block, parent) expd := CalcDifficulty(block, parent)
if expd.Cmp(block.Header().Difficulty) < 0 { if expd.Cmp(block.Header().Difficulty) != 0 {
return fmt.Errorf("Difficulty check failed for block %v, %v", block.Header().Difficulty, expd) return fmt.Errorf("Difficulty check failed for block %v, %v", block.Header().Difficulty, expd)
} }
@ -286,32 +269,38 @@ func (sm *BlockProcessor) ValidateBlock(block, parent *types.Block) error {
func (sm *BlockProcessor) AccumelateRewards(statedb *state.StateDB, block, parent *types.Block) error { func (sm *BlockProcessor) AccumelateRewards(statedb *state.StateDB, block, parent *types.Block) error {
reward := new(big.Int).Set(BlockReward) reward := new(big.Int).Set(BlockReward)
knownUncles := set.New() ancestors := set.New()
for _, uncle := range parent.Uncles() { for _, ancestor := range sm.bc.GetAncestors(block, 7) {
knownUncles.Add(string(uncle.Hash())) ancestors.Add(string(ancestor.Hash()))
} }
nonces := ethutil.NewSet(block.Header().Nonce) uncles := set.New()
uncles.Add(string(block.Hash()))
for _, uncle := range block.Uncles() { for _, uncle := range block.Uncles() {
if nonces.Include(uncle.Nonce) { if uncles.Has(string(uncle.Hash())) {
// Error not unique // Error not unique
return UncleError("Uncle not unique") return UncleError("Uncle not unique")
} }
uncles.Add(string(uncle.Hash()))
uncleParent := sm.bc.GetBlock(uncle.ParentHash) if !ancestors.Has(string(uncle.ParentHash)) {
if uncleParent == nil {
return UncleError(fmt.Sprintf("Uncle's parent unknown (%x)", uncle.ParentHash[0:4])) return UncleError(fmt.Sprintf("Uncle's parent unknown (%x)", uncle.ParentHash[0:4]))
} }
if uncleParent.Header().Number.Cmp(new(big.Int).Sub(parent.Header().Number, big.NewInt(6))) < 0 { /*
return UncleError("Uncle too old") uncleParent := sm.bc.GetBlock(uncle.ParentHash)
} if uncleParent == nil {
return UncleError(fmt.Sprintf("Uncle's parent unknown (%x)", uncle.ParentHash[0:4]))
}
if knownUncles.Has(string(uncle.Hash())) { if uncleParent.Number().Cmp(new(big.Int).Sub(parent.Number(), big.NewInt(6))) < 0 {
return UncleError("Uncle in chain") return UncleError("Uncle too old")
} }
nonces.Insert(uncle.Nonce) if knownUncles.Has(string(uncle.Hash())) {
return UncleError("Uncle in chain")
}
*/
r := new(big.Int) r := new(big.Int)
r.Mul(BlockReward, big.NewInt(15)).Div(r, big.NewInt(16)) r.Mul(BlockReward, big.NewInt(15)).Div(r, big.NewInt(16))
@ -347,7 +336,8 @@ func (sm *BlockProcessor) GetMessages(block *types.Block) (messages []*state.Mes
var ( var (
parent = sm.bc.GetBlock(block.Header().ParentHash) parent = sm.bc.GetBlock(block.Header().ParentHash)
state = state.New(parent.Trie().Copy()) //state = state.New(parent.Trie().Copy())
state = state.New(parent.Root(), sm.db)
) )
defer state.Reset() defer state.Reset()

@ -23,17 +23,30 @@ type StateQuery interface {
func CalcDifficulty(block, parent *types.Block) *big.Int { func CalcDifficulty(block, parent *types.Block) *big.Int {
diff := new(big.Int) diff := new(big.Int)
bh, ph := block.Header(), parent.Header() adjust := new(big.Int).Rsh(parent.Difficulty(), 10)
adjust := new(big.Int).Rsh(ph.Difficulty, 10) if block.Time() >= parent.Time()+8 {
if bh.Time >= ph.Time+5 { diff.Sub(parent.Difficulty(), adjust)
diff.Sub(ph.Difficulty, adjust)
} else { } else {
diff.Add(ph.Difficulty, adjust) diff.Add(parent.Difficulty(), adjust)
} }
return diff return diff
} }
func CalculateTD(block, parent *types.Block) *big.Int {
uncleDiff := new(big.Int)
for _, uncle := range block.Uncles() {
uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty)
}
// TD(genesis_block) = 0 and TD(B) = TD(B.parent) + sum(u.difficulty for u in B.uncles) + B.difficulty
td := new(big.Int)
td = td.Add(parent.Td, uncleDiff)
td = td.Add(td, block.Header().Difficulty)
return td
}
func CalcGasLimit(parent, block *types.Block) *big.Int { func CalcGasLimit(parent, block *types.Block) *big.Int {
if block.Number().Cmp(big.NewInt(0)) == 0 { if block.Number().Cmp(big.NewInt(0)) == 0 {
return ethutil.BigPow(10, 6) return ethutil.BigPow(10, 6)
@ -55,6 +68,7 @@ func CalcGasLimit(parent, block *types.Block) *big.Int {
type ChainManager struct { type ChainManager struct {
//eth EthManager //eth EthManager
db ethutil.Database
processor types.BlockProcessor processor types.BlockProcessor
eventMux *event.TypeMux eventMux *event.TypeMux
genesisBlock *types.Block genesisBlock *types.Block
@ -96,13 +110,9 @@ func (self *ChainManager) CurrentBlock() *types.Block {
return self.currentBlock return self.currentBlock
} }
func NewChainManager(mux *event.TypeMux) *ChainManager { func NewChainManager(db ethutil.Database, mux *event.TypeMux) *ChainManager {
bc := &ChainManager{} bc := &ChainManager{db: db, genesisBlock: GenesisBlock(db), eventMux: mux}
bc.genesisBlock = GenesisBlock()
bc.eventMux = mux
bc.setLastBlock() bc.setLastBlock()
bc.transState = bc.State().Copy() bc.transState = bc.State().Copy()
return bc return bc
@ -120,7 +130,7 @@ func (self *ChainManager) SetProcessor(proc types.BlockProcessor) {
} }
func (self *ChainManager) State() *state.StateDB { func (self *ChainManager) State() *state.StateDB {
return state.New(self.CurrentBlock().Trie()) return state.New(self.CurrentBlock().Root(), self.db)
} }
func (self *ChainManager) TransState() *state.StateDB { func (self *ChainManager) TransState() *state.StateDB {
@ -128,7 +138,7 @@ func (self *ChainManager) TransState() *state.StateDB {
} }
func (bc *ChainManager) setLastBlock() { func (bc *ChainManager) setLastBlock() {
data, _ := ethutil.Config.Db.Get([]byte("LastBlock")) data, _ := bc.db.Get([]byte("LastBlock"))
if len(data) != 0 { if len(data) != 0 {
var block types.Block var block types.Block
rlp.Decode(bytes.NewReader(data), &block) rlp.Decode(bytes.NewReader(data), &block)
@ -137,12 +147,12 @@ func (bc *ChainManager) setLastBlock() {
bc.lastBlockNumber = block.Header().Number.Uint64() bc.lastBlockNumber = block.Header().Number.Uint64()
// Set the last know difficulty (might be 0x0 as initial value, Genesis) // Set the last know difficulty (might be 0x0 as initial value, Genesis)
bc.td = ethutil.BigD(ethutil.Config.Db.LastKnownTD()) bc.td = ethutil.BigD(bc.db.LastKnownTD())
} else { } else {
bc.Reset() bc.Reset()
} }
chainlogger.Infof("Last block (#%d) %x\n", bc.lastBlockNumber, bc.currentBlock.Hash()) chainlogger.Infof("Last block (#%d) %x TD=%v\n", bc.lastBlockNumber, bc.currentBlock.Hash(), bc.td)
} }
// Block creation & chain handling // Block creation & chain handling
@ -183,7 +193,7 @@ func (bc *ChainManager) Reset() {
defer bc.mu.Unlock() defer bc.mu.Unlock()
for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.Header().ParentHash) { for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.Header().ParentHash) {
ethutil.Config.Db.Delete(block.Hash()) bc.db.Delete(block.Hash())
} }
// Prepare the genesis block // Prepare the genesis block
@ -210,7 +220,7 @@ func (self *ChainManager) Export() []byte {
func (bc *ChainManager) insert(block *types.Block) { func (bc *ChainManager) insert(block *types.Block) {
encodedBlock := ethutil.Encode(block) encodedBlock := ethutil.Encode(block)
ethutil.Config.Db.Put([]byte("LastBlock"), encodedBlock) bc.db.Put([]byte("LastBlock"), encodedBlock)
bc.currentBlock = block bc.currentBlock = block
bc.lastBlockHash = block.Hash() bc.lastBlockHash = block.Hash()
} }
@ -218,8 +228,8 @@ func (bc *ChainManager) insert(block *types.Block) {
func (bc *ChainManager) write(block *types.Block) { func (bc *ChainManager) write(block *types.Block) {
bc.writeBlockInfo(block) bc.writeBlockInfo(block)
encodedBlock := ethutil.Encode(block) encodedBlock := ethutil.Encode(block.RlpDataForStorage())
ethutil.Config.Db.Put(block.Hash(), encodedBlock) bc.db.Put(block.Hash(), encodedBlock)
} }
// Accessors // Accessors
@ -229,7 +239,7 @@ func (bc *ChainManager) Genesis() *types.Block {
// Block fetching methods // Block fetching methods
func (bc *ChainManager) HasBlock(hash []byte) bool { func (bc *ChainManager) HasBlock(hash []byte) bool {
data, _ := ethutil.Config.Db.Get(hash) data, _ := bc.db.Get(hash)
return len(data) != 0 return len(data) != 0
} }
@ -241,20 +251,18 @@ func (self *ChainManager) GetBlockHashesFromHash(hash []byte, max uint64) (chain
// XXX Could be optimised by using a different database which only holds hashes (i.e., linked list) // XXX Could be optimised by using a different database which only holds hashes (i.e., linked list)
for i := uint64(0); i < max; i++ { for i := uint64(0); i < max; i++ {
block = self.GetBlock(block.Header().ParentHash)
chain = append(chain, block.Hash()) chain = append(chain, block.Hash())
if block.Header().Number.Cmp(ethutil.Big0) <= 0 { if block.Header().Number.Cmp(ethutil.Big0) <= 0 {
break break
} }
block = self.GetBlock(block.Header().ParentHash)
} }
return return
} }
func (self *ChainManager) GetBlock(hash []byte) *types.Block { func (self *ChainManager) GetBlock(hash []byte) *types.Block {
data, _ := ethutil.Config.Db.Get(hash) data, _ := self.db.Get(hash)
if len(data) == 0 { if len(data) == 0 {
return nil return nil
} }
@ -267,6 +275,28 @@ func (self *ChainManager) GetBlock(hash []byte) *types.Block {
return &block return &block
} }
func (self *ChainManager) GetUnclesInChain(block *types.Block, length int) (uncles []*types.Header) {
for i := 0; block != nil && i < length; i++ {
uncles = append(uncles, block.Uncles()...)
block = self.GetBlock(block.ParentHash())
}
return
}
func (self *ChainManager) GetAncestors(block *types.Block, length int) (blocks []*types.Block) {
for i := 0; i < length; i++ {
block = self.GetBlock(block.ParentHash())
if block == nil {
break
}
blocks = append(blocks, block)
}
return
}
func (self *ChainManager) GetBlockByNumber(num uint64) *types.Block { func (self *ChainManager) GetBlockByNumber(num uint64) *types.Block {
self.mu.RLock() self.mu.RLock()
defer self.mu.RUnlock() defer self.mu.RUnlock()
@ -286,7 +316,7 @@ func (self *ChainManager) GetBlockByNumber(num uint64) *types.Block {
} }
func (bc *ChainManager) setTotalDifficulty(td *big.Int) { func (bc *ChainManager) setTotalDifficulty(td *big.Int) {
ethutil.Config.Db.Put([]byte("LTD"), td.Bytes()) bc.db.Put([]byte("LTD"), td.Bytes())
bc.td = td bc.td = td
} }
@ -343,12 +373,12 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error {
cblock := self.currentBlock cblock := self.currentBlock
if td.Cmp(self.td) > 0 { if td.Cmp(self.td) > 0 {
if block.Header().Number.Cmp(new(big.Int).Add(cblock.Header().Number, ethutil.Big1)) < 0 { if block.Header().Number.Cmp(new(big.Int).Add(cblock.Header().Number, ethutil.Big1)) < 0 {
chainlogger.Infof("Split detected. New head #%v (%x), was #%v (%x)\n", block.Header().Number, block.Hash()[:4], cblock.Header().Number, cblock.Hash()[:4]) chainlogger.Infof("Split detected. New head #%v (%x) TD=%v, was #%v (%x) TD=%v\n", block.Header().Number, block.Hash()[:4], td, cblock.Header().Number, cblock.Hash()[:4], self.td)
} }
self.setTotalDifficulty(td) self.setTotalDifficulty(td)
self.insert(block) self.insert(block)
self.transState = state.New(cblock.Trie().Copy()) self.transState = state.New(cblock.Root(), self.db) //state.New(cblock.Trie().Copy())
} }
} }

@ -1,10 +1,10 @@
package core package core
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"path" "path"
"reflect"
"runtime" "runtime"
"strconv" "strconv"
"testing" "testing"
@ -21,14 +21,6 @@ func init() {
ethutil.ReadConfig("/tmp/ethtest", "/tmp/ethtest", "ETH") ethutil.ReadConfig("/tmp/ethtest", "/tmp/ethtest", "ETH")
} }
func reset() {
db, err := ethdb.NewMemDatabase()
if err != nil {
panic("Could not create mem-db, failing")
}
ethutil.Config.Db = db
}
func loadChain(fn string, t *testing.T) (types.Blocks, error) { func loadChain(fn string, t *testing.T) (types.Blocks, error) {
fh, err := os.OpenFile(path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "_data", fn), os.O_RDONLY, os.ModePerm) fh, err := os.OpenFile(path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "_data", fn), os.O_RDONLY, os.ModePerm)
if err != nil { if err != nil {
@ -54,7 +46,7 @@ func insertChain(done chan bool, chainMan *ChainManager, chain types.Blocks, t *
} }
func TestChainInsertions(t *testing.T) { func TestChainInsertions(t *testing.T) {
reset() db, _ := ethdb.NewMemDatabase()
chain1, err := loadChain("valid1", t) chain1, err := loadChain("valid1", t)
if err != nil { if err != nil {
@ -69,9 +61,9 @@ func TestChainInsertions(t *testing.T) {
} }
var eventMux event.TypeMux var eventMux event.TypeMux
chainMan := NewChainManager(&eventMux) chainMan := NewChainManager(db, &eventMux)
txPool := NewTxPool(&eventMux) txPool := NewTxPool(&eventMux)
blockMan := NewBlockManager(txPool, chainMan, &eventMux) blockMan := NewBlockProcessor(db, txPool, chainMan, &eventMux)
chainMan.SetProcessor(blockMan) chainMan.SetProcessor(blockMan)
const max = 2 const max = 2
@ -84,17 +76,17 @@ func TestChainInsertions(t *testing.T) {
<-done <-done
} }
if reflect.DeepEqual(chain2[len(chain2)-1], chainMan.CurrentBlock()) { if bytes.Equal(chain2[len(chain2)-1].Hash(), chainMan.CurrentBlock().Hash()) {
t.Error("chain2 is canonical and shouldn't be") t.Error("chain2 is canonical and shouldn't be")
} }
if !reflect.DeepEqual(chain1[len(chain1)-1], chainMan.CurrentBlock()) { if !bytes.Equal(chain1[len(chain1)-1].Hash(), chainMan.CurrentBlock().Hash()) {
t.Error("chain1 isn't canonical and should be") t.Error("chain1 isn't canonical and should be")
} }
} }
func TestChainMultipleInsertions(t *testing.T) { func TestChainMultipleInsertions(t *testing.T) {
reset() db, _ := ethdb.NewMemDatabase()
const max = 4 const max = 4
chains := make([]types.Blocks, max) chains := make([]types.Blocks, max)
@ -113,9 +105,9 @@ func TestChainMultipleInsertions(t *testing.T) {
} }
} }
var eventMux event.TypeMux var eventMux event.TypeMux
chainMan := NewChainManager(&eventMux) chainMan := NewChainManager(db, &eventMux)
txPool := NewTxPool(&eventMux) txPool := NewTxPool(&eventMux)
blockMan := NewBlockManager(txPool, chainMan, &eventMux) blockMan := NewBlockProcessor(db, txPool, chainMan, &eventMux)
chainMan.SetProcessor(blockMan) chainMan.SetProcessor(blockMan)
done := make(chan bool, max) done := make(chan bool, max)
for i, chain := range chains { for i, chain := range chains {
@ -132,7 +124,25 @@ func TestChainMultipleInsertions(t *testing.T) {
<-done <-done
} }
if !reflect.DeepEqual(chains[longest][len(chains[longest])-1], chainMan.CurrentBlock()) { if !bytes.Equal(chains[longest][len(chains[longest])-1].Hash(), chainMan.CurrentBlock().Hash()) {
t.Error("Invalid canonical chain") t.Error("Invalid canonical chain")
} }
} }
func TestGetAncestors(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
var eventMux event.TypeMux
chainMan := NewChainManager(db, &eventMux)
chain, err := loadChain("valid1", t)
if err != nil {
fmt.Println(err)
t.FailNow()
}
for _, block := range chain {
chainMan.write(block)
}
ancestors := chainMan.GetAncestors(chain[len(chain)-1], 4)
fmt.Println(ancestors)
}

@ -5,6 +5,7 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/vm" "github.com/ethereum/go-ethereum/vm"
) )
@ -13,7 +14,6 @@ type Execution struct {
env vm.Environment env vm.Environment
address, input []byte address, input []byte
Gas, price, value *big.Int Gas, price, value *big.Int
SkipTransfer bool
} }
func NewExecution(env vm.Environment, address, input []byte, gas, gasPrice, value *big.Int) *Execution { func NewExecution(env vm.Environment, address, input []byte, gas, gasPrice, value *big.Int) *Execution {
@ -33,7 +33,7 @@ func (self *Execution) Call(codeAddr []byte, caller vm.ContextRef) ([]byte, erro
func (self *Execution) exec(code, contextAddr []byte, caller vm.ContextRef) (ret []byte, err error) { func (self *Execution) exec(code, contextAddr []byte, caller vm.ContextRef) (ret []byte, err error) {
env := self.env env := self.env
evm := vm.New(env, vm.DebugVmTy) evm := vm.New(env)
if env.Depth() == vm.MaxCallDepth { if env.Depth() == vm.MaxCallDepth {
caller.ReturnGas(self.Gas, self.price) caller.ReturnGas(self.Gas, self.price)
@ -41,16 +41,22 @@ func (self *Execution) exec(code, contextAddr []byte, caller vm.ContextRef) (ret
return nil, vm.DepthError{} return nil, vm.DepthError{}
} }
vsnapshot := env.State().Copy()
if len(self.address) == 0 {
// Generate a new address
nonce := env.State().GetNonce(caller.Address())
self.address = crypto.CreateAddress(caller.Address(), nonce)
env.State().SetNonce(caller.Address(), nonce+1)
}
from, to := env.State().GetStateObject(caller.Address()), env.State().GetOrNewStateObject(self.address) from, to := env.State().GetStateObject(caller.Address()), env.State().GetOrNewStateObject(self.address)
// Skipping transfer is used on testing for the initial call err = env.Transfer(from, to, self.value)
if !self.SkipTransfer { if err != nil {
err = env.Transfer(from, to, self.value) env.State().Set(vsnapshot)
if err != nil {
caller.ReturnGas(self.Gas, self.price) caller.ReturnGas(self.Gas, self.price)
err = fmt.Errorf("Insufficient funds to transfer value. Req %v, has %v", self.value, from.Balance) return nil, fmt.Errorf("insufficient funds to transfer value. Req %v, has %v", self.value, from.Balance())
return
}
} }
snapshot := env.State().Copy() snapshot := env.State().Copy()

@ -19,18 +19,20 @@ var ZeroHash512 = make([]byte, 64)
var EmptyShaList = crypto.Sha3(ethutil.Encode([]interface{}{})) var EmptyShaList = crypto.Sha3(ethutil.Encode([]interface{}{}))
var EmptyListRoot = crypto.Sha3(ethutil.Encode("")) var EmptyListRoot = crypto.Sha3(ethutil.Encode(""))
func GenesisBlock() *types.Block { func GenesisBlock(db ethutil.Database) *types.Block {
genesis := types.NewBlock(ZeroHash256, ZeroHash160, nil, big.NewInt(131072), crypto.Sha3(big.NewInt(42).Bytes()), "") genesis := types.NewBlock(ZeroHash256, ZeroHash160, nil, big.NewInt(131072), crypto.Sha3(big.NewInt(42).Bytes()), "")
genesis.Header().Number = ethutil.Big0 genesis.Header().Number = ethutil.Big0
genesis.Header().GasLimit = big.NewInt(1000000) genesis.Header().GasLimit = big.NewInt(1000000)
genesis.Header().GasUsed = ethutil.Big0 genesis.Header().GasUsed = ethutil.Big0
genesis.Header().Time = 0 genesis.Header().Time = 0
genesis.Td = ethutil.Big0
genesis.SetUncles([]*types.Header{}) genesis.SetUncles([]*types.Header{})
genesis.SetTransactions(types.Transactions{}) genesis.SetTransactions(types.Transactions{})
genesis.SetReceipts(types.Receipts{}) genesis.SetReceipts(types.Receipts{})
statedb := state.New(genesis.Trie()) statedb := state.New(genesis.Root(), db)
//statedb := state.New(genesis.Trie())
for _, addr := range []string{ for _, addr := range []string{
"51ba59315b3a95761d0863b05ccc7a7f54703d99", "51ba59315b3a95761d0863b05ccc7a7f54703d99",
"e4157b34ea9615cfbde6b4fda419828124b70c78", "e4157b34ea9615cfbde6b4fda419828124b70c78",

@ -77,7 +77,6 @@ func NewTestManager() *TestManager {
fmt.Println("Could not create mem-db, failing") fmt.Println("Could not create mem-db, failing")
return nil return nil
} }
ethutil.Config.Db = db
testManager := &TestManager{} testManager := &TestManager{}
testManager.eventMux = new(event.TypeMux) testManager.eventMux = new(event.TypeMux)

@ -192,8 +192,7 @@ func (self *StateTransition) TransitionState() (ret []byte, err error) {
if err == nil { if err == nil {
dataGas := big.NewInt(int64(len(ret))) dataGas := big.NewInt(int64(len(ret)))
dataGas.Mul(dataGas, vm.GasCreateByte) dataGas.Mul(dataGas, vm.GasCreateByte)
if err = self.UseGas(dataGas); err == nil { if err := self.UseGas(dataGas); err == nil {
//self.state.SetCode(ref.Address(), ret)
ref.SetCode(ret) ref.SetCode(ret)
} }
} }

@ -56,11 +56,6 @@ func NewTxPool(eventMux *event.TypeMux) *TxPool {
} }
func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error { func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error {
hash := tx.Hash()
if pool.txs[string(hash)] != nil {
return fmt.Errorf("Known transaction (%x)", hash[0:4])
}
if len(tx.To()) != 0 && len(tx.To()) != 20 { if len(tx.To()) != 0 && len(tx.To()) != 20 {
return fmt.Errorf("Invalid recipient. len = %d", len(tx.To())) return fmt.Errorf("Invalid recipient. len = %d", len(tx.To()))
} }
@ -97,6 +92,10 @@ func (self *TxPool) addTx(tx *types.Transaction) {
} }
func (self *TxPool) Add(tx *types.Transaction) error { func (self *TxPool) Add(tx *types.Transaction) error {
if self.txs[string(tx.Hash())] != nil {
return fmt.Errorf("Known transaction (%x)", tx.Hash()[0:4])
}
err := self.ValidateTransaction(tx) err := self.ValidateTransaction(tx)
if err != nil { if err != nil {
return err return err
@ -149,6 +148,7 @@ func (pool *TxPool) RemoveInvalid(query StateQuery) {
for _, tx := range pool.txs { for _, tx := range pool.txs {
sender := query.GetAccount(tx.From()) sender := query.GetAccount(tx.From())
err := pool.ValidateTransaction(tx) err := pool.ValidateTransaction(tx)
fmt.Println(err, sender.Nonce, tx.Nonce())
if err != nil || sender.Nonce >= tx.Nonce() { if err != nil || sender.Nonce >= tx.Nonce() {
removedTxs = append(removedTxs, tx) removedTxs = append(removedTxs, tx)
} }

@ -6,16 +6,22 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
) )
// State query interface // State query interface
type stateQuery struct{} type stateQuery struct{ db ethutil.Database }
func SQ() stateQuery {
db, _ := ethdb.NewMemDatabase()
return stateQuery{db: db}
}
func (self stateQuery) GetAccount(addr []byte) *state.StateObject { func (self stateQuery) GetAccount(addr []byte) *state.StateObject {
return state.NewStateObject(addr) return state.NewStateObject(addr, self.db)
} }
func transaction() *types.Transaction { func transaction() *types.Transaction {
@ -55,7 +61,7 @@ func TestAddInvalidTx(t *testing.T) {
func TestRemoveSet(t *testing.T) { func TestRemoveSet(t *testing.T) {
pool, _ := setup() pool, _ := setup()
tx1 := transaction() tx1 := transaction()
pool.pool.Add(tx1) pool.addTx(tx1)
pool.RemoveSet(types.Transactions{tx1}) pool.RemoveSet(types.Transactions{tx1})
if pool.Size() > 0 { if pool.Size() > 0 {
t.Error("expected pool size to be 0") t.Error("expected pool size to be 0")
@ -65,16 +71,16 @@ func TestRemoveSet(t *testing.T) {
func TestRemoveInvalid(t *testing.T) { func TestRemoveInvalid(t *testing.T) {
pool, key := setup() pool, key := setup()
tx1 := transaction() tx1 := transaction()
pool.pool.Add(tx1) pool.addTx(tx1)
pool.RemoveInvalid(stateQuery{}) pool.RemoveInvalid(SQ())
if pool.Size() > 0 { if pool.Size() > 0 {
t.Error("expected pool size to be 0") t.Error("expected pool size to be 0")
} }
tx1.SetNonce(1) tx1.SetNonce(1)
tx1.SignECDSA(key) tx1.SignECDSA(key)
pool.pool.Add(tx1) pool.addTx(tx1)
pool.RemoveInvalid(stateQuery{}) pool.RemoveInvalid(SQ())
if pool.Size() != 1 { if pool.Size() != 1 {
t.Error("expected pool size to be 1, is", pool.Size()) t.Error("expected pool size to be 1, is", pool.Size())
} }

@ -9,9 +9,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/ptrie"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/state"
) )
type Header struct { type Header struct {
@ -168,16 +166,18 @@ func (self *Block) RlpDataForStorage() interface{} {
} }
// Header accessors (add as you need them) // Header accessors (add as you need them)
func (self *Block) Number() *big.Int { return self.header.Number } func (self *Block) Number() *big.Int { return self.header.Number }
func (self *Block) NumberU64() uint64 { return self.header.Number.Uint64() } func (self *Block) NumberU64() uint64 { return self.header.Number.Uint64() }
func (self *Block) Bloom() []byte { return self.header.Bloom } func (self *Block) Bloom() []byte { return self.header.Bloom }
func (self *Block) Coinbase() []byte { return self.header.Coinbase } func (self *Block) Coinbase() []byte { return self.header.Coinbase }
func (self *Block) Time() int64 { return int64(self.header.Time) } func (self *Block) Time() int64 { return int64(self.header.Time) }
func (self *Block) GasLimit() *big.Int { return self.header.GasLimit } func (self *Block) GasLimit() *big.Int { return self.header.GasLimit }
func (self *Block) GasUsed() *big.Int { return self.header.GasUsed } func (self *Block) GasUsed() *big.Int { return self.header.GasUsed }
func (self *Block) Trie() *ptrie.Trie { return ptrie.New(self.header.Root, ethutil.Config.Db) }
//func (self *Block) Trie() *ptrie.Trie { return ptrie.New(self.header.Root, ethutil.Config.Db) }
//func (self *Block) State() *state.StateDB { return state.New(self.Trie()) }
func (self *Block) Root() []byte { return self.header.Root }
func (self *Block) SetRoot(root []byte) { self.header.Root = root } func (self *Block) SetRoot(root []byte) { self.header.Root = root }
func (self *Block) State() *state.StateDB { return state.New(self.Trie()) }
func (self *Block) Size() ethutil.StorageSize { return ethutil.StorageSize(len(ethutil.Encode(self))) } func (self *Block) Size() ethutil.StorageSize { return ethutil.StorageSize(len(ethutil.Encode(self))) }
// Implement pow.Block // Implement pow.Block

@ -1,8 +1,9 @@
package types package types
import ( import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/ptrie" "github.com/ethereum/go-ethereum/trie"
) )
type DerivableList interface { type DerivableList interface {
@ -11,7 +12,8 @@ type DerivableList interface {
} }
func DeriveSha(list DerivableList) []byte { func DeriveSha(list DerivableList) []byte {
trie := ptrie.New(nil, ethutil.Config.Db) db, _ := ethdb.NewMemDatabase()
trie := trie.New(nil, db)
for i := 0; i < list.Len(); i++ { for i := 0; i < list.Len(); i++ {
trie.Update(ethutil.Encode(i), list.GetRlp(i)) trie.Update(ethutil.Encode(i), list.GetRlp(i))
} }

@ -3,7 +3,12 @@ package crypto
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/ethutil"
"github.com/obscuren/secp256k1-go"
) )
// These tests are sanity checks. // These tests are sanity checks.
@ -34,3 +39,24 @@ func checkhash(t *testing.T, name string, f func([]byte) []byte, msg, exp []byte
t.Errorf("hash %s returned wrong result.\ngot: %x\nwant: %x", name, sum, exp) t.Errorf("hash %s returned wrong result.\ngot: %x\nwant: %x", name, sum, exp)
} }
} }
func BenchmarkSha3(b *testing.B) {
a := []byte("hello world")
amount := 1000000
start := time.Now()
for i := 0; i < amount; i++ {
Sha3(a)
}
fmt.Println(amount, ":", time.Since(start))
}
func Test0Key(t *testing.T) {
key := ethutil.Hex2Bytes("1111111111111111111111111111111111111111111111111111111111111111")
p, err := secp256k1.GeneratePubKey(key)
addr := Sha3(p[1:])[12:]
fmt.Printf("%x\n", p)
fmt.Printf("%v %x\n", err, addr)
}

@ -0,0 +1,107 @@
/*
This file is part of go-ethereum
go-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @authors
* Gustav Simonsson <gustav.simonsson@gmail.com>
* @date 2015
*
*/
package crypto
import (
"bytes"
"code.google.com/p/go-uuid/uuid"
"crypto/ecdsa"
"crypto/elliptic"
"encoding/json"
"io"
)
type Key struct {
Id *uuid.UUID // Version 4 "random" for unique id not derived from key data
// we only store privkey as pubkey/address can be derived from it
// privkey in this struct is always in plaintext
PrivateKey *ecdsa.PrivateKey
}
type plainKeyJSON struct {
Id []byte
PrivateKey []byte
}
type cipherJSON struct {
Salt []byte
IV []byte
CipherText []byte
}
type encryptedKeyJSON struct {
Id []byte
Crypto cipherJSON
}
func (k *Key) Address() []byte {
pubBytes := FromECDSAPub(&k.PrivateKey.PublicKey)
return Sha3(pubBytes)[12:]
}
func (k *Key) MarshalJSON() (j []byte, err error) {
jStruct := plainKeyJSON{
*k.Id,
FromECDSA(k.PrivateKey),
}
j, err = json.Marshal(jStruct)
return j, err
}
func (k *Key) UnmarshalJSON(j []byte) (err error) {
keyJSON := new(plainKeyJSON)
err = json.Unmarshal(j, &keyJSON)
if err != nil {
return err
}
u := new(uuid.UUID)
*u = keyJSON.Id
k.Id = u
k.PrivateKey = ToECDSA(keyJSON.PrivateKey)
return err
}
func NewKey(rand io.Reader) *Key {
randBytes := make([]byte, 32)
_, err := rand.Read(randBytes)
if err != nil {
panic("key generation: could not read from random source: " + err.Error())
}
reader := bytes.NewReader(randBytes)
_, x, y, err := elliptic.GenerateKey(S256(), reader)
if err != nil {
panic("key generation: elliptic.GenerateKey failed: " + err.Error())
}
privateKeyMarshalled := elliptic.Marshal(S256(), x, y)
privateKeyECDSA := ToECDSA(privateKeyMarshalled)
key := new(Key)
id := uuid.NewRandom()
key.Id = &id
key.PrivateKey = privateKeyECDSA
return key
}

@ -0,0 +1,245 @@
/*
This file is part of go-ethereum
go-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @authors
* Gustav Simonsson <gustav.simonsson@gmail.com>
* @date 2015
*
*/
/*
This key store behaves as KeyStorePlain with the difference that
the private key is encrypted and on disk uses another JSON encoding.
Cryptography:
1. Encryption key is scrypt derived key from user passphrase. Scrypt parameters
(work factors) [1][2] are defined as constants below.
2. Scrypt salt is 32 random bytes from CSPRNG. It is appended to ciphertext.
3. Checksum is SHA3 of the private key bytes.
4. Plaintext is concatenation of private key bytes and checksum.
5. Encryption algo is AES 256 CBC [3][4]
6. CBC IV is 16 random bytes from CSPRNG. It is appended to ciphertext.
7. Plaintext padding is PKCS #7 [5][6]
Encoding:
1. On disk, ciphertext, salt and IV are encoded in a nested JSON object.
cat a key file to see the structure.
2. byte arrays are base64 JSON strings.
3. The EC private key bytes are in uncompressed form [7].
They are a big-endian byte slice of the absolute value of D [8][9].
4. The checksum is the last 32 bytes of the plaintext byte array and the
private key is the preceeding bytes.
References:
1. http://www.tarsnap.com/scrypt/scrypt-slides.pdf
2. http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work-factors
3. http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
4. http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29
5. https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
6. http://tools.ietf.org/html/rfc2315
7. http://bitcoin.stackexchange.com/questions/3059/what-is-a-compressed-bitcoin-key
8. http://golang.org/pkg/crypto/ecdsa/#PrivateKey
9. https://golang.org/pkg/math/big/#Int.Bytes
*/
package crypto
import (
"bytes"
"code.google.com/p/go-uuid/uuid"
"code.google.com/p/go.crypto/scrypt"
"crypto/aes"
"crypto/cipher"
crand "crypto/rand"
"encoding/json"
"errors"
"io"
"os"
"path"
)
const (
// 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
scryptN = 1 << 18
scryptr = 8
scryptp = 1
scryptdkLen = 32
)
type keyStorePassphrase struct {
keysDirPath string
}
func NewKeyStorePassphrase(path string) KeyStore2 {
return &keyStorePassphrase{path}
}
func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) {
return GenerateNewKeyDefault(ks, rand, auth)
}
func (ks keyStorePassphrase) GetKey(keyId *uuid.UUID, auth string) (key *Key, err error) {
keyBytes, err := DecryptKey(ks, keyId, auth)
if err != nil {
return nil, err
}
key = &Key{
Id: keyId,
PrivateKey: ToECDSA(keyBytes),
}
return key, err
}
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
authArray := []byte(auth)
salt := getEntropyCSPRNG(32)
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
if err != nil {
return err
}
keyBytes := FromECDSA(key.PrivateKey)
keyBytesHash := Sha3(keyBytes)
toEncrypt := PKCS7Pad(append(keyBytes, keyBytesHash...))
AES256Block, err := aes.NewCipher(derivedKey)
if err != nil {
return err
}
iv := getEntropyCSPRNG(aes.BlockSize) // 16
AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv)
cipherText := make([]byte, len(toEncrypt))
AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt)
cipherStruct := cipherJSON{
salt,
iv,
cipherText,
}
keyStruct := encryptedKeyJSON{
*key.Id,
cipherStruct,
}
keyJSON, err := json.Marshal(keyStruct)
if err != nil {
return err
}
return WriteKeyFile(key.Id.String(), ks.keysDirPath, keyJSON)
}
func (ks keyStorePassphrase) DeleteKey(keyId *uuid.UUID, auth string) (err error) {
// only delete if correct passphrase is given
_, err = DecryptKey(ks, keyId, auth)
if err != nil {
return err
}
keyDirPath := path.Join(ks.keysDirPath, keyId.String())
return os.RemoveAll(keyDirPath)
}
func DecryptKey(ks keyStorePassphrase, keyId *uuid.UUID, auth string) (keyBytes []byte, err error) {
fileContent, err := GetKeyFile(ks.keysDirPath, keyId)
if err != nil {
return nil, err
}
keyProtected := new(encryptedKeyJSON)
err = json.Unmarshal(fileContent, keyProtected)
salt := keyProtected.Crypto.Salt
iv := keyProtected.Crypto.IV
cipherText := keyProtected.Crypto.CipherText
authArray := []byte(auth)
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
if err != nil {
return nil, err
}
AES256Block, err := aes.NewCipher(derivedKey)
if err != nil {
return nil, err
}
AES256CBCDecrypter := cipher.NewCBCDecrypter(AES256Block, iv)
paddedPlainText := make([]byte, len(cipherText))
AES256CBCDecrypter.CryptBlocks(paddedPlainText, cipherText)
plainText := PKCS7Unpad(paddedPlainText)
if plainText == nil {
err = errors.New("Decryption failed: PKCS7Unpad failed after decryption")
return nil, err
}
keyBytes = plainText[:len(plainText)-32]
keyBytesHash := plainText[len(plainText)-32:]
if !bytes.Equal(Sha3(keyBytes), keyBytesHash) {
err = errors.New("Decryption failed: checksum mismatch")
return nil, err
}
return keyBytes, err
}
func getEntropyCSPRNG(n int) []byte {
mainBuff := make([]byte, n)
_, err := io.ReadFull(crand.Reader, mainBuff)
if err != nil {
panic("key generation: reading from crypto/rand failed: " + err.Error())
}
return mainBuff
}
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
func PKCS7Pad(in []byte) []byte {
padding := 16 - (len(in) % 16)
if padding == 0 {
padding = 16
}
for i := 0; i < padding; i++ {
in = append(in, byte(padding))
}
return in
}
func PKCS7Unpad(in []byte) []byte {
if len(in) == 0 {
return nil
}
padding := in[len(in)-1]
if int(padding) > len(in) || padding > aes.BlockSize {
return nil
} else if padding == 0 {
return nil
}
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
if in[i] != padding {
return nil
}
}
return in[:len(in)-int(padding)]
}

@ -0,0 +1,114 @@
/*
This file is part of go-ethereum
go-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @authors
* Gustav Simonsson <gustav.simonsson@gmail.com>
* @date 2015
*
*/
package crypto
import (
"code.google.com/p/go-uuid/uuid"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"path"
)
// TODO: rename to KeyStore when replacing existing KeyStore
type KeyStore2 interface {
// create new key using io.Reader entropy source and optionally using auth string
GenerateNewKey(io.Reader, string) (*Key, error)
GetKey(*uuid.UUID, string) (*Key, error) // key from id and auth string
StoreKey(*Key, string) error // store key optionally using auth string
DeleteKey(*uuid.UUID, string) error // delete key by id and auth string
}
type keyStorePlain struct {
keysDirPath string
}
// TODO: copied from cmd/ethereum/flags.go
func DefaultDataDir() string {
usr, _ := user.Current()
return path.Join(usr.HomeDir, ".ethereum")
}
func NewKeyStorePlain(path string) KeyStore2 {
return &keyStorePlain{path}
}
func (ks keyStorePlain) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) {
return GenerateNewKeyDefault(ks, rand, auth)
}
func GenerateNewKeyDefault(ks KeyStore2, rand io.Reader, auth string) (key *Key, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("GenerateNewKey error: %v", r)
}
}()
key = NewKey(rand)
err = ks.StoreKey(key, auth)
return key, err
}
func (ks keyStorePlain) GetKey(keyId *uuid.UUID, auth string) (key *Key, err error) {
fileContent, err := GetKeyFile(ks.keysDirPath, keyId)
if err != nil {
return nil, err
}
key = new(Key)
err = json.Unmarshal(fileContent, key)
return key, err
}
func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) {
keyJSON, err := json.Marshal(key)
if err != nil {
return err
}
err = WriteKeyFile(key.Id.String(), ks.keysDirPath, keyJSON)
return err
}
func (ks keyStorePlain) DeleteKey(keyId *uuid.UUID, auth string) (err error) {
keyDirPath := path.Join(ks.keysDirPath, keyId.String())
err = os.RemoveAll(keyDirPath)
return err
}
func GetKeyFile(keysDirPath string, keyId *uuid.UUID) (fileContent []byte, err error) {
id := keyId.String()
return ioutil.ReadFile(path.Join(keysDirPath, id, id))
}
func WriteKeyFile(id string, keysDirPath string, content []byte) (err error) {
keyDirPath := path.Join(keysDirPath, id)
keyFilePath := path.Join(keyDirPath, id)
err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user
if err != nil {
return err
}
return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user
}

@ -0,0 +1,85 @@
package crypto
import (
crand "crypto/rand"
"reflect"
"testing"
)
func TestKeyStorePlain(t *testing.T) {
ks := NewKeyStorePlain(DefaultDataDir())
pass := "" // not used but required by API
k1, err := ks.GenerateNewKey(crand.Reader, pass)
if err != nil {
t.Fatal(err)
}
k2 := new(Key)
k2, err = ks.GetKey(k1.Id, pass)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.Id, k2.Id) {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
t.Fatal(err)
}
err = ks.DeleteKey(k2.Id, pass)
if err != nil {
t.Fatal(err)
}
}
func TestKeyStorePassphrase(t *testing.T) {
ks := NewKeyStorePassphrase(DefaultDataDir())
pass := "foo"
k1, err := ks.GenerateNewKey(crand.Reader, pass)
if err != nil {
t.Fatal(err)
}
k2 := new(Key)
k2, err = ks.GetKey(k1.Id, pass)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.Id, k2.Id) {
t.Fatal(err)
}
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
t.Fatal(err)
}
err = ks.DeleteKey(k2.Id, pass) // also to clean up created files
if err != nil {
t.Fatal(err)
}
}
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
ks := NewKeyStorePassphrase(DefaultDataDir())
pass := "foo"
k1, err := ks.GenerateNewKey(crand.Reader, pass)
if err != nil {
t.Fatal(err)
}
_, err = ks.GetKey(k1.Id, "bar") // wrong passphrase
if err == nil {
t.Fatal(err)
}
err = ks.DeleteKey(k1.Id, "bar") // wrong passphrase
if err == nil {
t.Fatal(err)
}
err = ks.DeleteKey(k1.Id, pass) // to clean up
if err != nil {
t.Fatal(err)
}
}

@ -18,7 +18,7 @@ import (
) )
const ( const (
seedNodeAddress = "poc-7.ethdev.com:30300" seedNodeAddress = "poc-8.ethdev.com:30303"
) )
type Config struct { type Config struct {
@ -81,7 +81,7 @@ type Ethereum struct {
func New(config *Config) (*Ethereum, error) { func New(config *Config) (*Ethereum, error) {
// Boostrap database // Boostrap database
logger := ethlogger.New(config.DataDir, config.LogFile, config.LogLevel) logger := ethlogger.New(config.DataDir, config.LogFile, config.LogLevel)
db, err := ethdb.NewLDBDatabase("database") db, err := ethdb.NewLDBDatabase("blockchain")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -110,7 +110,7 @@ func New(config *Config) (*Ethereum, error) {
clientId := p2p.NewSimpleClientIdentity(config.Name, config.Version, config.Identifier, keyManager.PublicKey()) clientId := p2p.NewSimpleClientIdentity(config.Name, config.Version, config.Identifier, keyManager.PublicKey())
saveProtocolVersion(db) saveProtocolVersion(db)
ethutil.Config.Db = db //ethutil.Config.Db = db
eth := &Ethereum{ eth := &Ethereum{
shutdownChan: make(chan bool), shutdownChan: make(chan bool),
@ -123,9 +123,9 @@ func New(config *Config) (*Ethereum, error) {
logger: logger, logger: logger,
} }
eth.chainManager = core.NewChainManager(eth.EventMux()) eth.chainManager = core.NewChainManager(db, eth.EventMux())
eth.txPool = core.NewTxPool(eth.EventMux()) eth.txPool = core.NewTxPool(eth.EventMux())
eth.blockProcessor = core.NewBlockProcessor(eth.txPool, eth.chainManager, eth.EventMux()) eth.blockProcessor = core.NewBlockProcessor(db, eth.txPool, eth.chainManager, eth.EventMux())
eth.chainManager.SetProcessor(eth.blockProcessor) eth.chainManager.SetProcessor(eth.blockProcessor)
eth.whisper = whisper.New() eth.whisper = whisper.New()
@ -134,24 +134,20 @@ func New(config *Config) (*Ethereum, error) {
eth.blockPool = NewBlockPool(hasBlock, insertChain, ezp.Verify) eth.blockPool = NewBlockPool(hasBlock, insertChain, ezp.Verify)
ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool) ethProto := EthProtocol(eth.txPool, eth.chainManager, eth.blockPool)
protocols := []p2p.Protocol{ethProto} protocols := []p2p.Protocol{ethProto, eth.whisper.Protocol()}
if config.Shh {
eth.whisper = whisper.New()
protocols = append(protocols, eth.whisper.Protocol())
}
nat, err := p2p.ParseNAT(config.NATType, config.PMPGateway) nat, err := p2p.ParseNAT(config.NATType, config.PMPGateway)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmt.Println(nat)
eth.net = &p2p.Server{ eth.net = &p2p.Server{
Identity: clientId, Identity: clientId,
MaxPeers: config.MaxPeers, MaxPeers: config.MaxPeers,
Protocols: protocols, Protocols: protocols,
Blacklist: eth.blacklist, Blacklist: eth.blacklist,
NAT: nat, NAT: p2p.UPNP(),
NoDial: !config.Dial, NoDial: !config.Dial,
} }
@ -249,7 +245,7 @@ func (s *Ethereum) Start(seed bool) error {
if seed { if seed {
logger.Infof("Connect to seed node %v", seedNodeAddress) logger.Infof("Connect to seed node %v", seedNodeAddress)
if err := s.SuggestPeer(seedNodeAddress); err != nil { if err := s.SuggestPeer(seedNodeAddress); err != nil {
return err logger.Infoln(err)
} }
} }

@ -1,6 +1,7 @@
package eth package eth
import ( import (
"bytes"
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
@ -24,8 +25,8 @@ const (
blocksRequestRepetition = 1 blocksRequestRepetition = 1
blockHashesRequestInterval = 500 // ms blockHashesRequestInterval = 500 // ms
blocksRequestMaxIdleRounds = 100 blocksRequestMaxIdleRounds = 100
cacheTimeout = 3 // minutes blockHashesTimeout = 60 // seconds
blockTimeout = 5 // minutes blocksTimeout = 120 // seconds
) )
type poolNode struct { type poolNode struct {
@ -70,9 +71,14 @@ type BlockPool struct {
type peerInfo struct { type peerInfo struct {
lock sync.RWMutex lock sync.RWMutex
td *big.Int td *big.Int
currentBlock []byte currentBlockHash []byte
id string currentBlock *types.Block
currentBlockC chan *types.Block
parentHash []byte
headSection *section
headSectionC chan *section
id string
requestBlockHashes func([]byte) error requestBlockHashes func([]byte) error
requestBlocks func([][]byte) error requestBlocks func([][]byte) error
@ -203,30 +209,39 @@ func (self *BlockPool) Wait(t time.Duration) {
// AddPeer is called by the eth protocol instance running on the peer after // AddPeer is called by the eth protocol instance running on the peer after
// the status message has been received with total difficulty and current block hash // the status message has been received with total difficulty and current block hash
// AddPeer can only be used once, RemovePeer needs to be called when the peer disconnects // AddPeer can only be used once, RemovePeer needs to be called when the peer disconnects
func (self *BlockPool) AddPeer(td *big.Int, currentBlock []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, peerError func(int, string, ...interface{})) bool { func (self *BlockPool) AddPeer(td *big.Int, currentBlockHash []byte, peerId string, requestBlockHashes func([]byte) error, requestBlocks func([][]byte) error, peerError func(int, string, ...interface{})) (best bool) {
self.peersLock.Lock() self.peersLock.Lock()
defer self.peersLock.Unlock() defer self.peersLock.Unlock()
peer, ok := self.peers[peerId] peer, ok := self.peers[peerId]
if ok { if ok {
poolLogger.Debugf("Update peer %v with td %v and current block %x", peerId, td, currentBlock[:4]) if bytes.Compare(peer.currentBlockHash, currentBlockHash) != 0 {
peer.td = td poolLogger.Debugf("Update peer %v with td %v and current block %s", peerId, td, name(currentBlockHash))
peer.currentBlock = currentBlock peer.lock.Lock()
peer.td = td
peer.currentBlockHash = currentBlockHash
peer.currentBlock = nil
peer.parentHash = nil
peer.headSection = nil
peer.lock.Unlock()
}
} else { } else {
peer = &peerInfo{ peer = &peerInfo{
td: td, td: td,
currentBlock: currentBlock, currentBlockHash: currentBlockHash,
id: peerId, //peer.Identity().Pubkey() id: peerId, //peer.Identity().Pubkey()
requestBlockHashes: requestBlockHashes, requestBlockHashes: requestBlockHashes,
requestBlocks: requestBlocks, requestBlocks: requestBlocks,
peerError: peerError, peerError: peerError,
sections: make(map[string]*section), sections: make(map[string]*section),
currentBlockC: make(chan *types.Block),
headSectionC: make(chan *section),
} }
self.peers[peerId] = peer self.peers[peerId] = peer
poolLogger.Debugf("add new peer %v with td %v and current block %x", peerId, td, currentBlock[:4]) poolLogger.Debugf("add new peer %v with td %v and current block %x", peerId, td, currentBlockHash[:4])
} }
// check peer current head // check peer current head
if self.hasBlock(currentBlock) { if self.hasBlock(currentBlockHash) {
// peer not ahead // peer not ahead
return false return false
} }
@ -234,22 +249,135 @@ func (self *BlockPool) AddPeer(td *big.Int, currentBlock []byte, peerId string,
if self.peer == peer { if self.peer == peer {
// new block update // new block update
// peer is already active best peer, request hashes // peer is already active best peer, request hashes
poolLogger.Debugf("[%s] already the best peer. request hashes from %s", peerId, name(currentBlock)) poolLogger.Debugf("[%s] already the best peer. Request new head section info from %s", peerId, name(currentBlockHash))
peer.requestBlockHashes(currentBlock) peer.headSectionC <- nil
return true best = true
} else {
currentTD := ethutil.Big0
if self.peer != nil {
currentTD = self.peer.td
}
if td.Cmp(currentTD) > 0 {
poolLogger.Debugf("peer %v promoted best peer", peerId)
self.switchPeer(self.peer, peer)
self.peer = peer
best = true
}
} }
return
}
currentTD := ethutil.Big0 func (self *BlockPool) requestHeadSection(peer *peerInfo) {
if self.peer != nil { self.wg.Add(1)
currentTD = self.peer.td self.procWg.Add(1)
} poolLogger.Debugf("[%s] head section at [%s] requesting info", peer.id, name(peer.currentBlockHash))
if td.Cmp(currentTD) > 0 {
poolLogger.Debugf("peer %v promoted best peer", peerId) go func() {
self.switchPeer(self.peer, peer) var idle bool
self.peer = peer peer.lock.RLock()
return true quitC := peer.quitC
} currentBlockHash := peer.currentBlockHash
return false peer.lock.RUnlock()
blockHashesRequestTimer := time.NewTimer(0)
blocksRequestTimer := time.NewTimer(0)
suicide := time.NewTimer(blockHashesTimeout * time.Second)
blockHashesRequestTimer.Stop()
defer blockHashesRequestTimer.Stop()
defer blocksRequestTimer.Stop()
entry := self.get(currentBlockHash)
if entry != nil {
entry.node.lock.RLock()
currentBlock := entry.node.block
entry.node.lock.RUnlock()
if currentBlock != nil {
peer.lock.Lock()
peer.currentBlock = currentBlock
peer.parentHash = currentBlock.ParentHash()
poolLogger.Debugf("[%s] head block [%s] found", peer.id, name(currentBlockHash))
peer.lock.Unlock()
blockHashesRequestTimer.Reset(0)
blocksRequestTimer.Stop()
}
}
LOOP:
for {
select {
case <-self.quit:
break LOOP
case <-quitC:
poolLogger.Debugf("[%s] head section at [%s] incomplete - quit request loop", peer.id, name(currentBlockHash))
break LOOP
case headSection := <-peer.headSectionC:
peer.lock.Lock()
peer.headSection = headSection
if headSection == nil {
oldBlockHash := currentBlockHash
currentBlockHash = peer.currentBlockHash
poolLogger.Debugf("[%s] head section changed [%s] -> [%s]", peer.id, name(oldBlockHash), name(currentBlockHash))
if idle {
idle = false
suicide.Reset(blockHashesTimeout * time.Second)
self.procWg.Add(1)
}
blocksRequestTimer.Reset(blocksRequestInterval * time.Millisecond)
} else {
poolLogger.DebugDetailf("[%s] head section at [%s] created", peer.id, name(currentBlockHash))
if !idle {
idle = true
suicide.Stop()
self.procWg.Done()
}
}
peer.lock.Unlock()
blockHashesRequestTimer.Stop()
case <-blockHashesRequestTimer.C:
poolLogger.DebugDetailf("[%s] head section at [%s] not found, requesting block hashes", peer.id, name(currentBlockHash))
peer.requestBlockHashes(currentBlockHash)
blockHashesRequestTimer.Reset(blockHashesRequestInterval * time.Millisecond)
case currentBlock := <-peer.currentBlockC:
peer.lock.Lock()
peer.currentBlock = currentBlock
peer.parentHash = currentBlock.ParentHash()
poolLogger.DebugDetailf("[%s] head block [%s] found", peer.id, name(currentBlockHash))
peer.lock.Unlock()
if self.hasBlock(currentBlock.ParentHash()) {
if err := self.insertChain(types.Blocks([]*types.Block{currentBlock})); err != nil {
peer.peerError(ErrInvalidBlock, "%v", err)
}
if !idle {
idle = true
suicide.Stop()
self.procWg.Done()
}
} else {
blockHashesRequestTimer.Reset(0)
}
blocksRequestTimer.Stop()
case <-blocksRequestTimer.C:
peer.lock.RLock()
poolLogger.DebugDetailf("[%s] head block [%s] not found, requesting", peer.id, name(currentBlockHash))
peer.requestBlocks([][]byte{peer.currentBlockHash})
peer.lock.RUnlock()
blocksRequestTimer.Reset(blocksRequestInterval * time.Millisecond)
case <-suicide.C:
peer.peerError(ErrInsufficientChainInfo, "peer failed to provide block hashes or head block for block hash %x", currentBlockHash)
break LOOP
}
}
self.wg.Done()
if !idle {
self.procWg.Done()
}
}()
} }
// RemovePeer is called by the eth protocol when the peer disconnects // RemovePeer is called by the eth protocol when the peer disconnects
@ -274,13 +402,13 @@ func (self *BlockPool) RemovePeer(peerId string) {
newPeer = info newPeer = info
} }
} }
self.peer = newPeer
self.switchPeer(peer, newPeer)
if newPeer != nil { if newPeer != nil {
poolLogger.Debugf("peer %v with td %v promoted to best peer", newPeer.id, newPeer.td) poolLogger.Debugf("peer %v with td %v promoted to best peer", newPeer.id, newPeer.td)
} else { } else {
poolLogger.Warnln("no peers") poolLogger.Warnln("no peers")
} }
self.peer = newPeer
self.switchPeer(peer, newPeer)
} }
} }
@ -299,25 +427,56 @@ func (self *BlockPool) AddBlockHashes(next func() ([]byte, bool), peerId string)
return return
} }
// peer is still the best // peer is still the best
poolLogger.Debugf("adding hashes for best peer %s", peerId)
var size, n int var size, n int
var hash []byte var hash []byte
var ok bool var ok, headSection bool
var section, child, parent *section var sec, child, parent *section
var entry *poolEntry var entry *poolEntry
var nodes []*poolNode var nodes []*poolNode
bestPeer := peer
hash, ok = next()
peer.lock.Lock()
if bytes.Compare(peer.parentHash, hash) == 0 {
if self.hasBlock(peer.currentBlockHash) {
return
}
poolLogger.Debugf("adding hashes at chain head for best peer %s starting from [%s]", peerId, name(peer.currentBlockHash))
headSection = true
if entry := self.get(peer.currentBlockHash); entry == nil {
node := &poolNode{
hash: peer.currentBlockHash,
block: peer.currentBlock,
peer: peerId,
blockBy: peerId,
}
if size == 0 {
sec = newSection()
}
nodes = append(nodes, node)
size++
n++
} else {
child = entry.section
}
} else {
poolLogger.Debugf("adding hashes for best peer %s starting from [%s]", peerId, name(hash))
}
quitC := peer.quitC
peer.lock.Unlock()
LOOP: LOOP:
// iterate using next (rlp stream lazy decoder) feeding hashesC // iterate using next (rlp stream lazy decoder) feeding hashesC
for hash, ok = next(); ok; hash, ok = next() { for ; ok; hash, ok = next() {
n++ n++
select { select {
case <-self.quit: case <-self.quit:
return return
case <-peer.quitC: case <-quitC:
// if the peer is demoted, no more hashes taken // if the peer is demoted, no more hashes taken
peer = nil bestPeer = nil
break LOOP break LOOP
default: default:
} }
@ -325,8 +484,8 @@ LOOP:
// check if known block connecting the downloaded chain to our blockchain // check if known block connecting the downloaded chain to our blockchain
poolLogger.DebugDetailf("[%s] known block", name(hash)) poolLogger.DebugDetailf("[%s] known block", name(hash))
// mark child as absolute pool root with parent known to blockchain // mark child as absolute pool root with parent known to blockchain
if section != nil { if sec != nil {
self.connectToBlockChain(section) self.connectToBlockChain(sec)
} else { } else {
if child != nil { if child != nil {
self.connectToBlockChain(child) self.connectToBlockChain(child)
@ -340,6 +499,7 @@ LOOP:
// reached a known chain in the pool // reached a known chain in the pool
if entry.node == entry.section.bottom && n == 1 { if entry.node == entry.section.bottom && n == 1 {
// the first block hash received is an orphan in the pool, so rejoice and continue // the first block hash received is an orphan in the pool, so rejoice and continue
poolLogger.DebugDetailf("[%s] connecting child section", sectionName(entry.section))
child = entry.section child = entry.section
continue LOOP continue LOOP
} }
@ -353,7 +513,7 @@ LOOP:
peer: peerId, peer: peerId,
} }
if size == 0 { if size == 0 {
section = newSection() sec = newSection()
} }
nodes = append(nodes, node) nodes = append(nodes, node)
size++ size++
@ -379,10 +539,10 @@ LOOP:
} }
if size > 0 { if size > 0 {
self.processSection(section, nodes) self.processSection(sec, nodes)
poolLogger.DebugDetailf("[%s]->[%s](%v)->[%s] new chain section", sectionName(parent), sectionName(section), size, sectionName(child)) poolLogger.DebugDetailf("[%s]->[%s](%v)->[%s] new chain section", sectionName(parent), sectionName(sec), size, sectionName(child))
self.link(parent, section) self.link(parent, sec)
self.link(section, child) self.link(sec, child)
} else { } else {
poolLogger.DebugDetailf("[%s]->[%s] connecting known sections", sectionName(parent), sectionName(child)) poolLogger.DebugDetailf("[%s]->[%s] connecting known sections", sectionName(parent), sectionName(child))
self.link(parent, child) self.link(parent, child)
@ -390,15 +550,31 @@ LOOP:
self.chainLock.Unlock() self.chainLock.Unlock()
if parent != nil && peer != nil { if parent != nil && bestPeer != nil {
self.activateChain(parent, peer) self.activateChain(parent, peer)
poolLogger.Debugf("[%s] activate parent section [%s]", name(parent.top.hash), sectionName(parent)) poolLogger.Debugf("[%s] activate parent section [%s]", name(parent.top.hash), sectionName(parent))
} }
if section != nil { if sec != nil {
peer.addSection(section.top.hash, section) peer.addSection(sec.top.hash, sec)
section.controlC <- peer // request next section here once, only repeat if bottom block arrives,
poolLogger.Debugf("[%s] activate new section", sectionName(section)) // otherwise no way to check if it arrived
peer.requestBlockHashes(sec.bottom.hash)
sec.controlC <- bestPeer
poolLogger.Debugf("[%s] activate new section", sectionName(sec))
}
if headSection {
var headSec *section
switch {
case sec != nil:
headSec = sec
case child != nil:
headSec = child
default:
headSec = parent
}
peer.headSectionC <- headSec
} }
} }
@ -426,14 +602,21 @@ func sectionName(section *section) (name string) {
// only the first PoW-valid block for a hash is considered legit // only the first PoW-valid block for a hash is considered legit
func (self *BlockPool) AddBlock(block *types.Block, peerId string) { func (self *BlockPool) AddBlock(block *types.Block, peerId string) {
hash := block.Hash() hash := block.Hash()
if self.hasBlock(hash) { self.peersLock.Lock()
poolLogger.DebugDetailf("block [%s] already known", name(hash)) peer := self.peer
return self.peersLock.Unlock()
}
entry := self.get(hash) entry := self.get(hash)
if bytes.Compare(hash, peer.currentBlockHash) == 0 {
poolLogger.Debugf("add head block [%s] for peer %s", name(hash), peerId)
peer.currentBlockC <- block
} else {
if entry == nil {
poolLogger.Warnf("unrequested block [%s] by peer %s", name(hash), peerId)
self.peerError(peerId, ErrUnrequestedBlock, "%x", hash)
}
}
if entry == nil { if entry == nil {
poolLogger.Warnf("unrequested block [%x] by peer %s", hash, peerId)
self.peerError(peerId, ErrUnrequestedBlock, "%x", hash)
return return
} }
@ -443,17 +626,21 @@ func (self *BlockPool) AddBlock(block *types.Block, peerId string) {
// check if block already present // check if block already present
if node.block != nil { if node.block != nil {
poolLogger.DebugDetailf("block [%x] already sent by %s", name(hash), node.blockBy) poolLogger.DebugDetailf("block [%s] already sent by %s", name(hash), node.blockBy)
return return
} }
// validate block for PoW if self.hasBlock(hash) {
if !self.verifyPoW(block) { poolLogger.DebugDetailf("block [%s] already known", name(hash))
poolLogger.Warnf("invalid pow on block [%x] by peer %s", hash, peerId) } else {
self.peerError(peerId, ErrInvalidPoW, "%x", hash)
return
}
// validate block for PoW
if !self.verifyPoW(block) {
poolLogger.Warnf("invalid pow on block [%s] by peer %s", name(hash), peerId)
self.peerError(peerId, ErrInvalidPoW, "%x", hash)
return
}
}
poolLogger.Debugf("added block [%s] sent by peer %s", name(hash), peerId) poolLogger.Debugf("added block [%s] sent by peer %s", name(hash), peerId)
node.block = block node.block = block
node.blockBy = peerId node.blockBy = peerId
@ -544,23 +731,23 @@ LOOP:
// - when turned off (if peer disconnects and new peer connects with alternative chain), no blockrequests are made but absolute expiry timer is ticking // - when turned off (if peer disconnects and new peer connects with alternative chain), no blockrequests are made but absolute expiry timer is ticking
// - when turned back on it recursively calls itself on the root of the next chain section // - when turned back on it recursively calls itself on the root of the next chain section
// - when exits, signals to // - when exits, signals to
func (self *BlockPool) processSection(section *section, nodes []*poolNode) { func (self *BlockPool) processSection(sec *section, nodes []*poolNode) {
for i, node := range nodes { for i, node := range nodes {
entry := &poolEntry{node: node, section: section, index: i} entry := &poolEntry{node: node, section: sec, index: i}
self.set(node.hash, entry) self.set(node.hash, entry)
} }
section.bottom = nodes[len(nodes)-1] sec.bottom = nodes[len(nodes)-1]
section.top = nodes[0] sec.top = nodes[0]
section.nodes = nodes sec.nodes = nodes
poolLogger.DebugDetailf("[%s] setup section process", sectionName(section)) poolLogger.DebugDetailf("[%s] setup section process", sectionName(sec))
self.wg.Add(1) self.wg.Add(1)
go func() { go func() {
// absolute time after which sub-chain is killed if not complete (some blocks are missing) // absolute time after which sub-chain is killed if not complete (some blocks are missing)
suicideTimer := time.After(blockTimeout * time.Minute) suicideTimer := time.After(blocksTimeout * time.Second)
var peer, newPeer *peerInfo var peer, newPeer *peerInfo
@ -580,21 +767,23 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
var insertChain bool var insertChain bool
var quitC chan bool var quitC chan bool
var blockChainC = section.blockChainC var blockChainC = sec.blockChainC
var parentHash []byte
LOOP: LOOP:
for { for {
if insertChain { if insertChain {
insertChain = false insertChain = false
rest, err := self.addSectionToBlockChain(section) rest, err := self.addSectionToBlockChain(sec)
if err != nil { if err != nil {
close(section.suicideC) close(sec.suicideC)
continue LOOP continue LOOP
} }
if rest == 0 { if rest == 0 {
blocksRequestsComplete = true blocksRequestsComplete = true
child := self.getChild(section) child := self.getChild(sec)
if child != nil { if child != nil {
self.connectToBlockChain(child) self.connectToBlockChain(child)
} }
@ -603,7 +792,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
if blockHashesRequestsComplete && blocksRequestsComplete { if blockHashesRequestsComplete && blocksRequestsComplete {
// not waiting for hashes any more // not waiting for hashes any more
poolLogger.Debugf("[%s] section complete %v blocks retrieved (%v attempts), hash requests complete on root (%v attempts)", sectionName(section), depth, blocksRequests, blockHashesRequests) poolLogger.Debugf("[%s] section complete %v blocks retrieved (%v attempts), hash requests complete on root (%v attempts)", sectionName(sec), depth, blocksRequests, blockHashesRequests)
break LOOP break LOOP
} // otherwise suicide if no hashes coming } // otherwise suicide if no hashes coming
@ -611,11 +800,12 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
// went through all blocks in section // went through all blocks in section
if missing == 0 { if missing == 0 {
// no missing blocks // no missing blocks
poolLogger.DebugDetailf("[%s] got all blocks. process complete (%v total blocksRequests): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) poolLogger.DebugDetailf("[%s] got all blocks. process complete (%v total blocksRequests): missing %v/%v/%v", sectionName(sec), blocksRequests, missing, lastMissing, depth)
blocksRequestsComplete = true blocksRequestsComplete = true
blocksRequestTimer = nil blocksRequestTimer = nil
blocksRequestTime = false blocksRequestTime = false
} else { } else {
poolLogger.DebugDetailf("[%s] section checked: missing %v/%v/%v", sectionName(sec), missing, lastMissing, depth)
// some missing blocks // some missing blocks
blocksRequests++ blocksRequests++
if len(hashes) > 0 { if len(hashes) > 0 {
@ -630,8 +820,8 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
idle++ idle++
// too many idle rounds // too many idle rounds
if idle >= blocksRequestMaxIdleRounds { if idle >= blocksRequestMaxIdleRounds {
poolLogger.DebugDetailf("[%s] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", sectionName(section), idle, blocksRequests, missing, lastMissing, depth) poolLogger.DebugDetailf("[%s] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", sectionName(sec), idle, blocksRequests, missing, lastMissing, depth)
close(section.suicideC) close(sec.suicideC)
} }
} else { } else {
idle = 0 idle = 0
@ -653,22 +843,39 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
// //
if ready && blocksRequestTime && !blocksRequestsComplete { if ready && blocksRequestTime && !blocksRequestsComplete {
poolLogger.DebugDetailf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) poolLogger.DebugDetailf("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionName(sec), blocksRequests, missing, lastMissing, depth)
blocksRequestTimer = time.After(blocksRequestInterval * time.Millisecond) blocksRequestTimer = time.After(blocksRequestInterval * time.Millisecond)
blocksRequestTime = false blocksRequestTime = false
processC = offC processC = offC
} }
if blockHashesRequestTime { if blockHashesRequestTime {
if self.getParent(section) != nil { var parentSection = self.getParent(sec)
if parentSection == nil {
if parent := self.get(parentHash); parent != nil {
parentSection = parent.section
self.chainLock.Lock()
self.link(parentSection, sec)
self.chainLock.Unlock()
} else {
if self.hasBlock(parentHash) {
insertChain = true
blockHashesRequestTime = false
blockHashesRequestTimer = nil
blockHashesRequestsComplete = true
continue LOOP
}
}
}
if parentSection != nil {
// if not root of chain, switch off // if not root of chain, switch off
poolLogger.DebugDetailf("[%s] parent found, hash requests deactivated (after %v total attempts)\n", sectionName(section), blockHashesRequests) poolLogger.DebugDetailf("[%s] parent found, hash requests deactivated (after %v total attempts)\n", sectionName(sec), blockHashesRequests)
blockHashesRequestTimer = nil blockHashesRequestTimer = nil
blockHashesRequestsComplete = true blockHashesRequestsComplete = true
} else { } else {
blockHashesRequests++ blockHashesRequests++
poolLogger.Debugf("[%s] hash request on root (%v total attempts)\n", sectionName(section), blockHashesRequests) poolLogger.Debugf("[%s] hash request on root (%v total attempts)\n", sectionName(sec), blockHashesRequests)
peer.requestBlockHashes(section.bottom.hash) peer.requestBlockHashes(sec.bottom.hash)
blockHashesRequestTimer = time.After(blockHashesRequestInterval * time.Millisecond) blockHashesRequestTimer = time.After(blockHashesRequestInterval * time.Millisecond)
} }
blockHashesRequestTime = false blockHashesRequestTime = false
@ -682,27 +889,27 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
// peer quit or demoted, put section in idle mode // peer quit or demoted, put section in idle mode
quitC = nil quitC = nil
go func() { go func() {
section.controlC <- nil sec.controlC <- nil
}() }()
case <-self.purgeC: case <-self.purgeC:
suicideTimer = time.After(0) suicideTimer = time.After(0)
case <-suicideTimer: case <-suicideTimer:
close(section.suicideC) close(sec.suicideC)
poolLogger.Debugf("[%s] timeout. (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) poolLogger.Debugf("[%s] timeout. (%v total attempts): missing %v/%v/%v", sectionName(sec), blocksRequests, missing, lastMissing, depth)
case <-section.suicideC: case <-sec.suicideC:
poolLogger.Debugf("[%s] suicide", sectionName(section)) poolLogger.Debugf("[%s] suicide", sectionName(sec))
// first delink from child and parent under chainlock // first delink from child and parent under chainlock
self.chainLock.Lock() self.chainLock.Lock()
self.link(nil, section) self.link(nil, sec)
self.link(section, nil) self.link(sec, nil)
self.chainLock.Unlock() self.chainLock.Unlock()
// delete node entries from pool index under pool lock // delete node entries from pool index under pool lock
self.lock.Lock() self.lock.Lock()
for _, node := range section.nodes { for _, node := range sec.nodes {
delete(self.pool, string(node.hash)) delete(self.pool, string(node.hash))
} }
self.lock.Unlock() self.lock.Unlock()
@ -710,20 +917,20 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
break LOOP break LOOP
case <-blocksRequestTimer: case <-blocksRequestTimer:
poolLogger.DebugDetailf("[%s] block request time", sectionName(section)) poolLogger.DebugDetailf("[%s] block request time", sectionName(sec))
blocksRequestTime = true blocksRequestTime = true
case <-blockHashesRequestTimer: case <-blockHashesRequestTimer:
poolLogger.DebugDetailf("[%s] hash request time", sectionName(section)) poolLogger.DebugDetailf("[%s] hash request time", sectionName(sec))
blockHashesRequestTime = true blockHashesRequestTime = true
case newPeer = <-section.controlC: case newPeer = <-sec.controlC:
// active -> idle // active -> idle
if peer != nil && newPeer == nil { if peer != nil && newPeer == nil {
self.procWg.Done() self.procWg.Done()
if init { if init {
poolLogger.Debugf("[%s] idle mode (%v total attempts): missing %v/%v/%v", sectionName(section), blocksRequests, missing, lastMissing, depth) poolLogger.Debugf("[%s] idle mode (%v total attempts): missing %v/%v/%v", sectionName(sec), blocksRequests, missing, lastMissing, depth)
} }
blocksRequestTime = false blocksRequestTime = false
blocksRequestTimer = nil blocksRequestTimer = nil
@ -739,11 +946,11 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
if peer == nil && newPeer != nil { if peer == nil && newPeer != nil {
self.procWg.Add(1) self.procWg.Add(1)
poolLogger.Debugf("[%s] active mode", sectionName(section)) poolLogger.Debugf("[%s] active mode", sectionName(sec))
if !blocksRequestsComplete { if !blocksRequestsComplete {
blocksRequestTime = true blocksRequestTime = true
} }
if !blockHashesRequestsComplete { if !blockHashesRequestsComplete && parentHash != nil {
blockHashesRequestTime = true blockHashesRequestTime = true
} }
if !init { if !init {
@ -753,13 +960,13 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
missing = 0 missing = 0
self.wg.Add(1) self.wg.Add(1)
self.procWg.Add(1) self.procWg.Add(1)
depth = len(section.nodes) depth = len(sec.nodes)
lastMissing = depth lastMissing = depth
// if not run at least once fully, launch iterator // if not run at least once fully, launch iterator
go func() { go func() {
var node *poolNode var node *poolNode
IT: IT:
for _, node = range section.nodes { for _, node = range sec.nodes {
select { select {
case processC <- node: case processC <- node:
case <-self.quit: case <-self.quit:
@ -771,7 +978,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
self.procWg.Done() self.procWg.Done()
}() }()
} else { } else {
poolLogger.Debugf("[%s] restore earlier state", sectionName(section)) poolLogger.Debugf("[%s] restore earlier state", sectionName(sec))
processC = offC processC = offC
} }
} }
@ -781,7 +988,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
} }
peer = newPeer peer = newPeer
case waiter := <-section.forkC: case waiter := <-sec.forkC:
// this case just blocks the process until section is split at the fork // this case just blocks the process until section is split at the fork
<-waiter <-waiter
init = false init = false
@ -794,7 +1001,7 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
init = true init = true
done = true done = true
processC = make(chan *poolNode, missing) processC = make(chan *poolNode, missing)
poolLogger.DebugDetailf("[%s] section initalised: missing %v/%v/%v", sectionName(section), missing, lastMissing, depth) poolLogger.DebugDetailf("[%s] section initalised: missing %v/%v/%v", sectionName(sec), missing, lastMissing, depth)
continue LOOP continue LOOP
} }
if ready { if ready {
@ -811,17 +1018,24 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
missing++ missing++
hashes = append(hashes, node.hash) hashes = append(hashes, node.hash)
if len(hashes) == blockBatchSize { if len(hashes) == blockBatchSize {
poolLogger.Debugf("[%s] request %v missing blocks", sectionName(section), len(hashes)) poolLogger.Debugf("[%s] request %v missing blocks", sectionName(sec), len(hashes))
self.requestBlocks(blocksRequests, hashes) self.requestBlocks(blocksRequests, hashes)
hashes = nil hashes = nil
} }
missingC <- node missingC <- node
} else { } else {
if blockChainC == nil && i == lastMissing { if i == lastMissing {
insertChain = true if blockChainC == nil {
insertChain = true
} else {
if parentHash == nil {
parentHash = block.ParentHash()
poolLogger.Debugf("[%s] found root block [%s]", sectionName(sec), name(parentHash))
blockHashesRequestTime = true
}
}
} }
} }
poolLogger.Debugf("[%s] %v/%v/%v/%v", sectionName(section), i, missing, lastMissing, depth)
if i == lastMissing && init { if i == lastMissing && init {
done = true done = true
} }
@ -829,23 +1043,22 @@ func (self *BlockPool) processSection(section *section, nodes []*poolNode) {
case <-blockChainC: case <-blockChainC:
// closed blockChain channel indicates that the blockpool is reached // closed blockChain channel indicates that the blockpool is reached
// connected to the blockchain, insert the longest chain of blocks // connected to the blockchain, insert the longest chain of blocks
poolLogger.Debugf("[%s] reached blockchain", sectionName(section)) poolLogger.Debugf("[%s] reached blockchain", sectionName(sec))
blockChainC = nil blockChainC = nil
// switch off hash requests in case they were on // switch off hash requests in case they were on
blockHashesRequestTime = false blockHashesRequestTime = false
blockHashesRequestTimer = nil blockHashesRequestTimer = nil
blockHashesRequestsComplete = true blockHashesRequestsComplete = true
// section root has block // section root has block
if len(section.nodes) > 0 && section.nodes[len(section.nodes)-1].block != nil { if len(sec.nodes) > 0 && sec.nodes[len(sec.nodes)-1].block != nil {
insertChain = true insertChain = true
} }
continue LOOP continue LOOP
} // select } // select
} // for } // for
poolLogger.Debugf("[%s] section complete: %v block hashes requests - %v block requests - missing %v/%v/%v", sectionName(section), blockHashesRequests, blocksRequests, missing, lastMissing, depth)
close(section.offC) close(sec.offC)
self.wg.Done() self.wg.Done()
if peer != nil { if peer != nil {
@ -917,22 +1130,28 @@ func (self *peerInfo) addSection(hash []byte, section *section) (found *section)
defer self.lock.Unlock() defer self.lock.Unlock()
key := string(hash) key := string(hash)
found = self.sections[key] found = self.sections[key]
poolLogger.DebugDetailf("[%s] section process %s registered", sectionName(section), self.id) poolLogger.DebugDetailf("[%s] section process stored for %s", sectionName(section), self.id)
self.sections[key] = section self.sections[key] = section
return return
} }
func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) { func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) {
if newPeer != nil { if newPeer != nil {
entry := self.get(newPeer.currentBlock) newPeer.quitC = make(chan bool)
if entry == nil {
poolLogger.Debugf("[%s] head block [%s] not found, requesting hashes", newPeer.id, name(newPeer.currentBlock))
newPeer.requestBlockHashes(newPeer.currentBlock)
} else {
poolLogger.Debugf("[%s] head block [%s] found, activate chain at section [%s]", newPeer.id, name(newPeer.currentBlock), sectionName(entry.section))
self.activateChain(entry.section, newPeer)
}
poolLogger.DebugDetailf("[%s] activate section processes", newPeer.id) poolLogger.DebugDetailf("[%s] activate section processes", newPeer.id)
var addSections []*section
for hash, section := range newPeer.sections {
// split sections get reorganised here
if string(section.top.hash) != hash {
addSections = append(addSections, section)
if entry := self.get([]byte(hash)); entry != nil {
addSections = append(addSections, entry.section)
}
}
}
for _, section := range addSections {
newPeer.sections[string(section.top.hash)] = section
}
for hash, section := range newPeer.sections { for hash, section := range newPeer.sections {
// this will block if section process is waiting for peer lock // this will block if section process is waiting for peer lock
select { select {
@ -940,12 +1159,26 @@ func (self *BlockPool) switchPeer(oldPeer, newPeer *peerInfo) {
poolLogger.DebugDetailf("[%s][%x] section process complete - remove", newPeer.id, hash[:4]) poolLogger.DebugDetailf("[%s][%x] section process complete - remove", newPeer.id, hash[:4])
delete(newPeer.sections, hash) delete(newPeer.sections, hash)
case section.controlC <- newPeer: case section.controlC <- newPeer:
poolLogger.DebugDetailf("[%s][%x] registered peer with section", newPeer.id, hash[:4]) poolLogger.DebugDetailf("[%s][%x] activates section [%s]", newPeer.id, hash[:4], sectionName(section))
} }
} }
newPeer.quitC = make(chan bool) newPeer.lock.Lock()
headSection := newPeer.headSection
currentBlockHash := newPeer.currentBlockHash
newPeer.lock.Unlock()
if headSection == nil {
poolLogger.DebugDetailf("[%s] head section for [%s] not created, requesting info", newPeer.id, name(currentBlockHash))
self.requestHeadSection(newPeer)
} else {
if entry := self.get(currentBlockHash); entry != nil {
headSection = entry.section
}
poolLogger.DebugDetailf("[%s] activate chain at head section [%s] for current head [%s]", newPeer.id, sectionName(headSection), name(currentBlockHash))
self.activateChain(headSection, newPeer)
}
} }
if oldPeer != nil { if oldPeer != nil {
poolLogger.DebugDetailf("[%s] quit section processes", oldPeer.id)
close(oldPeer.quitC) close(oldPeer.quitC)
} }
} }

@ -18,7 +18,7 @@ import (
const waitTimeout = 60 // seconds const waitTimeout = 60 // seconds
var logsys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugLevel)) var logsys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel))
var ini = false var ini = false
@ -336,12 +336,12 @@ func (self *peerTester) AddPeer() bool {
// peer sends blockhashes if and when gets a request // peer sends blockhashes if and when gets a request
func (self *peerTester) AddBlockHashes(indexes ...int) { func (self *peerTester) AddBlockHashes(indexes ...int) {
i := 0
fmt.Printf("ready to add block hashes %v\n", indexes) fmt.Printf("ready to add block hashes %v\n", indexes)
self.waitBlockHashesRequests(indexes[0]) self.waitBlockHashesRequests(indexes[0])
fmt.Printf("adding block hashes %v\n", indexes) fmt.Printf("adding block hashes %v\n", indexes)
hashes := self.hashPool.indexesToHashes(indexes) hashes := self.hashPool.indexesToHashes(indexes)
i := 1
next := func() (hash []byte, ok bool) { next := func() (hash []byte, ok bool) {
if i < len(hashes) { if i < len(hashes) {
hash = hashes[i] hash = hashes[i]
@ -415,7 +415,7 @@ func TestAddPeer(t *testing.T) {
if blockPool.peer.id != "peer0" { if blockPool.peer.id != "peer0" {
t.Errorf("peer0 (TD=1) not set as best") t.Errorf("peer0 (TD=1) not set as best")
} }
peer0.checkBlockHashesRequests(0) // peer0.checkBlockHashesRequests(0)
best = peer2.AddPeer() best = peer2.AddPeer()
if !best { if !best {
@ -424,7 +424,7 @@ func TestAddPeer(t *testing.T) {
if blockPool.peer.id != "peer2" { if blockPool.peer.id != "peer2" {
t.Errorf("peer2 (TD=3) not set as best") t.Errorf("peer2 (TD=3) not set as best")
} }
peer2.checkBlockHashesRequests(2) peer2.waitBlocksRequests(2)
best = peer1.AddPeer() best = peer1.AddPeer()
if best { if best {
@ -449,7 +449,7 @@ func TestAddPeer(t *testing.T) {
if blockPool.peer.td.Cmp(big.NewInt(int64(4))) != 0 { if blockPool.peer.td.Cmp(big.NewInt(int64(4))) != 0 {
t.Errorf("peer2 TD not updated") t.Errorf("peer2 TD not updated")
} }
peer2.checkBlockHashesRequests(2, 3) peer2.waitBlocksRequests(3)
peer1.td = 3 peer1.td = 3
peer1.currentBlock = 2 peer1.currentBlock = 2
@ -474,7 +474,7 @@ func TestAddPeer(t *testing.T) {
if blockPool.peer.id != "peer1" { if blockPool.peer.id != "peer1" {
t.Errorf("existing peer1 (TD=3) should be set as best peer") t.Errorf("existing peer1 (TD=3) should be set as best peer")
} }
peer1.checkBlockHashesRequests(2) peer1.waitBlocksRequests(2)
blockPool.RemovePeer("peer1") blockPool.RemovePeer("peer1")
peer, best = blockPool.getPeer("peer1") peer, best = blockPool.getPeer("peer1")
@ -485,6 +485,7 @@ func TestAddPeer(t *testing.T) {
if blockPool.peer.id != "peer0" { if blockPool.peer.id != "peer0" {
t.Errorf("existing peer0 (TD=1) should be set as best peer") t.Errorf("existing peer0 (TD=1) should be set as best peer")
} }
peer0.waitBlocksRequests(0)
blockPool.RemovePeer("peer0") blockPool.RemovePeer("peer0")
peer, best = blockPool.getPeer("peer0") peer, best = blockPool.getPeer("peer0")
@ -502,7 +503,7 @@ func TestAddPeer(t *testing.T) {
if blockPool.peer.id != "peer0" { if blockPool.peer.id != "peer0" {
t.Errorf("peer0 (TD=1) should be set as best") t.Errorf("peer0 (TD=1) should be set as best")
} }
peer0.checkBlockHashesRequests(0, 0, 3) peer0.waitBlocksRequests(3)
blockPool.Stop() blockPool.Stop()
@ -513,17 +514,36 @@ func TestPeerWithKnownBlock(t *testing.T) {
_, blockPool, blockPoolTester := newTestBlockPool(t) _, blockPool, blockPoolTester := newTestBlockPool(t)
blockPoolTester.refBlockChain[0] = nil blockPoolTester.refBlockChain[0] = nil
blockPoolTester.blockChain[0] = nil blockPoolTester.blockChain[0] = nil
// hashPool, blockPool, blockPoolTester := newTestBlockPool()
blockPool.Start() blockPool.Start()
peer0 := blockPoolTester.newPeer("0", 1, 0) peer0 := blockPoolTester.newPeer("0", 1, 0)
peer0.AddPeer() peer0.AddPeer()
blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop() blockPool.Stop()
// no request on known block // no request on known block
peer0.checkBlockHashesRequests() peer0.checkBlockHashesRequests()
} }
func TestPeerWithKnownParentBlock(t *testing.T) {
logInit()
_, blockPool, blockPoolTester := newTestBlockPool(t)
blockPoolTester.initRefBlockChain(1)
blockPoolTester.blockChain[0] = nil
blockPool.Start()
peer0 := blockPoolTester.newPeer("0", 1, 1)
peer0.AddPeer()
peer0.AddBlocks(0, 1)
blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop()
peer0.checkBlocksRequests([]int{1})
peer0.checkBlockHashesRequests()
blockPoolTester.refBlockChain[1] = []int{}
blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}
func TestSimpleChain(t *testing.T) { func TestSimpleChain(t *testing.T) {
logInit() logInit()
_, blockPool, blockPoolTester := newTestBlockPool(t) _, blockPool, blockPoolTester := newTestBlockPool(t)
@ -534,8 +554,9 @@ func TestSimpleChain(t *testing.T) {
peer1 := blockPoolTester.newPeer("peer1", 1, 2) peer1 := blockPoolTester.newPeer("peer1", 1, 2)
peer1.AddPeer() peer1.AddPeer()
peer1.AddBlocks(1, 2)
go peer1.AddBlockHashes(2, 1, 0) go peer1.AddBlockHashes(2, 1, 0)
peer1.AddBlocks(0, 1, 2) peer1.AddBlocks(0, 1)
blockPool.Wait(waitTimeout * time.Second) blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop() blockPool.Stop()
@ -543,6 +564,26 @@ func TestSimpleChain(t *testing.T) {
blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
} }
func TestChainConnectingWithParentHash(t *testing.T) {
logInit()
_, blockPool, blockPoolTester := newTestBlockPool(t)
blockPoolTester.blockChain[0] = nil
blockPoolTester.initRefBlockChain(3)
blockPool.Start()
peer1 := blockPoolTester.newPeer("peer1", 1, 3)
peer1.AddPeer()
go peer1.AddBlocks(2, 3)
go peer1.AddBlockHashes(3, 2, 1)
peer1.AddBlocks(0, 1, 2)
blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop()
blockPoolTester.refBlockChain[3] = []int{}
blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}
func TestInvalidBlock(t *testing.T) { func TestInvalidBlock(t *testing.T) {
logInit() logInit()
_, blockPool, blockPoolTester := newTestBlockPool(t) _, blockPool, blockPoolTester := newTestBlockPool(t)
@ -554,8 +595,9 @@ func TestInvalidBlock(t *testing.T) {
peer1 := blockPoolTester.newPeer("peer1", 1, 3) peer1 := blockPoolTester.newPeer("peer1", 1, 3)
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlocks(2, 3)
go peer1.AddBlockHashes(3, 2, 1, 0) go peer1.AddBlockHashes(3, 2, 1, 0)
peer1.AddBlocks(0, 1, 2, 3) peer1.AddBlocks(0, 1, 2)
blockPool.Wait(waitTimeout * time.Second) blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop() blockPool.Stop()
@ -566,7 +608,7 @@ func TestInvalidBlock(t *testing.T) {
t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInvalidBlock) t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInvalidBlock)
} }
} else { } else {
t.Errorf("expected invalid block error, got nothing") t.Errorf("expected invalid block error, got nothing %v", peer1.peerErrors)
} }
} }
@ -579,7 +621,7 @@ func TestVerifyPoW(t *testing.T) {
blockPoolTester.blockPool.verifyPoW = func(b pow.Block) bool { blockPoolTester.blockPool.verifyPoW = func(b pow.Block) bool {
bb, _ := b.(*types.Block) bb, _ := b.(*types.Block)
indexes := blockPoolTester.hashPool.hashesToIndexes([][]byte{bb.Hash()}) indexes := blockPoolTester.hashPool.hashesToIndexes([][]byte{bb.Hash()})
if indexes[0] == 1 && !first { if indexes[0] == 2 && !first {
first = true first = true
return false return false
} else { } else {
@ -590,15 +632,17 @@ func TestVerifyPoW(t *testing.T) {
blockPool.Start() blockPool.Start()
peer1 := blockPoolTester.newPeer("peer1", 1, 2) peer1 := blockPoolTester.newPeer("peer1", 1, 3)
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlockHashes(2, 1, 0) go peer1.AddBlocks(2, 3)
go peer1.AddBlockHashes(3, 2, 1, 0)
peer1.AddBlocks(0, 1, 2) peer1.AddBlocks(0, 1, 2)
peer1.AddBlocks(0, 1)
blockPool.Wait(waitTimeout * time.Second) // blockPool.Wait(waitTimeout * time.Second)
time.Sleep(1 * time.Second)
blockPool.Stop() blockPool.Stop()
blockPoolTester.refBlockChain[2] = []int{} blockPoolTester.refBlockChain[1] = []int{}
delete(blockPoolTester.refBlockChain, 2)
blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
if len(peer1.peerErrors) == 1 { if len(peer1.peerErrors) == 1 {
if peer1.peerErrors[0] != ErrInvalidPoW { if peer1.peerErrors[0] != ErrInvalidPoW {
@ -620,8 +664,9 @@ func TestMultiSectionChain(t *testing.T) {
peer1 := blockPoolTester.newPeer("peer1", 1, 5) peer1 := blockPoolTester.newPeer("peer1", 1, 5)
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlocks(4, 5)
go peer1.AddBlockHashes(5, 4, 3) go peer1.AddBlockHashes(5, 4, 3)
go peer1.AddBlocks(2, 3, 4, 5) go peer1.AddBlocks(2, 3, 4)
go peer1.AddBlockHashes(3, 2, 1, 0) go peer1.AddBlockHashes(3, 2, 1, 0)
peer1.AddBlocks(0, 1, 2) peer1.AddBlocks(0, 1, 2)
@ -641,14 +686,17 @@ func TestNewBlocksOnPartialChain(t *testing.T) {
peer1 := blockPoolTester.newPeer("peer1", 1, 5) peer1 := blockPoolTester.newPeer("peer1", 1, 5)
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlocks(4, 5) // partially complete section
go peer1.AddBlockHashes(5, 4, 3) go peer1.AddBlockHashes(5, 4, 3)
peer1.AddBlocks(2, 3) // partially complete section peer1.AddBlocks(3, 4) // partially complete section
// peer1 found new blocks // peer1 found new blocks
peer1.td = 2 peer1.td = 2
peer1.currentBlock = 7 peer1.currentBlock = 7
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlocks(6, 7)
go peer1.AddBlockHashes(7, 6, 5) go peer1.AddBlockHashes(7, 6, 5)
go peer1.AddBlocks(3, 4, 5, 6, 7) go peer1.AddBlocks(2, 3)
go peer1.AddBlocks(5, 6)
go peer1.AddBlockHashes(3, 2, 1, 0) // tests that hash request from known chain root is remembered go peer1.AddBlockHashes(3, 2, 1, 0) // tests that hash request from known chain root is remembered
peer1.AddBlocks(0, 1, 2) peer1.AddBlocks(0, 1, 2)
@ -658,35 +706,37 @@ func TestNewBlocksOnPartialChain(t *testing.T) {
blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
} }
func TestPeerSwitch(t *testing.T) { func TestPeerSwitchUp(t *testing.T) {
logInit() logInit()
_, blockPool, blockPoolTester := newTestBlockPool(t) _, blockPool, blockPoolTester := newTestBlockPool(t)
blockPoolTester.blockChain[0] = nil blockPoolTester.blockChain[0] = nil
blockPoolTester.initRefBlockChain(6) blockPoolTester.initRefBlockChain(7)
blockPool.Start() blockPool.Start()
peer1 := blockPoolTester.newPeer("peer1", 1, 5) peer1 := blockPoolTester.newPeer("peer1", 1, 6)
peer2 := blockPoolTester.newPeer("peer2", 2, 6) peer2 := blockPoolTester.newPeer("peer2", 2, 7)
peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.blocksRequestsMap = peer1.blocksRequestsMap
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlockHashes(5, 4, 3) go peer1.AddBlocks(5, 6)
go peer1.AddBlockHashes(6, 5, 4, 3) //
peer1.AddBlocks(2, 3) // section partially complete, block 3 will be preserved after peer demoted peer1.AddBlocks(2, 3) // section partially complete, block 3 will be preserved after peer demoted
peer2.AddPeer() // peer2 is promoted as best peer, peer1 is demoted peer2.AddPeer() // peer2 is promoted as best peer, peer1 is demoted
go peer2.AddBlockHashes(6, 5) // go peer2.AddBlocks(6, 7)
go peer2.AddBlocks(4, 5, 6) // tests that block request for earlier section is remembered go peer2.AddBlockHashes(7, 6) //
go peer2.AddBlocks(4, 5) // tests that block request for earlier section is remembered
go peer1.AddBlocks(3, 4) // tests that connecting section by demoted peer is remembered and blocks are accepted from demoted peer go peer1.AddBlocks(3, 4) // tests that connecting section by demoted peer is remembered and blocks are accepted from demoted peer
go peer2.AddBlockHashes(3, 2, 1, 0) // tests that known chain section is activated, hash requests from 3 is remembered go peer2.AddBlockHashes(3, 2, 1, 0) // tests that known chain section is activated, hash requests from 3 is remembered
peer2.AddBlocks(0, 1, 2) // final blocks linking to blockchain sent peer2.AddBlocks(0, 1, 2) // final blocks linking to blockchain sent
blockPool.Wait(waitTimeout * time.Second) blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop() blockPool.Stop()
blockPoolTester.refBlockChain[6] = []int{} blockPoolTester.refBlockChain[7] = []int{}
blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
} }
func TestPeerDownSwitch(t *testing.T) { func TestPeerSwitchDown(t *testing.T) {
logInit() logInit()
_, blockPool, blockPoolTester := newTestBlockPool(t) _, blockPool, blockPoolTester := newTestBlockPool(t)
blockPoolTester.blockChain[0] = nil blockPoolTester.blockChain[0] = nil
@ -698,12 +748,39 @@ func TestPeerDownSwitch(t *testing.T) {
peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.blocksRequestsMap = peer1.blocksRequestsMap
peer2.AddPeer() peer2.AddPeer()
go peer2.AddBlockHashes(6, 5, 4)
peer2.AddBlocks(5, 6) // partially complete, section will be preserved peer2.AddBlocks(5, 6) // partially complete, section will be preserved
go peer2.AddBlockHashes(6, 5, 4) //
peer2.AddBlocks(4, 5) //
blockPool.RemovePeer("peer2") // peer2 disconnects blockPool.RemovePeer("peer2") // peer2 disconnects
peer1.AddPeer() // inferior peer1 is promoted as best peer peer1.AddPeer() // inferior peer1 is promoted as best peer
go peer1.AddBlockHashes(4, 3, 2, 1, 0) // go peer1.AddBlockHashes(4, 3, 2, 1, 0) //
go peer1.AddBlocks(3, 4, 5) // tests that section set by demoted peer is remembered and blocks are accepted go peer1.AddBlocks(3, 4) // tests that section set by demoted peer is remembered and blocks are accepted , this connects the chain sections together
peer1.AddBlocks(0, 1, 2, 3)
blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop()
blockPoolTester.refBlockChain[6] = []int{}
blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}
func TestPeerCompleteSectionSwitchDown(t *testing.T) {
logInit()
_, blockPool, blockPoolTester := newTestBlockPool(t)
blockPoolTester.blockChain[0] = nil
blockPoolTester.initRefBlockChain(6)
blockPool.Start()
peer1 := blockPoolTester.newPeer("peer1", 1, 4)
peer2 := blockPoolTester.newPeer("peer2", 2, 6)
peer2.blocksRequestsMap = peer1.blocksRequestsMap
peer2.AddPeer()
peer2.AddBlocks(5, 6) // partially complete, section will be preserved
go peer2.AddBlockHashes(6, 5, 4) //
peer2.AddBlocks(3, 4, 5) // complete section
blockPool.RemovePeer("peer2") // peer2 disconnects
peer1.AddPeer() // inferior peer1 is promoted as best peer
peer1.AddBlockHashes(4, 3, 2, 1, 0) // tests that hash request are directly connecting if the head block exists
peer1.AddBlocks(0, 1, 2, 3) peer1.AddBlocks(0, 1, 2, 3)
blockPool.Wait(waitTimeout * time.Second) blockPool.Wait(waitTimeout * time.Second)
@ -725,11 +802,13 @@ func TestPeerSwitchBack(t *testing.T) {
peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.blocksRequestsMap = peer1.blocksRequestsMap
peer2.AddPeer() peer2.AddPeer()
go peer2.AddBlocks(7, 8)
go peer2.AddBlockHashes(8, 7, 6) go peer2.AddBlockHashes(8, 7, 6)
go peer2.AddBlockHashes(6, 5, 4) go peer2.AddBlockHashes(6, 5, 4)
peer2.AddBlocks(4, 5) // section partially complete peer2.AddBlocks(4, 5) // section partially complete
peer1.AddPeer() // peer1 is promoted as best peer peer1.AddPeer() // peer1 is promoted as best peer
go peer1.AddBlockHashes(11, 10) // only gives useless results go peer1.AddBlocks(10, 11) //
peer1.AddBlockHashes(11, 10) // only gives useless results
blockPool.RemovePeer("peer1") // peer1 disconnects blockPool.RemovePeer("peer1") // peer1 disconnects
go peer2.AddBlockHashes(4, 3, 2, 1, 0) // tests that asking for hashes from 4 is remembered go peer2.AddBlockHashes(4, 3, 2, 1, 0) // tests that asking for hashes from 4 is remembered
go peer2.AddBlocks(3, 4, 5, 6, 7, 8) // tests that section 4, 5, 6 and 7, 8 are remembered for missing blocks go peer2.AddBlocks(3, 4, 5, 6, 7, 8) // tests that section 4, 5, 6 and 7, 8 are remembered for missing blocks
@ -756,11 +835,13 @@ func TestForkSimple(t *testing.T) {
peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.blocksRequestsMap = peer1.blocksRequestsMap
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlocks(8, 9)
go peer1.AddBlockHashes(9, 8, 7, 3, 2) go peer1.AddBlockHashes(9, 8, 7, 3, 2)
peer1.AddBlocks(1, 2, 3, 7, 8, 9) peer1.AddBlocks(1, 2, 3, 7, 8)
peer2.AddPeer() // peer2 is promoted as best peer peer2.AddPeer() // peer2 is promoted as best peer
go peer2.AddBlocks(5, 6) //
go peer2.AddBlockHashes(6, 5, 4, 3, 2) // fork on 3 -> 4 (earlier child: 7) go peer2.AddBlockHashes(6, 5, 4, 3, 2) // fork on 3 -> 4 (earlier child: 7)
go peer2.AddBlocks(1, 2, 3, 4, 5, 6) go peer2.AddBlocks(1, 2, 3, 4, 5)
go peer2.AddBlockHashes(2, 1, 0) go peer2.AddBlockHashes(2, 1, 0)
peer2.AddBlocks(0, 1, 2) peer2.AddBlocks(0, 1, 2)
@ -790,23 +871,24 @@ func TestForkSwitchBackByNewBlocks(t *testing.T) {
peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.blocksRequestsMap = peer1.blocksRequestsMap
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlockHashes(9, 8, 7, 3, 2) peer1.AddBlocks(8, 9) //
peer1.AddBlocks(8, 9) // partial section go peer1.AddBlockHashes(9, 8, 7, 3, 2) //
peer1.AddBlocks(7, 8) // partial section
peer2.AddPeer() // peer2.AddPeer() //
peer2.AddBlocks(5, 6) //
go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3
peer2.AddBlocks(1, 2, 3, 4, 5, 6) // peer2.AddBlocks(1, 2, 3, 4, 5) //
// peer1 finds new blocks // peer1 finds new blocks
peer1.td = 3 peer1.td = 3
peer1.currentBlock = 11 peer1.currentBlock = 11
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlocks(10, 11)
go peer1.AddBlockHashes(11, 10, 9) go peer1.AddBlockHashes(11, 10, 9)
peer1.AddBlocks(7, 8, 9, 10, 11) peer1.AddBlocks(9, 10)
go peer1.AddBlockHashes(7, 3) // tests that hash request from fork root is remembered go peer1.AddBlocks(3, 7) // tests that block requests on earlier fork are remembered
go peer1.AddBlocks(3, 7) // tests that block requests on earlier fork are remembered
// go peer1.AddBlockHashes(1, 0) // tests that hash request from root of connecting chain section (added by demoted peer) is remembered
go peer1.AddBlockHashes(2, 1, 0) // tests that hash request from root of connecting chain section (added by demoted peer) is remembered go peer1.AddBlockHashes(2, 1, 0) // tests that hash request from root of connecting chain section (added by demoted peer) is remembered
peer1.AddBlocks(0, 1, 2, 3) peer1.AddBlocks(0, 1)
blockPool.Wait(waitTimeout * time.Second) blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop() blockPool.Stop()
@ -834,16 +916,18 @@ func TestForkSwitchBackByPeerSwitchBack(t *testing.T) {
peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.blocksRequestsMap = peer1.blocksRequestsMap
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlocks(8, 9)
go peer1.AddBlockHashes(9, 8, 7, 3, 2) go peer1.AddBlockHashes(9, 8, 7, 3, 2)
peer1.AddBlocks(8, 9) peer1.AddBlocks(7, 8)
peer2.AddPeer() // peer2.AddPeer()
go peer2.AddBlocks(5, 6) //
go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3
peer2.AddBlocks(2, 3, 4, 5, 6) // peer2.AddBlocks(2, 3, 4, 5) //
blockPool.RemovePeer("peer2") // peer2 disconnects, peer1 is promoted again as best peer blockPool.RemovePeer("peer2") // peer2 disconnects, peer1 is promoted again as best peer
peer1.AddBlockHashes(7, 3) // tests that hash request from fork root is remembered go peer1.AddBlocks(3, 7) // tests that block requests on earlier fork are remembered and orphan section relinks to existing parent block
go peer1.AddBlocks(3, 7, 8) // tests that block requests on earlier fork are remembered go peer1.AddBlocks(1, 2) //
go peer1.AddBlockHashes(2, 1, 0) // go peer1.AddBlockHashes(2, 1, 0) //
peer1.AddBlocks(0, 1, 2, 3) peer1.AddBlocks(0, 1)
blockPool.Wait(waitTimeout * time.Second) blockPool.Wait(waitTimeout * time.Second)
blockPool.Stop() blockPool.Stop()
@ -871,17 +955,19 @@ func TestForkCompleteSectionSwitchBackByPeerSwitchBack(t *testing.T) {
peer2.blocksRequestsMap = peer1.blocksRequestsMap peer2.blocksRequestsMap = peer1.blocksRequestsMap
peer1.AddPeer() peer1.AddPeer()
go peer1.AddBlocks(8, 9)
go peer1.AddBlockHashes(9, 8, 7) go peer1.AddBlockHashes(9, 8, 7)
peer1.AddBlocks(3, 7, 8, 9) // make sure this section is complete peer1.AddBlocks(3, 7, 8) // make sure this section is complete
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
go peer1.AddBlockHashes(7, 3, 2) // block 3/7 is section boundary go peer1.AddBlockHashes(7, 3, 2) // block 3/7 is section boundary
peer1.AddBlocks(2, 3) // partially complete sections peer1.AddBlocks(2, 3) // partially complete sections block 2 missing
peer2.AddPeer() // peer2.AddPeer() //
go peer2.AddBlocks(5, 6) //
go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3
peer2.AddBlocks(2, 3, 4, 5, 6) // block 2 still missing. peer2.AddBlocks(2, 3, 4, 5) // block 2 still missing.
blockPool.RemovePeer("peer2") // peer2 disconnects, peer1 is promoted again as best peer blockPool.RemovePeer("peer2") // peer2 disconnects, peer1 is promoted again as best peer
peer1.AddBlockHashes(7, 3) // tests that hash request from fork root is remembered even though section process completed // peer1.AddBlockHashes(7, 3) // tests that hash request from fork root is remembered even though section process completed
go peer1.AddBlockHashes(2, 1, 0) // go peer1.AddBlockHashes(2, 1, 0) //
peer1.AddBlocks(0, 1, 2) peer1.AddBlocks(0, 1, 2)
blockPool.Wait(waitTimeout * time.Second) blockPool.Wait(waitTimeout * time.Second)

@ -16,6 +16,7 @@ const (
ErrInvalidBlock ErrInvalidBlock
ErrInvalidPoW ErrInvalidPoW
ErrUnrequestedBlock ErrUnrequestedBlock
ErrInsufficientChainInfo
) )
var errorToString = map[int]string{ var errorToString = map[int]string{
@ -30,6 +31,7 @@ var errorToString = map[int]string{
ErrInvalidBlock: "Invalid block", ErrInvalidBlock: "Invalid block",
ErrInvalidPoW: "Invalid PoW", ErrInvalidPoW: "Invalid PoW",
ErrUnrequestedBlock: "Unrequested block", ErrUnrequestedBlock: "Unrequested block",
ErrInsufficientChainInfo: "Insufficient chain info",
} }
type protocolError struct { type protocolError struct {

@ -67,6 +67,8 @@ type newBlockMsgData struct {
TD *big.Int TD *big.Int
} }
const maxHashes = 255
type getBlockHashesMsgData struct { type getBlockHashesMsgData struct {
Hash []byte Hash []byte
Amount uint64 Amount uint64
@ -122,7 +124,7 @@ func (self *ethProtocol) handle() error {
defer msg.Discard() defer msg.Discard()
switch msg.Code { switch msg.Code {
case GetTxMsg: // ignore
case StatusMsg: case StatusMsg:
return self.protoError(ErrExtraStatusMsg, "") return self.protoError(ErrExtraStatusMsg, "")
@ -139,8 +141,13 @@ func (self *ethProtocol) handle() error {
if err := msg.Decode(&request); err != nil { if err := msg.Decode(&request); err != nil {
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) return self.protoError(ErrDecode, "->msg %v: %v", msg, err)
} }
//request.Amount = uint64(math.Min(float64(maxHashes), float64(request.Amount)))
if request.Amount > maxHashes {
request.Amount = maxHashes
}
hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount) hashes := self.chainManager.GetBlockHashesFromHash(request.Hash, request.Amount)
return self.rw.EncodeMsg(BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...) return p2p.EncodeMsg(self.rw, BlockHashesMsg, ethutil.ByteSliceToInterface(hashes)...)
case BlockHashesMsg: case BlockHashesMsg:
// TODO: redo using lazy decode , this way very inefficient on known chains // TODO: redo using lazy decode , this way very inefficient on known chains
@ -185,7 +192,7 @@ func (self *ethProtocol) handle() error {
break break
} }
} }
return self.rw.EncodeMsg(BlocksMsg, blocks...) return p2p.EncodeMsg(self.rw, BlocksMsg, blocks...)
case BlocksMsg: case BlocksMsg:
msgStream := rlp.NewStream(msg.Payload) msgStream := rlp.NewStream(msg.Payload)
@ -211,16 +218,6 @@ func (self *ethProtocol) handle() error {
// uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer // uses AddPeer followed by AddHashes, AddBlock only if peer is the best peer
// (or selected as new best peer) // (or selected as new best peer)
if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) { if self.blockPool.AddPeer(request.TD, hash, self.id, self.requestBlockHashes, self.requestBlocks, self.protoErrorDisconnect) {
called := true
iter := func() ([]byte, bool) {
if called {
called = false
return hash, true
} else {
return nil, false
}
}
self.blockPool.AddBlockHashes(iter, self.id)
self.blockPool.AddBlock(request.Block, self.id) self.blockPool.AddBlock(request.Block, self.id)
} }
@ -298,12 +295,12 @@ func (self *ethProtocol) handleStatus() error {
func (self *ethProtocol) requestBlockHashes(from []byte) error { func (self *ethProtocol) requestBlockHashes(from []byte) error {
self.peer.Debugf("fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4]) self.peer.Debugf("fetching hashes (%d) %x...\n", blockHashesBatchSize, from[0:4])
return self.rw.EncodeMsg(GetBlockHashesMsg, interface{}(from), uint64(blockHashesBatchSize)) return p2p.EncodeMsg(self.rw, GetBlockHashesMsg, interface{}(from), uint64(blockHashesBatchSize))
} }
func (self *ethProtocol) requestBlocks(hashes [][]byte) error { func (self *ethProtocol) requestBlocks(hashes [][]byte) error {
self.peer.Debugf("fetching %v blocks", len(hashes)) self.peer.Debugf("fetching %v blocks", len(hashes))
return self.rw.EncodeMsg(GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)...) return p2p.EncodeMsg(self.rw, GetBlocksMsg, ethutil.ByteSliceToInterface(hashes)...)
} }
func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) { func (self *ethProtocol) protoError(code int, format string, params ...interface{}) (err *protocolError) {

@ -41,10 +41,6 @@ func (self *testMsgReadWriter) WriteMsg(msg p2p.Msg) error {
return nil return nil
} }
func (self *testMsgReadWriter) EncodeMsg(code uint64, data ...interface{}) error {
return self.WriteMsg(p2p.NewMsg(code, data...))
}
func (self *testMsgReadWriter) ReadMsg() (p2p.Msg, error) { func (self *testMsgReadWriter) ReadMsg() (p2p.Msg, error) {
msg, ok := <-self.in msg, ok := <-self.in
if !ok { if !ok {

@ -12,8 +12,8 @@ EOF
peer 11 01 peer 11 01
peer 12 02 peer 12 02
P13ID=$PID P12ID=$PID
test_node $NAME "" -loglevel 5 $JSFILE test_node $NAME "" -loglevel 5 $JSFILE
sleep 0.5 sleep 0.3
kill $P13ID kill $P12ID

@ -1,10 +1,10 @@
#!/bin/bash #!/bin/bash
TIMEOUT=35 TIMEOUT=12
cat >> $JSFILE <<EOF cat >> $JSFILE <<EOF
eth.addPeer("localhost:30311"); eth.addPeer("localhost:30311");
sleep(30000); sleep(10000);
eth.export("$CHAIN_TEST"); eth.export("$CHAIN_TEST");
EOF EOF

@ -3,14 +3,14 @@
# launched by run.sh # launched by run.sh
function test_node { function test_node {
rm -rf $DIR/$1 rm -rf $DIR/$1
ARGS="-datadir $DIR/$1 -debug debug -seed=false -shh=false -id test$1" ARGS="-datadir $DIR/$1 -debug debug -seed=false -shh=false -id test$1 -port 303$1"
if [ "" != "$2" ]; then if [ "" != "$2" ]; then
chain="chains/$2.chain" chain="chains/$2.chain"
echo "import chain $chain" echo "import chain $chain"
$ETH $ARGS -loglevel 3 -chain $chain | grep CLI |grep import $ETH $ARGS -loglevel 3 -chain $chain | grep CLI |grep import
fi fi
echo "starting test node $1 with extra args ${@:3}" echo "starting test node $1 with args $ARGS ${@:3}"
$ETH $ARGS -port 303$1 ${@:3} & $ETH $ARGS ${@:3} &
PID=$! PID=$!
PIDS="$PIDS $PID" PIDS="$PIDS $PID"
} }

@ -84,4 +84,5 @@ var (
BigFalse = Big0 BigFalse = Big0
Big32 = big.NewInt(32) Big32 = big.NewInt(32)
Big256 = big.NewInt(0xff) Big256 = big.NewInt(0xff)
Big257 = big.NewInt(257)
) )

@ -10,8 +10,6 @@ import (
// Config struct // Config struct
type ConfigManager struct { type ConfigManager struct {
Db Database
ExecPath string ExecPath string
Debug bool Debug bool
Diff bool Diff bool

@ -12,7 +12,7 @@ func ExpandHomePath(p string) (path string) {
path = p path = p
// Check in case of paths like "/something/~/something/" // Check in case of paths like "/something/~/something/"
if path[:2] == "~/" { if len(path) > 1 && path[:2] == "~/" {
usr, _ := user.Current() usr, _ := user.Current()
dir := usr.HomeDir dir := usr.HomeDir

@ -137,7 +137,7 @@ func Encode(object interface{}) []byte {
case byte: case byte:
buff.Write(Encode(big.NewInt(int64(t)))) buff.Write(Encode(big.NewInt(int64(t))))
case *big.Int: case *big.Int:
// Not sure how this is possible while we check for // Not sure how this is possible while we check for nil
if t == nil { if t == nil {
buff.WriteByte(0xc0) buff.WriteByte(0xc0)
} else { } else {

@ -68,3 +68,11 @@ out:
} }
} }
} }
func (self *Filters) Match(a, b Filter) bool {
return reflect.TypeOf(a) == reflect.TypeOf(b) && a.Compare(b)
}
func (self *Filters) Get(i int) Filter {
return self.watchers[i]
}

@ -129,10 +129,9 @@ func (self *JSRE) initStdFuncs() {
*/ */
func (self *JSRE) dump(call otto.FunctionCall) otto.Value { func (self *JSRE) dump(call otto.FunctionCall) otto.Value {
var state *state.StateDB var block *types.Block
if len(call.ArgumentList) > 0 { if len(call.ArgumentList) > 0 {
var block *types.Block
if call.Argument(0).IsNumber() { if call.Argument(0).IsNumber() {
num, _ := call.Argument(0).ToInteger() num, _ := call.Argument(0).ToInteger()
block = self.ethereum.ChainManager().GetBlockByNumber(uint64(num)) block = self.ethereum.ChainManager().GetBlockByNumber(uint64(num))
@ -149,12 +148,12 @@ func (self *JSRE) dump(call otto.FunctionCall) otto.Value {
return otto.UndefinedValue() return otto.UndefinedValue()
} }
state = block.State()
} else { } else {
state = self.ethereum.ChainManager().State() block = self.ethereum.ChainManager().CurrentBlock()
} }
v, _ := self.Vm.ToValue(state.Dump()) statedb := state.New(block.Root(), self.ethereum.Db())
v, _ := self.Vm.ToValue(statedb.Dump())
return v return v
} }

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
"github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/pow"
"github.com/ethereum/go-ethereum/pow/ezp" "github.com/ethereum/go-ethereum/pow/ezp"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -69,6 +70,7 @@ type Miner struct {
mining bool mining bool
MinAcceptedGasPrice *big.Int MinAcceptedGasPrice *big.Int
Extra string
} }
func New(coinbase []byte, eth *eth.Ethereum) *Miner { func New(coinbase []byte, eth *eth.Ethereum) *Miner {
@ -177,7 +179,9 @@ func (self *Miner) mine() {
blockProcessor = self.eth.BlockProcessor() blockProcessor = self.eth.BlockProcessor()
chainMan = self.eth.ChainManager() chainMan = self.eth.ChainManager()
block = chainMan.NewBlock(self.Coinbase) block = chainMan.NewBlock(self.Coinbase)
state = state.New(block.Root(), self.eth.Db())
) )
block.Header().Extra = self.Extra
// Apply uncles // Apply uncles
if len(self.uncles) > 0 { if len(self.uncles) > 0 {
@ -185,13 +189,11 @@ func (self *Miner) mine() {
} }
parent := chainMan.GetBlock(block.ParentHash()) parent := chainMan.GetBlock(block.ParentHash())
coinbase := block.State().GetOrNewStateObject(block.Coinbase()) coinbase := state.GetOrNewStateObject(block.Coinbase())
coinbase.SetGasPool(core.CalcGasLimit(parent, block)) coinbase.SetGasPool(core.CalcGasLimit(parent, block))
transactions := self.finiliseTxs() transactions := self.finiliseTxs()
state := block.State()
// Accumulate all valid transactions and apply them to the new state // Accumulate all valid transactions and apply them to the new state
// Error may be ignored. It's not important during mining // Error may be ignored. It's not important during mining
receipts, txs, _, erroneous, err := blockProcessor.ApplyTransactions(coinbase, state, block, transactions, true) receipts, txs, _, erroneous, err := blockProcessor.ApplyTransactions(coinbase, state, block, transactions, true)

@ -71,14 +71,11 @@ type MsgReader interface {
} }
type MsgWriter interface { type MsgWriter interface {
// WriteMsg sends an existing message. // WriteMsg sends a message. It will block until the message's
// The Payload reader of the message is consumed. // Payload has been consumed by the other end.
//
// Note that messages can be sent only once. // Note that messages can be sent only once.
WriteMsg(Msg) error WriteMsg(Msg) error
// EncodeMsg writes an RLP-encoded message with the given
// code and data elements.
EncodeMsg(code uint64, data ...interface{}) error
} }
// MsgReadWriter provides reading and writing of encoded messages. // MsgReadWriter provides reading and writing of encoded messages.
@ -87,6 +84,12 @@ type MsgReadWriter interface {
MsgWriter MsgWriter
} }
// EncodeMsg writes an RLP-encoded message with the given code and
// data elements.
func EncodeMsg(w MsgWriter, code uint64, data ...interface{}) error {
return w.WriteMsg(NewMsg(code, data...))
}
var magicToken = []byte{34, 64, 8, 145} var magicToken = []byte{34, 64, 8, 145}
func writeMsg(w io.Writer, msg Msg) error { func writeMsg(w io.Writer, msg Msg) error {
@ -209,11 +212,6 @@ func (p *MsgPipeRW) WriteMsg(msg Msg) error {
return ErrPipeClosed return ErrPipeClosed
} }
// EncodeMsg is a convenient shorthand for sending an RLP-encoded message.
func (p *MsgPipeRW) EncodeMsg(code uint64, data ...interface{}) error {
return p.WriteMsg(NewMsg(code, data...))
}
// ReadMsg returns a message sent on the other end of the pipe. // ReadMsg returns a message sent on the other end of the pipe.
func (p *MsgPipeRW) ReadMsg() (Msg, error) { func (p *MsgPipeRW) ReadMsg() (Msg, error) {
if atomic.LoadInt32(p.closed) == 0 { if atomic.LoadInt32(p.closed) == 0 {

@ -75,8 +75,8 @@ func TestDecodeRealMsg(t *testing.T) {
func ExampleMsgPipe() { func ExampleMsgPipe() {
rw1, rw2 := MsgPipe() rw1, rw2 := MsgPipe()
go func() { go func() {
rw1.EncodeMsg(8, []byte{0, 0}) EncodeMsg(rw1, 8, []byte{0, 0})
rw1.EncodeMsg(5, []byte{1, 1}) EncodeMsg(rw1, 5, []byte{1, 1})
rw1.Close() rw1.Close()
}() }()
@ -100,7 +100,7 @@ loop:
rw1, rw2 := MsgPipe() rw1, rw2 := MsgPipe()
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
if err := rw1.EncodeMsg(1); err == nil { if err := EncodeMsg(rw1, 1); err == nil {
t.Error("EncodeMsg returned nil error") t.Error("EncodeMsg returned nil error")
} else if err != ErrPipeClosed { } else if err != ErrPipeClosed {
t.Error("EncodeMsg returned wrong error: got %v, want %v", err, ErrPipeClosed) t.Error("EncodeMsg returned wrong error: got %v, want %v", err, ErrPipeClosed)

@ -460,25 +460,3 @@ func (r *eofSignal) Read(buf []byte) (int, error) {
} }
return n, err return n, err
} }
func (peer *Peer) PeerList() []interface{} {
peers := peer.otherPeers()
ds := make([]interface{}, 0, len(peers))
for _, p := range peers {
p.infolock.Lock()
addr := p.listenAddr
p.infolock.Unlock()
// filter out this peer and peers that are not listening or
// have not completed the handshake.
// TODO: track previously sent peers and exclude them as well.
if p == peer || addr == nil {
continue
}
ds = append(ds, addr)
}
ourAddr := peer.ourListenAddr
if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() {
ds = append(ds, ourAddr)
}
return ds
}

@ -126,10 +126,10 @@ func TestPeerProtoEncodeMsg(t *testing.T) {
Name: "a", Name: "a",
Length: 2, Length: 2,
Run: func(peer *Peer, rw MsgReadWriter) error { Run: func(peer *Peer, rw MsgReadWriter) error {
if err := rw.EncodeMsg(2); err == nil { if err := EncodeMsg(rw, 2); err == nil {
t.Error("expected error for out-of-range msg code, got nil") t.Error("expected error for out-of-range msg code, got nil")
} }
if err := rw.EncodeMsg(1, "foo", "bar"); err != nil { if err := EncodeMsg(rw, 1, "foo", "bar"); err != nil {
t.Errorf("write error: %v", err) t.Errorf("write error: %v", err)
} }
return nil return nil

@ -119,14 +119,14 @@ func (bp *baseProtocol) loop(quit <-chan error) error {
getPeersTick := time.NewTicker(10 * time.Second) getPeersTick := time.NewTicker(10 * time.Second)
defer getPeersTick.Stop() defer getPeersTick.Stop()
err := bp.rw.EncodeMsg(getPeersMsg) err := EncodeMsg(bp.rw, getPeersMsg)
for err == nil { for err == nil {
select { select {
case err = <-quit: case err = <-quit:
return err return err
case <-getPeersTick.C: case <-getPeersTick.C:
err = bp.rw.EncodeMsg(getPeersMsg) err = EncodeMsg(bp.rw, getPeersMsg)
case event := <-activity.Chan(): case event := <-activity.Chan():
ping.Reset(pingTimeout) ping.Reset(pingTimeout)
lastActive = event.(time.Time) lastActive = event.(time.Time)
@ -134,7 +134,7 @@ func (bp *baseProtocol) loop(quit <-chan error) error {
if lastActive.Add(pingTimeout * 2).Before(t) { if lastActive.Add(pingTimeout * 2).Before(t) {
err = newPeerError(errPingTimeout, "") err = newPeerError(errPingTimeout, "")
} else if lastActive.Add(pingTimeout).Before(t) { } else if lastActive.Add(pingTimeout).Before(t) {
err = bp.rw.EncodeMsg(pingMsg) err = EncodeMsg(bp.rw, pingMsg)
} }
} }
} }
@ -164,12 +164,12 @@ func (bp *baseProtocol) handle(rw MsgReadWriter) error {
return discRequestedError(reason[0]) return discRequestedError(reason[0])
case pingMsg: case pingMsg:
return bp.rw.EncodeMsg(pongMsg) return EncodeMsg(bp.rw, pongMsg)
case pongMsg: case pongMsg:
case getPeersMsg: case getPeersMsg:
peers := bp.peer.PeerList() peers := bp.peerList()
// this is dangerous. the spec says that we should _delay_ // this is dangerous. the spec says that we should _delay_
// sending the response if no new information is available. // sending the response if no new information is available.
// this means that would need to send a response later when // this means that would need to send a response later when
@ -177,7 +177,7 @@ func (bp *baseProtocol) handle(rw MsgReadWriter) error {
// //
// TODO: add event mechanism to notify baseProtocol for new peers // TODO: add event mechanism to notify baseProtocol for new peers
if len(peers) > 0 { if len(peers) > 0 {
return bp.rw.EncodeMsg(peersMsg, peers...) return EncodeMsg(bp.rw, peersMsg, peers...)
} }
case peersMsg: case peersMsg:
@ -264,3 +264,25 @@ func (bp *baseProtocol) handshakeMsg() Msg {
bp.peer.ourID.Pubkey()[1:], bp.peer.ourID.Pubkey()[1:],
) )
} }
func (bp *baseProtocol) peerList() []interface{} {
peers := bp.peer.otherPeers()
ds := make([]interface{}, 0, len(peers))
for _, p := range peers {
p.infolock.Lock()
addr := p.listenAddr
p.infolock.Unlock()
// filter out this peer and peers that are not listening or
// have not completed the handshake.
// TODO: track previously sent peers and exclude them as well.
if p == bp.peer || addr == nil {
continue
}
ds = append(ds, addr)
}
ourAddr := bp.peer.ourListenAddr
if ourAddr != nil && !ourAddr.IP.IsLoopback() && !ourAddr.IP.IsUnspecified() {
ds = append(ds, ourAddr)
}
return ds
}

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net" "net"
"reflect" "reflect"
"sync"
"testing" "testing"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -36,50 +37,71 @@ func newTestPeer() (peer *Peer) {
} }
func TestBaseProtocolPeers(t *testing.T) { func TestBaseProtocolPeers(t *testing.T) {
cannedPeerList := []*peerAddr{ peerList := []*peerAddr{
{IP: net.ParseIP("1.2.3.4"), Port: 2222, Pubkey: []byte{}}, {IP: net.ParseIP("1.2.3.4"), Port: 2222, Pubkey: []byte{}},
{IP: net.ParseIP("5.6.7.8"), Port: 3333, Pubkey: []byte{}}, {IP: net.ParseIP("5.6.7.8"), Port: 3333, Pubkey: []byte{}},
} }
var ownAddr *peerAddr = &peerAddr{IP: net.ParseIP("1.3.5.7"), Port: 1111, Pubkey: []byte{}} listenAddr := &peerAddr{IP: net.ParseIP("1.3.5.7"), Port: 1111, Pubkey: []byte{}}
rw1, rw2 := MsgPipe() rw1, rw2 := MsgPipe()
defer rw1.Close()
wg := new(sync.WaitGroup)
// run matcher, close pipe when addresses have arrived // run matcher, close pipe when addresses have arrived
addrChan := make(chan *peerAddr, len(cannedPeerList)) numPeers := len(peerList) + 1
addrChan := make(chan *peerAddr)
wg.Add(1)
go func() { go func() {
for _, want := range cannedPeerList { i := 0
got := <-addrChan for got := range addrChan {
t.Logf("got peer: %+v", got) var want *peerAddr
switch {
case i < len(peerList):
want = peerList[i]
case i == len(peerList):
want = listenAddr // listenAddr should be the last thing sent
}
t.Logf("got peer %d/%d: %v", i+1, numPeers, got)
if !reflect.DeepEqual(want, got) { if !reflect.DeepEqual(want, got) {
t.Errorf("mismatch: got %#v, want %#v", got, want) t.Errorf("mismatch: got %+v, want %+v", got, want)
}
i++
if i == numPeers {
break
} }
} }
close(addrChan) if i != numPeers {
var own []*peerAddr t.Errorf("wrong number of peers received: got %d, want %d", i, numPeers)
var got *peerAddr
for got = range addrChan {
own = append(own, got)
}
if len(own) != 1 || !reflect.DeepEqual(ownAddr, own[0]) {
t.Errorf("mismatch: peers own address is incorrectly or not given, got %v, want %#v", ownAddr)
} }
rw2.Close() rw1.Close()
wg.Done()
}() }()
// run first peer
// run first peer (in background)
peer1 := newTestPeer() peer1 := newTestPeer()
peer1.ourListenAddr = ownAddr peer1.ourListenAddr = listenAddr
peer1.otherPeers = func() []*Peer { peer1.otherPeers = func() []*Peer {
pl := make([]*Peer, len(cannedPeerList)) pl := make([]*Peer, len(peerList))
for i, addr := range cannedPeerList { for i, addr := range peerList {
pl[i] = &Peer{listenAddr: addr} pl[i] = &Peer{listenAddr: addr}
} }
return pl return pl
} }
go runBaseProtocol(peer1, rw1) wg.Add(1)
go func() {
runBaseProtocol(peer1, rw1)
wg.Done()
}()
// run second peer // run second peer
peer2 := newTestPeer() peer2 := newTestPeer()
peer2.newPeerAddr = addrChan // feed peer suggestions into matcher peer2.newPeerAddr = addrChan // feed peer suggestions into matcher
if err := runBaseProtocol(peer2, rw2); err != ErrPipeClosed { if err := runBaseProtocol(peer2, rw2); err != ErrPipeClosed {
t.Errorf("peer2 terminated with unexpected error: %v", err) t.Errorf("peer2 terminated with unexpected error: %v", err)
} }
// terminate matcher
close(addrChan)
wg.Wait()
} }
func TestBaseProtocolDisconnect(t *testing.T) { func TestBaseProtocolDisconnect(t *testing.T) {
@ -93,7 +115,7 @@ func TestBaseProtocolDisconnect(t *testing.T) {
if err := expectMsg(rw2, handshakeMsg); err != nil { if err := expectMsg(rw2, handshakeMsg); err != nil {
t.Error(err) t.Error(err)
} }
err := rw2.EncodeMsg(handshakeMsg, err := EncodeMsg(rw2, handshakeMsg,
baseProtocolVersion, baseProtocolVersion,
"", "",
[]interface{}{}, []interface{}{},
@ -106,7 +128,7 @@ func TestBaseProtocolDisconnect(t *testing.T) {
if err := expectMsg(rw2, getPeersMsg); err != nil { if err := expectMsg(rw2, getPeersMsg); err != nil {
t.Error(err) t.Error(err)
} }
if err := rw2.EncodeMsg(discMsg, DiscQuitting); err != nil { if err := EncodeMsg(rw2, discMsg, DiscQuitting); err != nil {
t.Error(err) t.Error(err)
} }

@ -333,7 +333,7 @@ func (srv *Server) dialLoop() {
case desc := <-suggest: case desc := <-suggest:
// candidate peer found, will dial out asyncronously // candidate peer found, will dial out asyncronously
// if connection fails slot will be released // if connection fails slot will be released
srvlog.Infof("dial %v (%v)", desc, *slot) srvlog.DebugDetailf("dial %v (%v)", desc, *slot)
go srv.dialPeer(desc, *slot) go srv.dialPeer(desc, *slot)
// we can watch if more peers needed in the next loop // we can watch if more peers needed in the next loop
slots = srv.peerSlots slots = srv.peerSlots
@ -355,7 +355,7 @@ func (srv *Server) dialPeer(desc *peerAddr, slot int) {
srvlog.Debugf("Dialing %v (slot %d)\n", desc, slot) srvlog.Debugf("Dialing %v (slot %d)\n", desc, slot)
conn, err := srv.Dialer.Dial(desc.Network(), desc.String()) conn, err := srv.Dialer.Dial(desc.Network(), desc.String())
if err != nil { if err != nil {
srvlog.Errorf("Dial error: %v", err) srvlog.DebugDetailf("dial error: %v", err)
srv.peerSlots <- slot srv.peerSlots <- slot
return return
} }

@ -1,12 +0,0 @@
package ar
import (
"math/big"
"github.com/ethereum/go-ethereum/trie"
)
type Block interface {
Trie() *trie.Trie
Diff() *big.Int
}

@ -1,54 +0,0 @@
package ar
import "math/big"
const lenops int64 = 9
type OpsFunc func(a, b *big.Int) *big.Int
var ops [lenops]OpsFunc
func init() {
ops[0] = Add
ops[1] = Mul
ops[2] = Mod
ops[3] = Xor
ops[4] = And
ops[5] = Or
ops[6] = Sub1
ops[7] = XorSub
ops[8] = Rsh
}
func Add(x, y *big.Int) *big.Int {
return new(big.Int).Add(x, y)
}
func Mul(x, y *big.Int) *big.Int {
return new(big.Int).Mul(x, y)
}
func Mod(x, y *big.Int) *big.Int {
return new(big.Int).Mod(x, y)
}
func Xor(x, y *big.Int) *big.Int {
return new(big.Int).Xor(x, y)
}
func And(x, y *big.Int) *big.Int {
return new(big.Int).And(x, y)
}
func Or(x, y *big.Int) *big.Int {
return new(big.Int).Or(x, y)
}
func Sub1(x, y *big.Int) *big.Int {
a := big.NewInt(-1)
a.Sub(a, x)
return a
}
func XorSub(x, y *big.Int) *big.Int {
t := Sub1(x, nil)
return t.Xor(t, y)
}
func Rsh(x, y *big.Int) *big.Int {
return new(big.Int).Rsh(x, uint(y.Uint64()%64))
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save