Merge pull request #1970 from karalabe/customizable-protocol-stacks

Customizable protocol stacks
This commit is contained in:
Jeffrey Wilcke
2015-11-27 10:41:22 +01:00
39 changed files with 2504 additions and 1033 deletions

View File

@ -1,135 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// 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
import (
"fmt"
"os"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/tests"
)
var blocktestCommand = cli.Command{
Action: runBlockTest,
Name: "blocktest",
Usage: `loads a block test file`,
Description: `
The first argument should be a block test file.
The second argument is the name of a block test from the file.
The block test will be loaded into an in-memory database.
If loading succeeds, the RPC server is started. Clients will
be able to interact with the chain defined by the test.
`,
}
func runBlockTest(ctx *cli.Context) {
var (
file, testname string
rpc bool
)
args := ctx.Args()
switch {
case len(args) == 1:
file = args[0]
case len(args) == 2:
file, testname = args[0], args[1]
case len(args) == 3:
file, testname = args[0], args[1]
rpc = true
default:
utils.Fatalf(`Usage: ethereum blocktest <path-to-test-file> [ <test-name> [ "rpc" ] ]`)
}
bt, err := tests.LoadBlockTests(file)
if err != nil {
utils.Fatalf("%v", err)
}
// run all tests if no test name is specified
if testname == "" {
ecode := 0
for name, test := range bt {
fmt.Printf("----------------- Running Block Test %q\n", name)
ethereum, err := runOneBlockTest(ctx, test)
if err != nil {
fmt.Println(err)
fmt.Println("FAIL")
ecode = 1
}
if ethereum != nil {
ethereum.Stop()
ethereum.WaitForShutdown()
}
}
os.Exit(ecode)
return
}
// otherwise, run the given test
test, ok := bt[testname]
if !ok {
utils.Fatalf("Test file does not contain test named %q", testname)
}
ethereum, err := runOneBlockTest(ctx, test)
if err != nil {
utils.Fatalf("%v", err)
}
if rpc {
fmt.Println("Block Test post state validated, starting RPC interface.")
startEth(ctx, ethereum)
utils.StartRPC(ethereum, ctx)
ethereum.WaitForShutdown()
}
}
func runOneBlockTest(ctx *cli.Context, test *tests.BlockTest) (*eth.Ethereum, error) {
cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
db, _ := ethdb.NewMemDatabase()
cfg.NewDB = func(path string) (ethdb.Database, error) { return db, nil }
cfg.MaxPeers = 0 // disable network
cfg.Shh = false // disable whisper
cfg.NAT = nil // disable port mapping
ethereum, err := eth.New(cfg)
if err != nil {
return nil, err
}
// import the genesis block
ethereum.ResetWithGenesisBlock(test.Genesis)
// import pre accounts
_, err = test.InsertPreState(db, cfg.AccountManager)
if err != nil {
return ethereum, fmt.Errorf("InsertPreState: %v", err)
}
cm := ethereum.BlockChain()
validBlocks, err := test.TryBlocksInsert(cm)
if err != nil {
return ethereum, fmt.Errorf("Block Test load error: %v", err)
}
newDB, err := cm.State()
if err != nil {
return ethereum, fmt.Errorf("Block Test get state error: %v", err)
}
if err := test.ValidatePostState(newDB); err != nil {
return ethereum, fmt.Errorf("post state validation failed: %v", err)
}
return ethereum, test.ValidateImportedHeaders(cm, validBlocks)
}

View File

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/common/registrar"
"github.com/ethereum/go-ethereum/eth"
re "github.com/ethereum/go-ethereum/jsre"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec"
@ -77,7 +78,7 @@ func (r dumbterm) AppendHistory(string) {}
type jsre struct {
re *re.JSRE
ethereum *eth.Ethereum
stack *node.Node
xeth *xeth.XEth
wait chan *big.Int
ps1 string
@ -176,18 +177,18 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str
return js
}
func newJSRE(ethereum *eth.Ethereum, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre {
js := &jsre{ethereum: ethereum, ps1: "> "}
func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre {
js := &jsre{stack: stack, ps1: "> "}
// set default cors domain used by startRpc from CLI flag
js.corsDomain = corsDomain
if f == nil {
f = js
}
js.xeth = xeth.New(ethereum, f)
js.xeth = xeth.New(stack, f)
js.wait = js.xeth.UpdateState()
js.client = client
if clt, ok := js.client.(*comms.InProcClient); ok {
if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, ethereum); err == nil {
if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, stack); err == nil {
clt.Initialize(api.Merge(offeredApis...))
}
}
@ -202,14 +203,14 @@ func newJSRE(ethereum *eth.Ethereum, docRoot, corsDomain string, client comms.Et
js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
} else {
lr := liner.NewLiner()
js.withHistory(ethereum.DataDir, func(hist *os.File) { lr.ReadHistory(hist) })
js.withHistory(stack.DataDir(), func(hist *os.File) { lr.ReadHistory(hist) })
lr.SetCtrlCAborts(true)
js.loadAutoCompletion()
lr.SetWordCompleter(apiWordCompleter)
lr.SetTabCompletionStyle(liner.TabPrints)
js.prompter = lr
js.atexit = func() {
js.withHistory(ethereum.DataDir, func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
js.withHistory(stack.DataDir(), func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
lr.Close()
close(js.wait)
}
@ -276,7 +277,7 @@ func (js *jsre) apiBindings(f xeth.Frontend) error {
apiNames = append(apiNames, a)
}
apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.ethereum)
apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.stack)
if err != nil {
utils.Fatalf("Unable to determine supported api's: %v", err)
}
@ -342,8 +343,14 @@ func (self *jsre) AskPassword() (string, bool) {
}
func (self *jsre) ConfirmTransaction(tx string) bool {
if self.ethereum.NatSpec {
notice := natspec.GetNotice(self.xeth, tx, self.ethereum.HTTPClient())
// Retrieve the Ethereum instance from the node
var ethereum *eth.Ethereum
if err := self.stack.Service(&ethereum); err != nil {
return false
}
// If natspec is enabled, ask for permission
if ethereum.NatSpec {
notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient())
fmt.Println(notice)
answer, _ := self.Prompt("Confirm Transaction [y/n]")
return strings.HasPrefix(strings.Trim(answer, " "), "y")
@ -359,7 +366,11 @@ func (self *jsre) UnlockAccount(addr []byte) bool {
return false
}
// TODO: allow retry
if err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil {
var ethereum *eth.Ethereum
if err := self.stack.Service(&ethereum); err != nil {
return false
}
if err := ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil {
return false
} else {
fmt.Println("Account is now unlocked for this session.")

View File

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
)
@ -66,7 +67,10 @@ type testjethre struct {
}
func (self *testjethre) UnlockAccount(acc []byte) bool {
err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
var ethereum *eth.Ethereum
self.stack.Service(&ethereum)
err := ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
if err != nil {
panic("unable to unlock")
}
@ -74,67 +78,74 @@ func (self *testjethre) UnlockAccount(acc []byte) bool {
}
func (self *testjethre) ConfirmTransaction(tx string) bool {
if self.ethereum.NatSpec {
var ethereum *eth.Ethereum
self.stack.Service(&ethereum)
if ethereum.NatSpec {
self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client)
}
return true
}
func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
func testJEthRE(t *testing.T) (string, *testjethre, *node.Node) {
return testREPL(t, nil)
}
func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *eth.Ethereum) {
func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *node.Node) {
tmp, err := ioutil.TempDir("", "geth-test")
if err != nil {
t.Fatal(err)
}
// Create a networkless protocol stack
stack, err := node.New(&node.Config{PrivateKey: testNodeKey, Name: "test", NoDiscovery: true})
if err != nil {
t.Fatalf("failed to create node: %v", err)
}
// Initialize and register the Ethereum protocol
keystore := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore"))
accman := accounts.NewManager(keystore)
db, _ := ethdb.NewMemDatabase()
core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)})
ks := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore"))
am := accounts.NewManager(ks)
conf := &eth.Config{
NodeKey: testNodeKey,
DataDir: tmp,
AccountManager: am,
MaxPeers: 0,
Name: "test",
DocRoot: "/",
SolcPath: testSolcPath,
PowTest: true,
NewDB: func(path string) (ethdb.Database, error) { return db, nil },
ethConf := &eth.Config{
TestGenesisState: db,
AccountManager: accman,
DocRoot: "/",
SolcPath: testSolcPath,
PowTest: true,
}
if config != nil {
config(conf)
config(ethConf)
}
ethereum, err := eth.New(conf)
if err != nil {
t.Fatal("%v", err)
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
t.Fatalf("failed to register ethereum protocol: %v", err)
}
// Initialize all the keys for testing
keyb, err := crypto.HexToECDSA(testKey)
if err != nil {
t.Fatal(err)
}
key := crypto.NewKeyFromECDSA(keyb)
err = ks.StoreKey(key, "")
if err != nil {
if err := keystore.StoreKey(key, ""); err != nil {
t.Fatal(err)
}
err = am.Unlock(key.Address, "")
if err != nil {
if err := accman.Unlock(key.Address, ""); err != nil {
t.Fatal(err)
}
// Start the node and assemble the REPL tester
if err := stack.Start(); err != nil {
t.Fatalf("failed to start test stack: %v", err)
}
var ethereum *eth.Ethereum
stack.Service(&ethereum)
assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
client := comms.NewInProcClient(codec.JSON)
tf := &testjethre{client: ethereum.HTTPClient()}
repl := newJSRE(ethereum, assetPath, "", client, false, tf)
repl := newJSRE(stack, assetPath, "", client, false, tf)
tf.jsre = repl
return tmp, tf, ethereum
return tmp, tf, stack
}
func TestNodeInfo(t *testing.T) {
@ -151,11 +162,8 @@ func TestNodeInfo(t *testing.T) {
}
func TestAccounts(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil {
t.Fatalf("error starting ethereum: %v", err)
}
defer ethereum.Stop()
tmp, repl, node := testJEthRE(t)
defer node.Stop()
defer os.RemoveAll(tmp)
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`)
@ -174,11 +182,8 @@ func TestAccounts(t *testing.T) {
}
func TestBlockChain(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil {
t.Fatalf("error starting ethereum: %v", err)
}
defer ethereum.Stop()
tmp, repl, node := testJEthRE(t)
defer node.Stop()
defer os.RemoveAll(tmp)
// get current block dump before export/import.
val, err := repl.re.Run("JSON.stringify(debug.dumpBlock(eth.blockNumber))")
@ -196,6 +201,8 @@ func TestBlockChain(t *testing.T) {
tmpfile := filepath.Join(extmp, "export.chain")
tmpfileq := strconv.Quote(tmpfile)
var ethereum *eth.Ethereum
node.Service(&ethereum)
ethereum.BlockChain().Reset()
checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`)
@ -209,22 +216,15 @@ func TestBlockChain(t *testing.T) {
}
func TestMining(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil {
t.Fatalf("error starting ethereum: %v", err)
}
defer ethereum.Stop()
tmp, repl, node := testJEthRE(t)
defer node.Stop()
defer os.RemoveAll(tmp)
checkEvalJSON(t, repl, `eth.mining`, `false`)
}
func TestRPC(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
tmp, repl, node := testJEthRE(t)
defer node.Stop()
defer os.RemoveAll(tmp)
checkEvalJSON(t, repl, `admin.startRPC("127.0.0.1", 5004, "*", "web3,eth,net")`, `true`)
@ -234,12 +234,8 @@ func TestCheckTestAccountBalance(t *testing.T) {
t.Skip() // i don't think it tests the correct behaviour here. it's actually testing
// internals which shouldn't be tested. This now fails because of a change in the core
// and i have no means to fix this, sorry - @obscuren
tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
tmp, repl, node := testJEthRE(t)
defer node.Stop()
defer os.RemoveAll(tmp)
repl.re.Run(`primary = "` + testAddress + `"`)
@ -247,12 +243,8 @@ func TestCheckTestAccountBalance(t *testing.T) {
}
func TestSignature(t *testing.T) {
tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
tmp, repl, node := testJEthRE(t)
defer node.Stop()
defer os.RemoveAll(tmp)
val, err := repl.re.Run(`eth.sign("` + testAddress + `", "` + testHash + `")`)
@ -443,7 +435,10 @@ multiply7 = Multiply7.at(contractaddress);
}
func pendingTransactions(repl *testjethre, t *testing.T) (txc int64, err error) {
txs := repl.ethereum.TxPool().GetTransactions()
var ethereum *eth.Ethereum
repl.stack.Service(&ethereum)
txs := ethereum.TxPool().GetTransactions()
return int64(len(txs)), nil
}
@ -468,12 +463,15 @@ func processTxs(repl *testjethre, t *testing.T, expTxc int) bool {
t.Errorf("incorrect number of pending transactions, expected %v, got %v", expTxc, txc)
return false
}
err = repl.ethereum.StartMining(runtime.NumCPU(), "")
var ethereum *eth.Ethereum
repl.stack.Service(&ethereum)
err = ethereum.StartMining(runtime.NumCPU(), "")
if err != nil {
t.Errorf("unexpected error mining: %v", err)
return false
}
defer repl.ethereum.StopMining()
defer ethereum.StopMining()
timer := time.NewTimer(100 * time.Second)
height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1))

View File

@ -33,13 +33,11 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc/codec"
@ -68,22 +66,9 @@ func init() {
}
app = utils.NewApp(Version, "the go-ethereum command line interface")
app.Action = run
app.Action = geth
app.HideVersion = true // we have a command to print the version
app.Commands = []cli.Command{
{
Action: blockRecovery,
Name: "recover",
Usage: "Attempts to recover a corrupted database by setting a new block by number or hash",
Description: `
The recover commands will attempt to read out the last
block based on that.
recover #number recovers by number
recover <hex> recovers by hash
`,
},
blocktestCommand,
importCommand,
exportCommand,
upgradedbCommand,
@ -285,7 +270,7 @@ This command allows to open a console on a running geth node.
`,
},
{
Action: execJSFiles,
Action: execScripts,
Name: "js",
Usage: `executes the given JavaScript files in the Geth JavaScript VM`,
Description: `
@ -376,14 +361,6 @@ func main() {
}
}
// makeExtra resolves extradata for the miner from a flag or returns a default.
func makeExtra(ctx *cli.Context) []byte {
if ctx.GlobalIsSet(utils.ExtraDataFlag.Name) {
return []byte(ctx.GlobalString(utils.ExtraDataFlag.Name))
}
return makeDefaultExtra()
}
func makeDefaultExtra() []byte {
var clientInfo = struct {
Version uint
@ -404,18 +381,13 @@ func makeDefaultExtra() []byte {
return extra
}
func run(ctx *cli.Context) {
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx)
cfg.ExtraData = makeExtra(ctx)
ethereum, err := eth.New(cfg)
if err != nil {
utils.Fatalf("%v", err)
}
startEth(ctx, ethereum)
// this blocks the thread
ethereum.WaitForShutdown()
// geth is the main entry point into the system if no special subcommand is ran.
// It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down.
func geth(ctx *cli.Context) {
node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx)
startNode(ctx, node)
node.Wait()
}
func attach(ctx *cli.Context) {
@ -449,156 +421,107 @@ func attach(ctx *cli.Context) {
}
}
// console starts a new geth node, attaching a JavaScript console to it at the
// same time.
func console(ctx *cli.Context) {
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx)
cfg.ExtraData = makeExtra(ctx)
ethereum, err := eth.New(cfg)
if err != nil {
utils.Fatalf("%v", err)
}
// Create and start the node based on the CLI flags
node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx)
startNode(ctx, node)
// Attach to the newly started node, and either execute script or become interactive
client := comms.NewInProcClient(codec.JSON)
startEth(ctx, ethereum)
repl := newJSRE(
ethereum,
repl := newJSRE(node,
ctx.GlobalString(utils.JSpathFlag.Name),
ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
client,
true,
nil,
)
client, true, nil)
if ctx.GlobalString(utils.ExecFlag.Name) != "" {
repl.batch(ctx.GlobalString(utils.ExecFlag.Name))
if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
repl.batch(script)
} else {
repl.welcome()
repl.interactive()
}
ethereum.Stop()
ethereum.WaitForShutdown()
node.Stop()
}
func execJSFiles(ctx *cli.Context) {
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx)
ethereum, err := eth.New(cfg)
if err != nil {
utils.Fatalf("%v", err)
}
// execScripts starts a new geth node based on the CLI flags, and executes each
// of the JavaScript files specified as command arguments.
func execScripts(ctx *cli.Context) {
// Create and start the node based on the CLI flags
node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx)
startNode(ctx, node)
// Attach to the newly started node and execute the given scripts
client := comms.NewInProcClient(codec.JSON)
startEth(ctx, ethereum)
repl := newJSRE(
ethereum,
repl := newJSRE(node,
ctx.GlobalString(utils.JSpathFlag.Name),
ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
client,
false,
nil,
)
client, false, nil)
for _, file := range ctx.Args() {
repl.exec(file)
}
ethereum.Stop()
ethereum.WaitForShutdown()
node.Stop()
}
func unlockAccount(ctx *cli.Context, am *accounts.Manager, addr string, i int, inputpassphrases []string) (addrHex, auth string, passphrases []string) {
var err error
passphrases = inputpassphrases
addrHex, err = utils.ParamToAddress(addr, am)
if err == nil {
// Attempt to unlock the account 3 times
attempts := 3
for tries := 0; tries < attempts; tries++ {
msg := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", addr, tries+1, attempts)
auth, passphrases = getPassPhrase(ctx, msg, false, i, passphrases)
err = am.Unlock(common.HexToAddress(addrHex), auth)
if err == nil || passphrases != nil {
break
}
func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (common.Address, string) {
// Try to unlock the specified account a few times
account := utils.MakeAddress(accman, address)
for trials := 0; trials < 3; trials++ {
prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
password := getPassPhrase(prompt, false, i, passwords)
if err := accman.Unlock(account, password); err == nil {
return account, password
}
}
if err != nil {
utils.Fatalf("Unlock account '%s' (%v) failed: %v", addr, addrHex, err)
}
fmt.Printf("Account '%s' (%v) unlocked.\n", addr, addrHex)
return
// All trials expended to unlock account, bail out
utils.Fatalf("Failed to unlock account: %s", address)
return common.Address{}, ""
}
func blockRecovery(ctx *cli.Context) {
if len(ctx.Args()) < 1 {
glog.Fatal("recover requires block number or hash")
// startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner.
func startNode(ctx *cli.Context, stack *node.Node) {
// Start up the node itself
utils.StartNode(stack)
// Unlock any account specifically requested
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err != nil {
utils.Fatalf("ethereum service not running: %v", err)
}
arg := ctx.Args().First()
accman := ethereum.AccountManager()
passwords := utils.MakePasswordList(ctx)
cfg := utils.MakeEthConfig(ClientIdentifier, nodeNameVersion, ctx)
blockDb, err := ethdb.NewLDBDatabase(filepath.Join(cfg.DataDir, "blockchain"), cfg.DatabaseCache)
if err != nil {
glog.Fatalln("could not open db:", err)
}
var block *types.Block
if arg[0] == '#' {
block = core.GetBlock(blockDb, core.GetCanonicalHash(blockDb, common.String2Big(arg[1:]).Uint64()))
} else {
block = core.GetBlock(blockDb, common.HexToHash(arg))
}
if block == nil {
glog.Fatalln("block not found. Recovery failed")
}
if err = core.WriteHeadBlockHash(blockDb, block.Hash()); err != nil {
glog.Fatalln("block write err", err)
}
glog.Infof("Recovery succesful. New HEAD %x\n", block.Hash())
}
func startEth(ctx *cli.Context, eth *eth.Ethereum) {
// Start Ethereum itself
utils.StartEthereum(eth)
am := eth.AccountManager()
account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
accounts := strings.Split(account, " ")
var passphrases []string
accounts := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
for i, account := range accounts {
if len(account) > 0 {
if account == "primary" {
utils.Fatalf("the 'primary' keyword is deprecated. You can use integer indexes, but the indexes are not permanent, they can change if you add external keys, export your keys or copy your keystore to another node.")
}
_, _, passphrases = unlockAccount(ctx, am, account, i, passphrases)
if trimmed := strings.TrimSpace(account); trimmed != "" {
unlockAccount(ctx, accman, trimmed, i, passwords)
}
}
// Start auxiliary services if enabled.
if !ctx.GlobalBool(utils.IPCDisabledFlag.Name) {
if err := utils.StartIPC(eth, ctx); err != nil {
utils.Fatalf("Error string IPC: %v", err)
if err := utils.StartIPC(stack, ctx); err != nil {
utils.Fatalf("Failed to start IPC: %v", err)
}
}
if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
if err := utils.StartRPC(eth, ctx); err != nil {
utils.Fatalf("Error starting RPC: %v", err)
if err := utils.StartRPC(stack, ctx); err != nil {
utils.Fatalf("Failed to start RPC: %v", err)
}
}
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
err := eth.StartMining(
ctx.GlobalInt(utils.MinerThreadsFlag.Name),
ctx.GlobalString(utils.MiningGPUFlag.Name))
if err != nil {
utils.Fatalf("%v", err)
if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil {
utils.Fatalf("Failed to start mining: %v", err)
}
}
}
func accountList(ctx *cli.Context) {
am := utils.MakeAccountManager(ctx)
accts, err := am.Accounts()
accman := utils.MakeAccountManager(ctx)
accts, err := accman.Accounts()
if err != nil {
utils.Fatalf("Could not list accounts: %v", err)
}
@ -607,67 +530,57 @@ func accountList(ctx *cli.Context) {
}
}
func getPassPhrase(ctx *cli.Context, desc string, confirmation bool, i int, inputpassphrases []string) (passphrase string, passphrases []string) {
passfile := ctx.GlobalString(utils.PasswordFileFlag.Name)
if len(passfile) == 0 {
fmt.Println(desc)
auth, err := utils.PromptPassword("Passphrase: ", true)
// getPassPhrase retrieves the passwor associated with an account, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
// If a list of passwords was supplied, retrieve from them
if len(passwords) > 0 {
if i < len(passwords) {
return passwords[i]
}
return passwords[len(passwords)-1]
}
// Otherwise prompt the user for the password
fmt.Println(prompt)
password, err := utils.PromptPassword("Passphrase: ", true)
if err != nil {
utils.Fatalf("Failed to read passphrase: %v", err)
}
if confirmation {
confirm, err := utils.PromptPassword("Repeat passphrase: ", false)
if err != nil {
utils.Fatalf("%v", err)
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
}
if confirmation {
confirm, err := utils.PromptPassword("Repeat Passphrase: ", false)
if err != nil {
utils.Fatalf("%v", err)
}
if auth != confirm {
utils.Fatalf("Passphrases did not match.")
}
}
passphrase = auth
} else {
passphrases = inputpassphrases
if passphrases == nil {
passbytes, err := ioutil.ReadFile(passfile)
if err != nil {
utils.Fatalf("Unable to read password file '%s': %v", passfile, err)
}
// this is backwards compatible if the same password unlocks several accounts
// it also has the consequence that trailing newlines will not count as part
// of the password, so --password <(echo -n 'pass') will now work without -n
passphrases = strings.Split(string(passbytes), "\n")
}
if i >= len(passphrases) {
passphrase = passphrases[len(passphrases)-1]
} else {
passphrase = passphrases[i]
if password != confirm {
utils.Fatalf("Passphrases do not match")
}
}
return
return password
}
// accountCreate creates a new account into the keystore defined by the CLI flags.
func accountCreate(ctx *cli.Context) {
am := utils.MakeAccountManager(ctx)
passphrase, _ := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, nil)
acct, err := am.NewAccount(passphrase)
accman := utils.MakeAccountManager(ctx)
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
account, err := accman.NewAccount(password)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
utils.Fatalf("Failed to create account: %v", err)
}
fmt.Printf("Address: %x\n", acct)
fmt.Printf("Address: %x\n", account)
}
// accountUpdate transitions an account from a previous format to the current
// one, also providing the possibility to change the pass-phrase.
func accountUpdate(ctx *cli.Context) {
am := utils.MakeAccountManager(ctx)
arg := ctx.Args().First()
if len(arg) == 0 {
utils.Fatalf("account address or index must be given as argument")
if len(ctx.Args()) == 0 {
utils.Fatalf("No accounts specified to update")
}
accman := utils.MakeAccountManager(ctx)
addr, authFrom, passphrases := unlockAccount(ctx, am, arg, 0, nil)
authTo, _ := getPassPhrase(ctx, "Please give a new password. Do not forget this password.", true, 0, passphrases)
err := am.Update(common.HexToAddress(addr), authFrom, authTo)
if err != nil {
account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil)
newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
if err := accman.Update(account, oldPassword, newPassword); err != nil {
utils.Fatalf("Could not update the account: %v", err)
}
}
@ -682,10 +595,10 @@ func importWallet(ctx *cli.Context) {
utils.Fatalf("Could not read wallet file: %v", err)
}
am := utils.MakeAccountManager(ctx)
passphrase, _ := getPassPhrase(ctx, "", false, 0, nil)
accman := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
acct, err := am.ImportPreSaleKey(keyJson, passphrase)
acct, err := accman.ImportPreSaleKey(keyJson, passphrase)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
}
@ -697,9 +610,9 @@ func accountImport(ctx *cli.Context) {
if len(keyfile) == 0 {
utils.Fatalf("keyfile must be given as argument")
}
am := utils.MakeAccountManager(ctx)
passphrase, _ := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, nil)
acct, err := am.Import(keyfile, passphrase)
accman := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
acct, err := accman.Import(keyfile, passphrase)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
}

179
cmd/gethrpctest/main.go Normal file
View File

@ -0,0 +1,179 @@
// Copyright 2015 The go-ethereum Authors
// 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/>.
// gethrpctest is a command to run the external RPC tests.
package main
import (
"flag"
"io/ioutil"
"log"
"os"
"os/signal"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/tests"
"github.com/ethereum/go-ethereum/whisper"
"github.com/ethereum/go-ethereum/xeth"
)
const defaultTestKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
var (
testFile = flag.String("json", "", "Path to the .json test file to load")
testName = flag.String("test", "", "Name of the test from the .json file to run")
testKey = flag.String("key", defaultTestKey, "Private key of a test account to inject")
)
func main() {
flag.Parse()
// Load the test suite to run the RPC against
tests, err := tests.LoadBlockTests(*testFile)
if err != nil {
log.Fatalf("Failed to load test suite: %v", err)
}
test, found := tests[*testName]
if !found {
log.Fatalf("Requested test (%s) not found within suite", *testName)
}
// Create the protocol stack to run the test with
keydir, err := ioutil.TempDir("", "")
if err != nil {
log.Fatalf("Failed to create temporary keystore directory: %v", err)
}
defer os.RemoveAll(keydir)
stack, err := MakeSystemNode(keydir, *testKey, test)
if err != nil {
log.Fatalf("Failed to assemble test stack: %v", err)
}
if err := stack.Start(); err != nil {
log.Fatalf("Failed to start test node: %v", err)
}
defer stack.Stop()
log.Println("Test node started...")
// Make sure the tests contained within the suite pass
if err := RunTest(stack, test); err != nil {
log.Fatalf("Failed to run the pre-configured test: %v", err)
}
log.Println("Initial test suite passed...")
// Start the RPC interface and wait until terminated
if err := StartRPC(stack); err != nil {
log.Fatalf("Failed to start RPC instarface: %v", err)
}
log.Println("RPC Interface started, accepting requests...")
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
}
// MakeSystemNode configures a protocol stack for the RPC tests based on a given
// keystore path and initial pre-state.
func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node.Node, error) {
// Create a networkless protocol stack
stack, err := node.New(&node.Config{NoDiscovery: true})
if err != nil {
return nil, err
}
// Create the keystore and inject an unlocked account if requested
keystore := crypto.NewKeyStorePassphrase(keydir, crypto.StandardScryptN, crypto.StandardScryptP)
accman := accounts.NewManager(keystore)
if len(privkey) > 0 {
key, err := crypto.HexToECDSA(privkey)
if err != nil {
return nil, err
}
if err := keystore.StoreKey(crypto.NewKeyFromECDSA(key), ""); err != nil {
return nil, err
}
if err := accman.Unlock(crypto.NewKeyFromECDSA(key).Address, ""); err != nil {
return nil, err
}
}
// Initialize and register the Ethereum protocol
db, _ := ethdb.NewMemDatabase()
if _, err := test.InsertPreState(db, accman); err != nil {
return nil, err
}
ethConf := &eth.Config{
TestGenesisState: db,
TestGenesisBlock: test.Genesis,
AccountManager: accman,
}
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
return nil, err
}
// Initialize and register the Whisper protocol
if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil {
return nil, err
}
return stack, nil
}
// RunTest executes the specified test against an already pre-configured protocol
// stack to ensure basic checks pass before running RPC tests.
func RunTest(stack *node.Node, test *tests.BlockTest) error {
var ethereum *eth.Ethereum
stack.Service(&ethereum)
blockchain := ethereum.BlockChain()
// Process the blocks and verify the imported headers
blocks, err := test.TryBlocksInsert(blockchain)
if err != nil {
return err
}
if err := test.ValidateImportedHeaders(blockchain, blocks); err != nil {
return err
}
// Retrieve the assembled state and validate it
stateDb, err := blockchain.State()
if err != nil {
return err
}
if err := test.ValidatePostState(stateDb); err != nil {
return err
}
return nil
}
// StartRPC initializes an RPC interface to the given protocol stack.
func StartRPC(stack *node.Node) error {
config := comms.HttpConfig{
ListenAddress: "127.0.0.1",
ListenPort: 8545,
}
xeth := xeth.New(stack, nil)
codec := codec.JSON
apis, err := api.ParseApiString(comms.DefaultHttpRpcApis, codec, xeth, stack)
if err != nil {
return err
}
return comms.StartHttp(config, codec, api.Merge(apis...))
}

41
cmd/utils/bootnodes.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2015 The go-ethereum Authors
// 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 utils
import "github.com/ethereum/go-ethereum/p2p/discover"
// FrontierBootNodes are the enode URLs of the P2P bootstrap nodes running on
// the Frontier network.
var FrontierBootNodes = []*discover.Node{
// ETH/DEV Go Bootnodes
discover.MustParseNode("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303"), // IE
discover.MustParseNode("enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303"), // BR
discover.MustParseNode("enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303"), // SG
// ETH/DEV Cpp Bootnodes
discover.MustParseNode("enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303"),
}
// TestNetBootNodes are the enode URLs of the P2P bootstrap nodes running on the
// Morden test network.
var TestNetBootNodes = []*discover.Node{
// ETH/DEV Go Bootnodes
discover.MustParseNode("enode://e4533109cc9bd7604e4ff6c095f7a1d807e15b38e9bfeb05d3b7c423ba86af0a9e89abbf40bd9dde4250fef114cd09270fa4e224cbeef8b7bf05a51e8260d6b8@94.242.229.4:40404"),
discover.MustParseNode("enode://8c336ee6f03e99613ad21274f269479bf4413fb294d697ef15ab897598afb931f56beb8e97af530aee20ce2bcba5776f4a312bc168545de4d43736992c814592@94.242.229.203:30303"),
// ETH/DEV Cpp Bootnodes
}

View File

@ -29,9 +29,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp"
"github.com/peterh/liner"
)
@ -110,10 +110,9 @@ func Fatalf(format string, args ...interface{}) {
os.Exit(1)
}
func StartEthereum(ethereum *eth.Ethereum) {
glog.V(logger.Info).Infoln("Starting", ethereum.Name())
if err := ethereum.Start(); err != nil {
Fatalf("Error starting Ethereum: %v", err)
func StartNode(stack *node.Node) {
if err := stack.Start(); err != nil {
Fatalf("Error starting protocol stack: %v", err)
}
go func() {
sigc := make(chan os.Signal, 1)
@ -121,7 +120,7 @@ func StartEthereum(ethereum *eth.Ethereum) {
defer signal.Stop(sigc)
<-sigc
glog.V(logger.Info).Infoln("Got interrupt, shutting down...")
go ethereum.Stop()
go stack.Stop()
logger.Flush()
for i := 10; i > 0; i-- {
<-sigc

View File

@ -19,6 +19,7 @@ package utils
import (
"crypto/ecdsa"
"fmt"
"io/ioutil"
"log"
"math"
"math/big"
@ -28,12 +29,14 @@ import (
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/codegangsta/cli"
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
@ -42,6 +45,8 @@ import (
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc/api"
@ -49,6 +54,7 @@ import (
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/rpc/useragent"
"github.com/ethereum/go-ethereum/whisper"
"github.com/ethereum/go-ethereum/xeth"
)
@ -192,12 +198,12 @@ var (
// Account settings
UnlockedAccountFlag = cli.StringFlag{
Name: "unlock",
Usage: "Unlock an account (may be creation index) until this program exits (prompts for password)",
Usage: "Comma separated list of accounts to unlock",
Value: "",
}
PasswordFileFlag = cli.StringFlag{
Name: "password",
Usage: "Password file to use with options/subcommands needing a pass phrase",
Usage: "Password file to use for non-inteactive password input",
Value: "",
}
@ -316,7 +322,7 @@ var (
}
BootnodesFlag = cli.StringFlag{
Name: "bootnodes",
Usage: "Space-separated enode URLs for P2P discovery bootstrap",
Usage: "Comma separated enode URLs for P2P discovery bootstrap",
Value: "",
}
NodeKeyFileFlag = cli.StringFlag{
@ -385,6 +391,90 @@ var (
}
)
// MustMakeDataDir retrieves the currently requested data directory, terminating
// if none (or the empty string) is specified. If the node is starting a testnet,
// the a subdirectory of the specified datadir will be used.
func MustMakeDataDir(ctx *cli.Context) string {
if path := ctx.GlobalString(DataDirFlag.Name); path != "" {
if ctx.GlobalBool(TestNetFlag.Name) {
return filepath.Join(path, "/testnet")
}
return path
}
Fatalf("Cannot determine default data directory, please set manually (--datadir)")
return ""
}
// MakeNodeKey creates a node key from set command line flags, either loading it
// from a file or as a specified hex value. If neither flags were provided, this
// method returns nil and an emphemeral key is to be generated.
func MakeNodeKey(ctx *cli.Context) *ecdsa.PrivateKey {
var (
hex = ctx.GlobalString(NodeKeyHexFlag.Name)
file = ctx.GlobalString(NodeKeyFileFlag.Name)
key *ecdsa.PrivateKey
err error
)
switch {
case file != "" && hex != "":
Fatalf("Options %q and %q are mutually exclusive", NodeKeyFileFlag.Name, NodeKeyHexFlag.Name)
case file != "":
if key, err = crypto.LoadECDSA(file); err != nil {
Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err)
}
case hex != "":
if key, err = crypto.HexToECDSA(hex); err != nil {
Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err)
}
}
return key
}
// MakeNodeName creates a node name from a base set and the command line flags.
func MakeNodeName(client, version string, ctx *cli.Context) string {
name := common.MakeName(client, version)
if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 {
name += "/" + identity
}
if ctx.GlobalBool(VMEnableJitFlag.Name) {
name += "/JIT"
}
return name
}
// MakeBootstrapNodes creates a list of bootstrap nodes from the command line
// flags, reverting to pre-configured ones if none have been specified.
func MakeBootstrapNodes(ctx *cli.Context) []*discover.Node {
// Return pre-configured nodes if none were manually requested
if !ctx.GlobalIsSet(BootnodesFlag.Name) {
if ctx.GlobalBool(TestNetFlag.Name) {
return TestNetBootNodes
}
return FrontierBootNodes
}
// Otherwise parse and use the CLI bootstrap nodes
bootnodes := []*discover.Node{}
for _, url := range strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",") {
node, err := discover.ParseNode(url)
if err != nil {
glog.V(logger.Error).Infof("Bootstrap URL %s: %v\n", url, err)
continue
}
bootnodes = append(bootnodes, node)
}
return bootnodes
}
// MakeListenAddress creates a TCP listening address string from set command
// line flags.
func MakeListenAddress(ctx *cli.Context) string {
return fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name))
}
// MakeNAT creates a port mapper from set command line flags.
func MakeNAT(ctx *cli.Context) nat.Interface {
natif, err := nat.Parse(ctx.GlobalString(NATFlag.Name))
@ -394,64 +484,145 @@ func MakeNAT(ctx *cli.Context) nat.Interface {
return natif
}
// MakeNodeKey creates a node key from set command line flags.
func MakeNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) {
hex, file := ctx.GlobalString(NodeKeyHexFlag.Name), ctx.GlobalString(NodeKeyFileFlag.Name)
var err error
switch {
case file != "" && hex != "":
Fatalf("Options %q and %q are mutually exclusive", NodeKeyFileFlag.Name, NodeKeyHexFlag.Name)
case file != "":
if key, err = crypto.LoadECDSA(file); err != nil {
Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err)
}
case hex != "":
if key, err = crypto.HexToECDSA(hex); err != nil {
Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err)
}
// MakeGenesisBlock loads up a genesis block from an input file specified in the
// command line, or returns the empty string if none set.
func MakeGenesisBlock(ctx *cli.Context) string {
genesis := ctx.GlobalString(GenesisFileFlag.Name)
if genesis == "" {
return ""
}
return key
data, err := ioutil.ReadFile(genesis)
if err != nil {
Fatalf("Failed to load custom genesis file: %v", err)
}
return string(data)
}
// MakeEthConfig creates ethereum options from set command line flags.
func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
customName := ctx.GlobalString(IdentityFlag.Name)
if len(customName) > 0 {
clientID += "/" + customName
// MakeAccountManager creates an account manager from set command line flags.
func MakeAccountManager(ctx *cli.Context) *accounts.Manager {
// Create the keystore crypto primitive, light if requested
scryptN := crypto.StandardScryptN
scryptP := crypto.StandardScryptP
if ctx.GlobalBool(LightKDFFlag.Name) {
scryptN = crypto.LightScryptN
scryptP = crypto.LightScryptP
}
am := MakeAccountManager(ctx)
etherbase, err := ParamToAddress(ctx.GlobalString(EtherbaseFlag.Name), am)
// Assemble an account manager using the configured datadir
var (
datadir = MustMakeDataDir(ctx)
keystore = crypto.NewKeyStorePassphrase(filepath.Join(datadir, "keystore"), scryptN, scryptP)
)
return accounts.NewManager(keystore)
}
// MakeAddress converts an account specified directly as a hex encoded string or
// a key index in the key store to an internal account representation.
func MakeAddress(accman *accounts.Manager, account string) common.Address {
// If the specified account is a valid address, return it
if common.IsHexAddress(account) {
return common.HexToAddress(account)
}
// Otherwise try to interpret the account as a keystore index
index, err := strconv.Atoi(account)
if err != nil {
glog.V(logger.Error).Infoln("WARNING: No etherbase set and no accounts found as default")
Fatalf("Invalid account address or index: '%s'", account)
}
// Assemble the entire eth configuration and return
cfg := &eth.Config{
Name: common.MakeName(clientID, version),
DataDir: MustDataDir(ctx),
GenesisFile: ctx.GlobalString(GenesisFileFlag.Name),
hex, err := accman.AddressByIndex(index)
if err != nil {
Fatalf("Failed to retrieve requested account #%d: %v", index, err)
}
return common.HexToAddress(hex)
}
// MakeEtherbase retrieves the etherbase either from the directly specified
// command line flags or from the keystore if CLI indexed.
func MakeEtherbase(accman *accounts.Manager, ctx *cli.Context) common.Address {
// If the specified etherbase is a valid address, return it
etherbase := ctx.GlobalString(EtherbaseFlag.Name)
if common.IsHexAddress(etherbase) {
return common.HexToAddress(etherbase)
}
// If no etherbase was specified and no accounts are known, bail out
accounts, _ := accman.Accounts()
if etherbase == "" && len(accounts) == 0 {
glog.V(logger.Error).Infoln("WARNING: No etherbase set and no accounts found as default")
return common.Address{}
}
// Otherwise try to interpret the parameter as a keystore index
index, err := strconv.Atoi(etherbase)
if err != nil {
Fatalf("Invalid account address or index: '%s'", etherbase)
}
hex, err := accman.AddressByIndex(index)
if err != nil {
Fatalf("Failed to set requested account #%d as etherbase: %v", index, err)
}
return common.HexToAddress(hex)
}
// MakeMinerExtra resolves extradata for the miner from the set command line flags
// or returns a default one composed on the client, runtime and OS metadata.
func MakeMinerExtra(extra []byte, ctx *cli.Context) []byte {
if ctx.GlobalIsSet(ExtraDataFlag.Name) {
return []byte(ctx.GlobalString(ExtraDataFlag.Name))
}
return extra
}
// MakePasswordList loads up a list of password from a file specified by the
// command line flags.
func MakePasswordList(ctx *cli.Context) []string {
if path := ctx.GlobalString(PasswordFileFlag.Name); path != "" {
blob, err := ioutil.ReadFile(path)
if err != nil {
Fatalf("Failed to read password file: %v", err)
}
return strings.Split(string(blob), "\n")
}
return nil
}
// MakeSystemNode sets up a local node, configures the services to launch and
// assembles the P2P protocol stack.
func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.Node {
// Avoid conflicting network flags
networks, netFlags := 0, []cli.BoolFlag{DevModeFlag, TestNetFlag, OlympicFlag}
for _, flag := range netFlags {
if ctx.GlobalBool(flag.Name) {
networks++
}
}
if networks > 1 {
Fatalf("The %v flags are mutually exclusive", netFlags)
}
// Configure the node's service container
stackConf := &node.Config{
DataDir: MustMakeDataDir(ctx),
PrivateKey: MakeNodeKey(ctx),
Name: MakeNodeName(name, version, ctx),
NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name),
BootstrapNodes: MakeBootstrapNodes(ctx),
ListenAddr: MakeListenAddress(ctx),
NAT: MakeNAT(ctx),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
}
// Configure the Ethereum service
accman := MakeAccountManager(ctx)
ethConf := &eth.Config{
Genesis: MakeGenesisBlock(ctx),
FastSync: ctx.GlobalBool(FastSyncFlag.Name),
BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name),
DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
SkipBcVersionCheck: false,
NetworkId: ctx.GlobalInt(NetworkIdFlag.Name),
LogFile: ctx.GlobalString(LogFileFlag.Name),
Verbosity: ctx.GlobalInt(VerbosityFlag.Name),
Etherbase: common.HexToAddress(etherbase),
AccountManager: accman,
Etherbase: MakeEtherbase(accman, ctx),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
AccountManager: am,
VmDebug: ctx.GlobalBool(VMDebugFlag.Name),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
Port: ctx.GlobalString(ListenPortFlag.Name),
Olympic: ctx.GlobalBool(OlympicFlag.Name),
NAT: MakeNAT(ctx),
ExtraData: MakeMinerExtra(extra, ctx),
NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name),
DocRoot: ctx.GlobalString(DocRootFlag.Name),
Discovery: !ctx.GlobalBool(NoDiscoverFlag.Name),
NodeKey: MakeNodeKey(ctx),
Shh: ctx.GlobalBool(WhisperEnabledFlag.Name),
Dial: true,
BootNodes: ctx.GlobalString(BootnodesFlag.Name),
GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)),
GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)),
GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)),
@ -462,46 +633,70 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
SolcPath: ctx.GlobalString(SolcPathFlag.Name),
AutoDAG: ctx.GlobalBool(AutoDAGFlag.Name) || ctx.GlobalBool(MiningEnabledFlag.Name),
}
// Configure the Whisper service
shhEnable := ctx.GlobalBool(WhisperEnabledFlag.Name)
if ctx.GlobalBool(DevModeFlag.Name) && ctx.GlobalBool(TestNetFlag.Name) {
glog.Fatalf("%s and %s are mutually exclusive\n", DevModeFlag.Name, TestNetFlag.Name)
}
// Override any default configs in dev mode or the test net
switch {
case ctx.GlobalBool(OlympicFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
ethConf.NetworkId = 1
}
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
ethConf.Genesis = core.OlympicGenesisBlock()
}
if ctx.GlobalBool(TestNetFlag.Name) {
// testnet is always stored in the testnet folder
cfg.DataDir += "/testnet"
cfg.NetworkId = 2
cfg.TestNet = true
}
case ctx.GlobalBool(TestNetFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
ethConf.NetworkId = 2
}
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
ethConf.Genesis = core.TestNetGenesisBlock()
}
state.StartingNonce = 1048576 // (2**20)
if ctx.GlobalBool(VMEnableJitFlag.Name) {
cfg.Name += "/JIT"
}
if ctx.GlobalBool(DevModeFlag.Name) {
if !ctx.GlobalIsSet(VMDebugFlag.Name) {
cfg.VmDebug = true
case ctx.GlobalBool(DevModeFlag.Name):
// Override the base network stack configs
if !ctx.GlobalIsSet(DataDirFlag.Name) {
stackConf.DataDir = filepath.Join(os.TempDir(), "/ethereum_dev_mode")
}
if !ctx.GlobalIsSet(MaxPeersFlag.Name) {
cfg.MaxPeers = 0
}
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
cfg.GasPrice = new(big.Int)
stackConf.MaxPeers = 0
}
if !ctx.GlobalIsSet(ListenPortFlag.Name) {
cfg.Port = "0" // auto port
stackConf.ListenAddr = ":0"
}
// Override the Ethereum protocol configs
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
ethConf.Genesis = core.OlympicGenesisBlock()
}
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
ethConf.GasPrice = new(big.Int)
}
if !ctx.GlobalIsSet(WhisperEnabledFlag.Name) {
cfg.Shh = true
shhEnable = true
}
if !ctx.GlobalIsSet(DataDirFlag.Name) {
cfg.DataDir = os.TempDir() + "/ethereum_dev_mode"
if !ctx.GlobalIsSet(VMDebugFlag.Name) {
vm.Debug = true
}
cfg.PowTest = true
cfg.DevMode = true
glog.V(logger.Info).Infoln("dev mode enabled")
ethConf.PowTest = true
}
return cfg
// Assemble and return the protocol stack
stack, err := node.New(stackConf)
if err != nil {
Fatalf("Failed to create the protocol stack: %v", err)
}
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return eth.New(ctx, ethConf)
}); err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
if shhEnable {
if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil {
Fatalf("Failed to register the Whisper service: %v", err)
}
}
return stack
}
// SetupLogger configures glog from the logging-related command line flags.
@ -509,7 +704,12 @@ func SetupLogger(ctx *cli.Context) {
glog.SetV(ctx.GlobalInt(VerbosityFlag.Name))
glog.CopyStandardLogTo("INFO")
glog.SetToStderr(true)
glog.SetLogDir(ctx.GlobalString(LogFileFlag.Name))
if ctx.GlobalIsSet(LogFileFlag.Name) {
logger.New("", ctx.GlobalString(LogFileFlag.Name), ctx.GlobalInt(VerbosityFlag.Name))
}
if ctx.GlobalIsSet(VMDebugFlag.Name) {
vm.Debug = ctx.GlobalBool(VMDebugFlag.Name)
}
}
// SetupNetwork configures the system for either the main net or some test network.
@ -535,7 +735,7 @@ func SetupVM(ctx *cli.Context) {
// MakeChain creates a chain manager from set command line flags.
func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database) {
datadir := MustDataDir(ctx)
datadir := MustMakeDataDir(ctx)
cache := ctx.GlobalInt(CacheFlag.Name)
var err error
@ -543,7 +743,7 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
Fatalf("Could not open database: %v", err)
}
if ctx.GlobalBool(OlympicFlag.Name) {
_, err := core.WriteTestNetGenesisBlock(chainDb, 42)
_, err := core.WriteTestNetGenesisBlock(chainDb)
if err != nil {
glog.Fatalln(err)
}
@ -560,32 +760,6 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
return chain, chainDb
}
// MakeChain creates an account manager from set command line flags.
func MakeAccountManager(ctx *cli.Context) *accounts.Manager {
dataDir := MustDataDir(ctx)
if ctx.GlobalBool(TestNetFlag.Name) {
dataDir += "/testnet"
}
scryptN := crypto.StandardScryptN
scryptP := crypto.StandardScryptP
if ctx.GlobalBool(LightKDFFlag.Name) {
scryptN = crypto.LightScryptN
scryptP = crypto.LightScryptP
}
ks := crypto.NewKeyStorePassphrase(filepath.Join(dataDir, "keystore"), scryptN, scryptP)
return accounts.NewManager(ks)
}
// MustDataDir retrieves the currently requested data directory, terminating if
// none (or the empty string) is specified.
func MustDataDir(ctx *cli.Context) string {
if path := ctx.GlobalString(DataDirFlag.Name); path != "" {
return path
}
Fatalf("Cannot determine default data directory, please set manually (--datadir)")
return ""
}
func IpcSocketPath(ctx *cli.Context) (ipcpath string) {
if runtime.GOOS == "windows" {
ipcpath = common.DefaultIpcPath()
@ -605,39 +779,43 @@ func IpcSocketPath(ctx *cli.Context) (ipcpath string) {
return
}
func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error {
// StartIPC starts a IPC JSON-RPC API server.
func StartIPC(stack *node.Node, ctx *cli.Context) error {
config := comms.IpcConfig{
Endpoint: IpcSocketPath(ctx),
}
initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) {
fe := useragent.NewRemoteFrontend(conn, eth.AccountManager())
xeth := xeth.New(eth, fe)
apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, eth)
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err != nil {
return nil, nil, err
}
fe := useragent.NewRemoteFrontend(conn, ethereum.AccountManager())
xeth := xeth.New(stack, fe)
apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, stack)
if err != nil {
return nil, nil, err
}
return xeth, api.Merge(apis...), nil
}
return comms.StartIpc(config, codec.JSON, initializer)
}
func StartRPC(eth *eth.Ethereum, ctx *cli.Context) error {
// StartRPC starts a HTTP JSON-RPC API server.
func StartRPC(stack *node.Node, ctx *cli.Context) error {
config := comms.HttpConfig{
ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name),
ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)),
CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name),
}
xeth := xeth.New(eth, nil)
xeth := xeth.New(stack, nil)
codec := codec.JSON
apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, eth)
apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, stack)
if err != nil {
return err
}
return comms.StartHttp(config, codec, api.Merge(apis...))
}
@ -647,20 +825,3 @@ func StartPProf(ctx *cli.Context) {
log.Println(http.ListenAndServe(address, nil))
}()
}
func ParamToAddress(addr string, am *accounts.Manager) (addrHex string, err error) {
if !((len(addr) == 40) || (len(addr) == 42)) { // with or without 0x
index, err := strconv.Atoi(addr)
if err != nil {
Fatalf("Invalid account address '%s'", addr)
}
addrHex, err = am.AddressByIndex(index)
if err != nil {
return "", err
}
} else {
addrHex = addr
}
return
}