mirror of https://github.com/ethereum/go-ethereum
Merge pull request #1970 from karalabe/customizable-protocol-stacks
Customizable protocol stackspull/2009/head
commit
7dde2b902c
@ -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) |
|
||||||
} |
|
@ -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 := ð.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(ðereum) |
||||||
|
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...)) |
||||||
|
} |
@ -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
|
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,171 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/ecdsa" |
||||||
|
"encoding/json" |
||||||
|
"io/ioutil" |
||||||
|
"net" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/logger" |
||||||
|
"github.com/ethereum/go-ethereum/logger/glog" |
||||||
|
"github.com/ethereum/go-ethereum/p2p/discover" |
||||||
|
"github.com/ethereum/go-ethereum/p2p/nat" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key
|
||||||
|
datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list
|
||||||
|
datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list
|
||||||
|
datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos
|
||||||
|
) |
||||||
|
|
||||||
|
// Config represents a small collection of configuration values to fine tune the
|
||||||
|
// P2P network layer of a protocol stack. These values can be further extended by
|
||||||
|
// all registered services.
|
||||||
|
type Config struct { |
||||||
|
// DataDir is the file system folder the node should use for any data storage
|
||||||
|
// requirements. The configured data directory will not be directly shared with
|
||||||
|
// registered services, instead those can use utility methods to create/access
|
||||||
|
// databases or flat files. This enables ephemeral nodes which can fully reside
|
||||||
|
// in memory.
|
||||||
|
DataDir string |
||||||
|
|
||||||
|
// This field should be a valid secp256k1 private key that will be used for both
|
||||||
|
// remote peer identification as well as network traffic encryption. If no key
|
||||||
|
// is configured, the preset one is loaded from the data dir, generating it if
|
||||||
|
// needed.
|
||||||
|
PrivateKey *ecdsa.PrivateKey |
||||||
|
|
||||||
|
// Name sets the node name of this server. Use common.MakeName to create a name
|
||||||
|
// that follows existing conventions.
|
||||||
|
Name string |
||||||
|
|
||||||
|
// NoDiscovery specifies whether the peer discovery mechanism should be started
|
||||||
|
// or not. Disabling is usually useful for protocol debugging (manual topology).
|
||||||
|
NoDiscovery bool |
||||||
|
|
||||||
|
// Bootstrap nodes used to establish connectivity with the rest of the network.
|
||||||
|
BootstrapNodes []*discover.Node |
||||||
|
|
||||||
|
// Network interface address on which the node should listen for inbound peers.
|
||||||
|
ListenAddr string |
||||||
|
|
||||||
|
// If set to a non-nil value, the given NAT port mapper is used to make the
|
||||||
|
// listening port available to the Internet.
|
||||||
|
NAT nat.Interface |
||||||
|
|
||||||
|
// If Dialer is set to a non-nil value, the given Dialer is used to dial outbound
|
||||||
|
// peer connections.
|
||||||
|
Dialer *net.Dialer |
||||||
|
|
||||||
|
// If NoDial is true, the node will not dial any peers.
|
||||||
|
NoDial bool |
||||||
|
|
||||||
|
// MaxPeers is the maximum number of peers that can be connected. If this is
|
||||||
|
// set to zero, then only the configured static and trusted peers can connect.
|
||||||
|
MaxPeers int |
||||||
|
|
||||||
|
// MaxPendingPeers is the maximum number of peers that can be pending in the
|
||||||
|
// handshake phase, counted separately for inbound and outbound connections.
|
||||||
|
// Zero defaults to preset values.
|
||||||
|
MaxPendingPeers int |
||||||
|
} |
||||||
|
|
||||||
|
// NodeKey retrieves the currently configured private key of the node, checking
|
||||||
|
// first any manually set key, falling back to the one found in the configured
|
||||||
|
// data folder. If no key can be found, a new one is generated.
|
||||||
|
func (c *Config) NodeKey() *ecdsa.PrivateKey { |
||||||
|
// Use any specifically configured key
|
||||||
|
if c.PrivateKey != nil { |
||||||
|
return c.PrivateKey |
||||||
|
} |
||||||
|
// Generate ephemeral key if no datadir is being used
|
||||||
|
if c.DataDir == "" { |
||||||
|
key, err := crypto.GenerateKey() |
||||||
|
if err != nil { |
||||||
|
glog.Fatalf("Failed to generate ephemeral node key: %v", err) |
||||||
|
} |
||||||
|
return key |
||||||
|
} |
||||||
|
// Fall back to persistent key from the data directory
|
||||||
|
keyfile := filepath.Join(c.DataDir, datadirPrivateKey) |
||||||
|
if key, err := crypto.LoadECDSA(keyfile); err == nil { |
||||||
|
return key |
||||||
|
} |
||||||
|
// No persistent key found, generate and store a new one
|
||||||
|
key, err := crypto.GenerateKey() |
||||||
|
if err != nil { |
||||||
|
glog.Fatalf("Failed to generate node key: %v", err) |
||||||
|
} |
||||||
|
if err := crypto.SaveECDSA(keyfile, key); err != nil { |
||||||
|
glog.V(logger.Error).Infof("Failed to persist node key: %v", err) |
||||||
|
} |
||||||
|
return key |
||||||
|
} |
||||||
|
|
||||||
|
// StaticNodes returns a list of node enode URLs configured as static nodes.
|
||||||
|
func (c *Config) StaticNodes() []*discover.Node { |
||||||
|
return c.parsePersistentNodes(datadirStaticNodes) |
||||||
|
} |
||||||
|
|
||||||
|
// TrusterNodes returns a list of node enode URLs configured as trusted nodes.
|
||||||
|
func (c *Config) TrusterNodes() []*discover.Node { |
||||||
|
return c.parsePersistentNodes(datadirTrustedNodes) |
||||||
|
} |
||||||
|
|
||||||
|
// parsePersistentNodes parses a list of discovery node URLs loaded from a .json
|
||||||
|
// file from within the data directory.
|
||||||
|
func (c *Config) parsePersistentNodes(file string) []*discover.Node { |
||||||
|
// Short circuit if no node config is present
|
||||||
|
if c.DataDir == "" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
path := filepath.Join(c.DataDir, file) |
||||||
|
if _, err := os.Stat(path); err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
// Load the nodes from the config file
|
||||||
|
blob, err := ioutil.ReadFile(path) |
||||||
|
if err != nil { |
||||||
|
glog.V(logger.Error).Infof("Failed to access nodes: %v", err) |
||||||
|
return nil |
||||||
|
} |
||||||
|
nodelist := []string{} |
||||||
|
if err := json.Unmarshal(blob, &nodelist); err != nil { |
||||||
|
glog.V(logger.Error).Infof("Failed to load nodes: %v", err) |
||||||
|
return nil |
||||||
|
} |
||||||
|
// Interpret the list as a discovery node array
|
||||||
|
var nodes []*discover.Node |
||||||
|
for _, url := range nodelist { |
||||||
|
if url == "" { |
||||||
|
continue |
||||||
|
} |
||||||
|
node, err := discover.ParseNode(url) |
||||||
|
if err != nil { |
||||||
|
glog.V(logger.Error).Infof("Node URL %s: %v\n", url, err) |
||||||
|
continue |
||||||
|
} |
||||||
|
nodes = append(nodes, node) |
||||||
|
} |
||||||
|
return nodes |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
) |
||||||
|
|
||||||
|
// Tests that datadirs can be successfully created, be them manually configured
|
||||||
|
// ones or automatically generated temporary ones.
|
||||||
|
func TestDatadirCreation(t *testing.T) { |
||||||
|
// Create a temporary data dir and check that it can be used by a node
|
||||||
|
dir, err := ioutil.TempDir("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create manual data dir: %v", err) |
||||||
|
} |
||||||
|
defer os.RemoveAll(dir) |
||||||
|
|
||||||
|
if _, err := New(&Config{DataDir: dir}); err != nil { |
||||||
|
t.Fatalf("failed to create stack with existing datadir: %v", err) |
||||||
|
} |
||||||
|
// Generate a long non-existing datadir path and check that it gets created by a node
|
||||||
|
dir = filepath.Join(dir, "a", "b", "c", "d", "e", "f") |
||||||
|
if _, err := New(&Config{DataDir: dir}); err != nil { |
||||||
|
t.Fatalf("failed to create stack with creatable datadir: %v", err) |
||||||
|
} |
||||||
|
if _, err := os.Stat(dir); err != nil { |
||||||
|
t.Fatalf("freshly created datadir not accessible: %v", err) |
||||||
|
} |
||||||
|
// Verify that an impossible datadir fails creation
|
||||||
|
file, err := ioutil.TempFile("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create temporary file: %v", err) |
||||||
|
} |
||||||
|
defer os.Remove(file.Name()) |
||||||
|
|
||||||
|
dir = filepath.Join(file.Name(), "invalid/path") |
||||||
|
if _, err := New(&Config{DataDir: dir}); err == nil { |
||||||
|
t.Fatalf("protocol stack created with an invalid datadir") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that node keys can be correctly created, persisted, loaded and/or made
|
||||||
|
// ephemeral.
|
||||||
|
func TestNodeKeyPersistency(t *testing.T) { |
||||||
|
// Create a temporary folder and make sure no key is present
|
||||||
|
dir, err := ioutil.TempDir("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create temporary data directory: %v", err) |
||||||
|
} |
||||||
|
defer os.RemoveAll(dir) |
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil { |
||||||
|
t.Fatalf("non-created node key already exists") |
||||||
|
} |
||||||
|
// Configure a node with a preset key and ensure it's not persisted
|
||||||
|
key, err := crypto.GenerateKey() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to generate one-shot node key: %v", err) |
||||||
|
} |
||||||
|
if _, err := New(&Config{DataDir: dir, PrivateKey: key}); err != nil { |
||||||
|
t.Fatalf("failed to create empty stack: %v", err) |
||||||
|
} |
||||||
|
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err == nil { |
||||||
|
t.Fatalf("one-shot node key persisted to data directory") |
||||||
|
} |
||||||
|
// Configure a node with no preset key and ensure it is persisted this time
|
||||||
|
if _, err := New(&Config{DataDir: dir}); err != nil { |
||||||
|
t.Fatalf("failed to create newly keyed stack: %v", err) |
||||||
|
} |
||||||
|
if _, err := os.Stat(filepath.Join(dir, datadirPrivateKey)); err != nil { |
||||||
|
t.Fatalf("node key not persisted to data directory: %v", err) |
||||||
|
} |
||||||
|
key, err = crypto.LoadECDSA(filepath.Join(dir, datadirPrivateKey)) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to load freshly persisted node key: %v", err) |
||||||
|
} |
||||||
|
blob1, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey)) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to read freshly persisted node key: %v", err) |
||||||
|
} |
||||||
|
// Configure a new node and ensure the previously persisted key is loaded
|
||||||
|
if _, err := New(&Config{DataDir: dir}); err != nil { |
||||||
|
t.Fatalf("failed to create previously keyed stack: %v", err) |
||||||
|
} |
||||||
|
blob2, err := ioutil.ReadFile(filepath.Join(dir, datadirPrivateKey)) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to read previously persisted node key: %v", err) |
||||||
|
} |
||||||
|
if bytes.Compare(blob1, blob2) != 0 { |
||||||
|
t.Fatalf("persisted node key mismatch: have %x, want %x", blob2, blob1) |
||||||
|
} |
||||||
|
// Configure ephemeral node and ensure no key is dumped locally
|
||||||
|
if _, err := New(&Config{DataDir: ""}); err != nil { |
||||||
|
t.Fatalf("failed to create ephemeral stack: %v", err) |
||||||
|
} |
||||||
|
if _, err := os.Stat(filepath.Join(".", datadirPrivateKey)); err == nil { |
||||||
|
t.Fatalf("ephemeral node key persisted to disk") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
) |
||||||
|
|
||||||
|
// DuplicateServiceError is returned during Node startup if a registered service
|
||||||
|
// constructor returns a service of the same type that was already started.
|
||||||
|
type DuplicateServiceError struct { |
||||||
|
Kind reflect.Type |
||||||
|
} |
||||||
|
|
||||||
|
// Error generates a textual representation of the duplicate service error.
|
||||||
|
func (e *DuplicateServiceError) Error() string { |
||||||
|
return fmt.Sprintf("duplicate service: %v", e.Kind) |
||||||
|
} |
||||||
|
|
||||||
|
// StopError is returned if a Node fails to stop either any of its registered
|
||||||
|
// services or itself.
|
||||||
|
type StopError struct { |
||||||
|
Server error |
||||||
|
Services map[reflect.Type]error |
||||||
|
} |
||||||
|
|
||||||
|
// Error generates a textual representation of the stop error.
|
||||||
|
func (e *StopError) Error() string { |
||||||
|
return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services) |
||||||
|
} |
@ -0,0 +1,266 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package node represents the Ethereum protocol stack container.
|
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"reflect" |
||||||
|
"sync" |
||||||
|
"syscall" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/event" |
||||||
|
"github.com/ethereum/go-ethereum/p2p" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
ErrDatadirUsed = errors.New("datadir already used") |
||||||
|
ErrNodeStopped = errors.New("node not started") |
||||||
|
ErrNodeRunning = errors.New("node already running") |
||||||
|
ErrServiceUnknown = errors.New("unknown service") |
||||||
|
|
||||||
|
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} |
||||||
|
) |
||||||
|
|
||||||
|
// Node represents a P2P node into which arbitrary (uniquely typed) services might
|
||||||
|
// be registered.
|
||||||
|
type Node struct { |
||||||
|
datadir string // Path to the currently used data directory
|
||||||
|
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
|
||||||
|
|
||||||
|
serverConfig *p2p.Server // Configuration of the underlying P2P networking layer
|
||||||
|
server *p2p.Server // Currently running P2P networking layer
|
||||||
|
|
||||||
|
serviceFuncs []ServiceConstructor // Service constructors (in dependency order)
|
||||||
|
services map[reflect.Type]Service // Currently running services
|
||||||
|
|
||||||
|
stop chan struct{} // Channel to wait for termination notifications
|
||||||
|
lock sync.RWMutex |
||||||
|
} |
||||||
|
|
||||||
|
// New creates a new P2P node, ready for protocol registration.
|
||||||
|
func New(conf *Config) (*Node, error) { |
||||||
|
// Ensure the data directory exists, failing if it cannot be created
|
||||||
|
if conf.DataDir != "" { |
||||||
|
if err := os.MkdirAll(conf.DataDir, 0700); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
// Assemble the networking layer and the node itself
|
||||||
|
nodeDbPath := "" |
||||||
|
if conf.DataDir != "" { |
||||||
|
nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase) |
||||||
|
} |
||||||
|
return &Node{ |
||||||
|
datadir: conf.DataDir, |
||||||
|
serverConfig: &p2p.Server{ |
||||||
|
PrivateKey: conf.NodeKey(), |
||||||
|
Name: conf.Name, |
||||||
|
Discovery: !conf.NoDiscovery, |
||||||
|
BootstrapNodes: conf.BootstrapNodes, |
||||||
|
StaticNodes: conf.StaticNodes(), |
||||||
|
TrustedNodes: conf.TrusterNodes(), |
||||||
|
NodeDatabase: nodeDbPath, |
||||||
|
ListenAddr: conf.ListenAddr, |
||||||
|
NAT: conf.NAT, |
||||||
|
Dialer: conf.Dialer, |
||||||
|
NoDial: conf.NoDial, |
||||||
|
MaxPeers: conf.MaxPeers, |
||||||
|
MaxPendingPeers: conf.MaxPendingPeers, |
||||||
|
}, |
||||||
|
serviceFuncs: []ServiceConstructor{}, |
||||||
|
eventmux: new(event.TypeMux), |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Register injects a new service into the node's stack. The service created by
|
||||||
|
// the passed constructor must be unique in its type with regard to sibling ones.
|
||||||
|
func (n *Node) Register(constructor ServiceConstructor) error { |
||||||
|
n.lock.Lock() |
||||||
|
defer n.lock.Unlock() |
||||||
|
|
||||||
|
if n.server != nil { |
||||||
|
return ErrNodeRunning |
||||||
|
} |
||||||
|
n.serviceFuncs = append(n.serviceFuncs, constructor) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Start create a live P2P node and starts running it.
|
||||||
|
func (n *Node) Start() error { |
||||||
|
n.lock.Lock() |
||||||
|
defer n.lock.Unlock() |
||||||
|
|
||||||
|
// Short circuit if the node's already running
|
||||||
|
if n.server != nil { |
||||||
|
return ErrNodeRunning |
||||||
|
} |
||||||
|
// Otherwise copy and specialize the P2P configuration
|
||||||
|
running := new(p2p.Server) |
||||||
|
*running = *n.serverConfig |
||||||
|
|
||||||
|
services := make(map[reflect.Type]Service) |
||||||
|
for _, constructor := range n.serviceFuncs { |
||||||
|
// Create a new context for the particular service
|
||||||
|
ctx := &ServiceContext{ |
||||||
|
datadir: n.datadir, |
||||||
|
services: make(map[reflect.Type]Service), |
||||||
|
EventMux: n.eventmux, |
||||||
|
} |
||||||
|
for kind, s := range services { // copy needed for threaded access
|
||||||
|
ctx.services[kind] = s |
||||||
|
} |
||||||
|
// Construct and save the service
|
||||||
|
service, err := constructor(ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
kind := reflect.TypeOf(service) |
||||||
|
if _, exists := services[kind]; exists { |
||||||
|
return &DuplicateServiceError{Kind: kind} |
||||||
|
} |
||||||
|
services[kind] = service |
||||||
|
} |
||||||
|
// Gather the protocols and start the freshly assembled P2P server
|
||||||
|
for _, service := range services { |
||||||
|
running.Protocols = append(running.Protocols, service.Protocols()...) |
||||||
|
} |
||||||
|
if err := running.Start(); err != nil { |
||||||
|
if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { |
||||||
|
return ErrDatadirUsed |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
// Start each of the services
|
||||||
|
started := []reflect.Type{} |
||||||
|
for kind, service := range services { |
||||||
|
// Start the next service, stopping all previous upon failure
|
||||||
|
if err := service.Start(running); err != nil { |
||||||
|
for _, kind := range started { |
||||||
|
services[kind].Stop() |
||||||
|
} |
||||||
|
running.Stop() |
||||||
|
|
||||||
|
return err |
||||||
|
} |
||||||
|
// Mark the service started for potential cleanup
|
||||||
|
started = append(started, kind) |
||||||
|
} |
||||||
|
// Finish initializing the startup
|
||||||
|
n.services = services |
||||||
|
n.server = running |
||||||
|
n.stop = make(chan struct{}) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Stop terminates a running node along with all it's services. In the node was
|
||||||
|
// not started, an error is returned.
|
||||||
|
func (n *Node) Stop() error { |
||||||
|
n.lock.Lock() |
||||||
|
defer n.lock.Unlock() |
||||||
|
|
||||||
|
// Short circuit if the node's not running
|
||||||
|
if n.server == nil { |
||||||
|
return ErrNodeStopped |
||||||
|
} |
||||||
|
// Otherwise terminate all the services and the P2P server too
|
||||||
|
failure := &StopError{ |
||||||
|
Services: make(map[reflect.Type]error), |
||||||
|
} |
||||||
|
for kind, service := range n.services { |
||||||
|
if err := service.Stop(); err != nil { |
||||||
|
failure.Services[kind] = err |
||||||
|
} |
||||||
|
} |
||||||
|
n.server.Stop() |
||||||
|
|
||||||
|
n.services = nil |
||||||
|
n.server = nil |
||||||
|
close(n.stop) |
||||||
|
|
||||||
|
if len(failure.Services) > 0 { |
||||||
|
return failure |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Wait blocks the thread until the node is stopped. If the node is not running
|
||||||
|
// at the time of invocation, the method immediately returns.
|
||||||
|
func (n *Node) Wait() { |
||||||
|
n.lock.RLock() |
||||||
|
if n.server == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
stop := n.stop |
||||||
|
n.lock.RUnlock() |
||||||
|
|
||||||
|
<-stop |
||||||
|
} |
||||||
|
|
||||||
|
// Restart terminates a running node and boots up a new one in its place. If the
|
||||||
|
// node isn't running, an error is returned.
|
||||||
|
func (n *Node) Restart() error { |
||||||
|
if err := n.Stop(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := n.Start(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Server retrieves the currently running P2P network layer. This method is meant
|
||||||
|
// only to inspect fields of the currently running server, life cycle management
|
||||||
|
// should be left to this Node entity.
|
||||||
|
func (n *Node) Server() *p2p.Server { |
||||||
|
n.lock.RLock() |
||||||
|
defer n.lock.RUnlock() |
||||||
|
|
||||||
|
return n.server |
||||||
|
} |
||||||
|
|
||||||
|
// Service retrieves a currently running service registered of a specific type.
|
||||||
|
func (n *Node) Service(service interface{}) error { |
||||||
|
n.lock.RLock() |
||||||
|
defer n.lock.RUnlock() |
||||||
|
|
||||||
|
// Short circuit if the node's not running
|
||||||
|
if n.server == nil { |
||||||
|
return ErrNodeStopped |
||||||
|
} |
||||||
|
// Otherwise try to find the service to return
|
||||||
|
element := reflect.ValueOf(service).Elem() |
||||||
|
if running, ok := n.services[element.Type()]; ok { |
||||||
|
element.Set(reflect.ValueOf(running)) |
||||||
|
return nil |
||||||
|
} |
||||||
|
return ErrServiceUnknown |
||||||
|
} |
||||||
|
|
||||||
|
// DataDir retrieves the current datadir used by the protocol stack.
|
||||||
|
func (n *Node) DataDir() string { |
||||||
|
return n.datadir |
||||||
|
} |
||||||
|
|
||||||
|
// EventMux retrieves the event multiplexer used by all the network services in
|
||||||
|
// the current protocol stack.
|
||||||
|
func (n *Node) EventMux() *event.TypeMux { |
||||||
|
return n.eventmux |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package node_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"log" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/node" |
||||||
|
"github.com/ethereum/go-ethereum/p2p" |
||||||
|
"github.com/ethereum/go-ethereum/p2p/discover" |
||||||
|
) |
||||||
|
|
||||||
|
// SampleService is a trivial network service that can be attached to a node for
|
||||||
|
// life cycle management.
|
||||||
|
//
|
||||||
|
// The following methods are needed to implement a node.Service:
|
||||||
|
// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on
|
||||||
|
// - Start() error - method invoked when the node is ready to start the service
|
||||||
|
// - Stop() error - method invoked when the node terminates the service
|
||||||
|
type SampleService struct{} |
||||||
|
|
||||||
|
func (s *SampleService) Protocols() []p2p.Protocol { return nil } |
||||||
|
func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil } |
||||||
|
func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil } |
||||||
|
|
||||||
|
func ExampleUsage() { |
||||||
|
// Create a network node to run protocols with the default values. The below list
|
||||||
|
// is only used to display each of the configuration options. All of these could
|
||||||
|
// have been ommited if the default behavior is desired.
|
||||||
|
nodeConfig := &node.Config{ |
||||||
|
DataDir: "", // Empty uses ephemeral storage
|
||||||
|
PrivateKey: nil, // Nil generates a node key on the fly
|
||||||
|
Name: "", // Any textual node name is allowed
|
||||||
|
NoDiscovery: false, // Can disable discovering remote nodes
|
||||||
|
BootstrapNodes: []*discover.Node{}, // List of bootstrap nodes to use
|
||||||
|
ListenAddr: ":0", // Network interface to listen on
|
||||||
|
NAT: nil, // UPnP port mapper to use for crossing firewalls
|
||||||
|
Dialer: nil, // Custom dialer to use for establishing peer connections
|
||||||
|
NoDial: false, // Can prevent this node from dialing out
|
||||||
|
MaxPeers: 0, // Number of peers to allow
|
||||||
|
MaxPendingPeers: 0, // Number of peers allowed to handshake concurrently
|
||||||
|
} |
||||||
|
stack, err := node.New(nodeConfig) |
||||||
|
if err != nil { |
||||||
|
log.Fatalf("Failed to create network node: %v", err) |
||||||
|
} |
||||||
|
// Create and register a simple network service. This is done through the definition
|
||||||
|
// of a node.ServiceConstructor that will instantiate a node.Service. The reason for
|
||||||
|
// the factory method approach is to support service restarts without relying on the
|
||||||
|
// individual implementations' support for such operations.
|
||||||
|
constructor := func(context *node.ServiceContext) (node.Service, error) { |
||||||
|
return new(SampleService), nil |
||||||
|
} |
||||||
|
if err := stack.Register(constructor); err != nil { |
||||||
|
log.Fatalf("Failed to register service: %v", err) |
||||||
|
} |
||||||
|
// Boot up the entire protocol stack, do a restart and terminate
|
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
log.Fatalf("Failed to start the protocol stack: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Restart(); err != nil { |
||||||
|
log.Fatalf("Failed to restart the protocol stack: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Stop(); err != nil { |
||||||
|
log.Fatalf("Failed to stop the protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Output:
|
||||||
|
// Service starting...
|
||||||
|
// Service stopping...
|
||||||
|
// Service starting...
|
||||||
|
// Service stopping...
|
||||||
|
} |
@ -0,0 +1,496 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"reflect" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/p2p" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
testNodeKey, _ = crypto.GenerateKey() |
||||||
|
|
||||||
|
testNodeConfig = &Config{ |
||||||
|
PrivateKey: testNodeKey, |
||||||
|
Name: "test node", |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
// Tests that an empty protocol stack can be started, restarted and stopped.
|
||||||
|
func TestNodeLifeCycle(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Ensure that a stopped node can be stopped again
|
||||||
|
for i := 0; i < 3; i++ { |
||||||
|
if err := stack.Stop(); err != ErrNodeStopped { |
||||||
|
t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped) |
||||||
|
} |
||||||
|
} |
||||||
|
// Ensure that a node can be successfully started, but only once
|
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
t.Fatalf("failed to start node: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Start(); err != ErrNodeRunning { |
||||||
|
t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning) |
||||||
|
} |
||||||
|
// Ensure that a node can be restarted arbitrarily many times
|
||||||
|
for i := 0; i < 3; i++ { |
||||||
|
if err := stack.Restart(); err != nil { |
||||||
|
t.Fatalf("iter %d: failed to restart node: %v", i, err) |
||||||
|
} |
||||||
|
} |
||||||
|
// Ensure that a node can be stopped, but only once
|
||||||
|
if err := stack.Stop(); err != nil { |
||||||
|
t.Fatalf("failed to stop node: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Stop(); err != ErrNodeStopped { |
||||||
|
t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that if the data dir is already in use, an appropriate error is returned.
|
||||||
|
func TestNodeUsedDataDir(t *testing.T) { |
||||||
|
// Create a temporary folder to use as the data directory
|
||||||
|
dir, err := ioutil.TempDir("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create temporary data directory: %v", err) |
||||||
|
} |
||||||
|
defer os.RemoveAll(dir) |
||||||
|
|
||||||
|
// Create a new node based on the data directory
|
||||||
|
original, err := New(&Config{DataDir: dir}) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create original protocol stack: %v", err) |
||||||
|
} |
||||||
|
if err := original.Start(); err != nil { |
||||||
|
t.Fatalf("failed to start original protocol stack: %v", err) |
||||||
|
} |
||||||
|
defer original.Stop() |
||||||
|
|
||||||
|
// Create a second node based on the same data directory and ensure failure
|
||||||
|
duplicate, err := New(&Config{DataDir: dir}) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create duplicate protocol stack: %v", err) |
||||||
|
} |
||||||
|
if err := duplicate.Start(); err != ErrDatadirUsed { |
||||||
|
t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests whether services can be registered and duplicates caught.
|
||||||
|
func TestServiceRegistry(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Register a batch of unique services and ensure they start successfully
|
||||||
|
services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC} |
||||||
|
for i, constructor := range services { |
||||||
|
if err := stack.Register(constructor); err != nil { |
||||||
|
t.Fatalf("service #%d: registration failed: %v", i, err) |
||||||
|
} |
||||||
|
} |
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
t.Fatalf("failed to start original service stack: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Stop(); err != nil { |
||||||
|
t.Fatalf("failed to stop original service stack: %v", err) |
||||||
|
} |
||||||
|
// Duplicate one of the services and retry starting the node
|
||||||
|
if err := stack.Register(NewNoopServiceB); err != nil { |
||||||
|
t.Fatalf("duplicate registration failed: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Start(); err == nil { |
||||||
|
t.Fatalf("duplicate service started") |
||||||
|
} else { |
||||||
|
if _, ok := err.(*DuplicateServiceError); !ok { |
||||||
|
t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that registered services get started and stopped correctly.
|
||||||
|
func TestServiceLifeCycle(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Register a batch of life-cycle instrumented services
|
||||||
|
services := map[string]InstrumentingWrapper{ |
||||||
|
"A": InstrumentedServiceMakerA, |
||||||
|
"B": InstrumentedServiceMakerB, |
||||||
|
"C": InstrumentedServiceMakerC, |
||||||
|
} |
||||||
|
started := make(map[string]bool) |
||||||
|
stopped := make(map[string]bool) |
||||||
|
|
||||||
|
for id, maker := range services { |
||||||
|
id := id // Closure for the constructor
|
||||||
|
constructor := func(*ServiceContext) (Service, error) { |
||||||
|
return &InstrumentedService{ |
||||||
|
startHook: func(*p2p.Server) { started[id] = true }, |
||||||
|
stopHook: func() { stopped[id] = true }, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
if err := stack.Register(maker(constructor)); err != nil { |
||||||
|
t.Fatalf("service %s: registration failed: %v", id, err) |
||||||
|
} |
||||||
|
} |
||||||
|
// Start the node and check that all services are running
|
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
t.Fatalf("failed to start protocol stack: %v", err) |
||||||
|
} |
||||||
|
for id, _ := range services { |
||||||
|
if !started[id] { |
||||||
|
t.Fatalf("service %s: freshly started service not running", id) |
||||||
|
} |
||||||
|
if stopped[id] { |
||||||
|
t.Fatalf("service %s: freshly started service already stopped", id) |
||||||
|
} |
||||||
|
} |
||||||
|
// Stop the node and check that all services have been stopped
|
||||||
|
if err := stack.Stop(); err != nil { |
||||||
|
t.Fatalf("failed to stop protocol stack: %v", err) |
||||||
|
} |
||||||
|
for id, _ := range services { |
||||||
|
if !stopped[id] { |
||||||
|
t.Fatalf("service %s: freshly terminated service still running", id) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that services are restarted cleanly as new instances.
|
||||||
|
func TestServiceRestarts(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Define a service that does not support restarts
|
||||||
|
var ( |
||||||
|
running bool |
||||||
|
started int |
||||||
|
) |
||||||
|
constructor := func(*ServiceContext) (Service, error) { |
||||||
|
running = false |
||||||
|
|
||||||
|
return &InstrumentedService{ |
||||||
|
startHook: func(*p2p.Server) { |
||||||
|
if running { |
||||||
|
panic("already running") |
||||||
|
} |
||||||
|
running = true |
||||||
|
started++ |
||||||
|
}, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
// Register the service and start the protocol stack
|
||||||
|
if err := stack.Register(constructor); err != nil { |
||||||
|
t.Fatalf("failed to register the service: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
t.Fatalf("failed to start protocol stack: %v", err) |
||||||
|
} |
||||||
|
defer stack.Stop() |
||||||
|
|
||||||
|
if running != true || started != 1 { |
||||||
|
t.Fatalf("running/started mismatch: have %v/%d, want true/1", running, started) |
||||||
|
} |
||||||
|
// Restart the stack a few times and check successful service restarts
|
||||||
|
for i := 0; i < 3; i++ { |
||||||
|
if err := stack.Restart(); err != nil { |
||||||
|
t.Fatalf("iter %d: failed to restart stack: %v", i, err) |
||||||
|
} |
||||||
|
} |
||||||
|
if running != true || started != 4 { |
||||||
|
t.Fatalf("running/started mismatch: have %v/%d, want true/4", running, started) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that if a service fails to initialize itself, none of the other services
|
||||||
|
// will be allowed to even start.
|
||||||
|
func TestServiceConstructionAbortion(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Define a batch of good services
|
||||||
|
services := map[string]InstrumentingWrapper{ |
||||||
|
"A": InstrumentedServiceMakerA, |
||||||
|
"B": InstrumentedServiceMakerB, |
||||||
|
"C": InstrumentedServiceMakerC, |
||||||
|
} |
||||||
|
started := make(map[string]bool) |
||||||
|
for id, maker := range services { |
||||||
|
id := id // Closure for the constructor
|
||||||
|
constructor := func(*ServiceContext) (Service, error) { |
||||||
|
return &InstrumentedService{ |
||||||
|
startHook: func(*p2p.Server) { started[id] = true }, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
if err := stack.Register(maker(constructor)); err != nil { |
||||||
|
t.Fatalf("service %s: registration failed: %v", id, err) |
||||||
|
} |
||||||
|
} |
||||||
|
// Register a service that fails to construct itself
|
||||||
|
failure := errors.New("fail") |
||||||
|
failer := func(*ServiceContext) (Service, error) { |
||||||
|
return nil, failure |
||||||
|
} |
||||||
|
if err := stack.Register(failer); err != nil { |
||||||
|
t.Fatalf("failer registration failed: %v", err) |
||||||
|
} |
||||||
|
// Start the protocol stack and ensure none of the services get started
|
||||||
|
for i := 0; i < 100; i++ { |
||||||
|
if err := stack.Start(); err != failure { |
||||||
|
t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) |
||||||
|
} |
||||||
|
for id, _ := range services { |
||||||
|
if started[id] { |
||||||
|
t.Fatalf("service %s: started should not have", id) |
||||||
|
} |
||||||
|
delete(started, id) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that if a service fails to start, all others started before it will be
|
||||||
|
// shut down.
|
||||||
|
func TestServiceStartupAbortion(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Register a batch of good services
|
||||||
|
services := map[string]InstrumentingWrapper{ |
||||||
|
"A": InstrumentedServiceMakerA, |
||||||
|
"B": InstrumentedServiceMakerB, |
||||||
|
"C": InstrumentedServiceMakerC, |
||||||
|
} |
||||||
|
started := make(map[string]bool) |
||||||
|
stopped := make(map[string]bool) |
||||||
|
|
||||||
|
for id, maker := range services { |
||||||
|
id := id // Closure for the constructor
|
||||||
|
constructor := func(*ServiceContext) (Service, error) { |
||||||
|
return &InstrumentedService{ |
||||||
|
startHook: func(*p2p.Server) { started[id] = true }, |
||||||
|
stopHook: func() { stopped[id] = true }, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
if err := stack.Register(maker(constructor)); err != nil { |
||||||
|
t.Fatalf("service %s: registration failed: %v", id, err) |
||||||
|
} |
||||||
|
} |
||||||
|
// Register a service that fails to start
|
||||||
|
failure := errors.New("fail") |
||||||
|
failer := func(*ServiceContext) (Service, error) { |
||||||
|
return &InstrumentedService{ |
||||||
|
start: failure, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
if err := stack.Register(failer); err != nil { |
||||||
|
t.Fatalf("failer registration failed: %v", err) |
||||||
|
} |
||||||
|
// Start the protocol stack and ensure all started services stop
|
||||||
|
for i := 0; i < 100; i++ { |
||||||
|
if err := stack.Start(); err != failure { |
||||||
|
t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) |
||||||
|
} |
||||||
|
for id, _ := range services { |
||||||
|
if started[id] && !stopped[id] { |
||||||
|
t.Fatalf("service %s: started but not stopped", id) |
||||||
|
} |
||||||
|
delete(started, id) |
||||||
|
delete(stopped, id) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that even if a registered service fails to shut down cleanly, it does
|
||||||
|
// not influece the rest of the shutdown invocations.
|
||||||
|
func TestServiceTerminationGuarantee(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Register a batch of good services
|
||||||
|
services := map[string]InstrumentingWrapper{ |
||||||
|
"A": InstrumentedServiceMakerA, |
||||||
|
"B": InstrumentedServiceMakerB, |
||||||
|
"C": InstrumentedServiceMakerC, |
||||||
|
} |
||||||
|
started := make(map[string]bool) |
||||||
|
stopped := make(map[string]bool) |
||||||
|
|
||||||
|
for id, maker := range services { |
||||||
|
id := id // Closure for the constructor
|
||||||
|
constructor := func(*ServiceContext) (Service, error) { |
||||||
|
return &InstrumentedService{ |
||||||
|
startHook: func(*p2p.Server) { started[id] = true }, |
||||||
|
stopHook: func() { stopped[id] = true }, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
if err := stack.Register(maker(constructor)); err != nil { |
||||||
|
t.Fatalf("service %s: registration failed: %v", id, err) |
||||||
|
} |
||||||
|
} |
||||||
|
// Register a service that fails to shot down cleanly
|
||||||
|
failure := errors.New("fail") |
||||||
|
failer := func(*ServiceContext) (Service, error) { |
||||||
|
return &InstrumentedService{ |
||||||
|
stop: failure, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
if err := stack.Register(failer); err != nil { |
||||||
|
t.Fatalf("failer registration failed: %v", err) |
||||||
|
} |
||||||
|
// Start the protocol stack, and ensure that a failing shut down terminates all
|
||||||
|
for i := 0; i < 100; i++ { |
||||||
|
// Start the stack and make sure all is online
|
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
t.Fatalf("iter %d: failed to start protocol stack: %v", i, err) |
||||||
|
} |
||||||
|
for id, _ := range services { |
||||||
|
if !started[id] { |
||||||
|
t.Fatalf("iter %d, service %s: service not running", i, id) |
||||||
|
} |
||||||
|
if stopped[id] { |
||||||
|
t.Fatalf("iter %d, service %s: service already stopped", i, id) |
||||||
|
} |
||||||
|
} |
||||||
|
// Stop the stack, verify failure and check all terminations
|
||||||
|
err := stack.Stop() |
||||||
|
if err, ok := err.(*StopError); !ok { |
||||||
|
t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err) |
||||||
|
} else { |
||||||
|
failer := reflect.TypeOf(&InstrumentedService{}) |
||||||
|
if err.Services[failer] != failure { |
||||||
|
t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services[failer], failure) |
||||||
|
} |
||||||
|
if len(err.Services) != 1 { |
||||||
|
t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1) |
||||||
|
} |
||||||
|
} |
||||||
|
for id, _ := range services { |
||||||
|
if !stopped[id] { |
||||||
|
t.Fatalf("iter %d, service %s: service not terminated", i, id) |
||||||
|
} |
||||||
|
delete(started, id) |
||||||
|
delete(stopped, id) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TestServiceRetrieval tests that individual services can be retrieved.
|
||||||
|
func TestServiceRetrieval(t *testing.T) { |
||||||
|
// Create a simple stack and register two service types
|
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Register(NewNoopService); err != nil { |
||||||
|
t.Fatalf("noop service registration failed: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Register(NewInstrumentedService); err != nil { |
||||||
|
t.Fatalf("instrumented service registration failed: %v", err) |
||||||
|
} |
||||||
|
// Make sure none of the services can be retrieved until started
|
||||||
|
var noopServ *NoopService |
||||||
|
if err := stack.Service(&noopServ); err != ErrNodeStopped { |
||||||
|
t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) |
||||||
|
} |
||||||
|
var instServ *InstrumentedService |
||||||
|
if err := stack.Service(&instServ); err != ErrNodeStopped { |
||||||
|
t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) |
||||||
|
} |
||||||
|
// Start the stack and ensure everything is retrievable now
|
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
t.Fatalf("failed to start stack: %v", err) |
||||||
|
} |
||||||
|
defer stack.Stop() |
||||||
|
|
||||||
|
if err := stack.Service(&noopServ); err != nil { |
||||||
|
t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil) |
||||||
|
} |
||||||
|
if err := stack.Service(&instServ); err != nil { |
||||||
|
t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that all protocols defined by individual services get launched.
|
||||||
|
func TestProtocolGather(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Register a batch of services with some configured number of protocols
|
||||||
|
services := map[string]struct { |
||||||
|
Count int |
||||||
|
Maker InstrumentingWrapper |
||||||
|
}{ |
||||||
|
"Zero Protocols": {0, InstrumentedServiceMakerA}, |
||||||
|
"Single Protocol": {1, InstrumentedServiceMakerB}, |
||||||
|
"Many Protocols": {25, InstrumentedServiceMakerC}, |
||||||
|
} |
||||||
|
for id, config := range services { |
||||||
|
protocols := make([]p2p.Protocol, config.Count) |
||||||
|
for i := 0; i < len(protocols); i++ { |
||||||
|
protocols[i].Name = id |
||||||
|
protocols[i].Version = uint(i) |
||||||
|
} |
||||||
|
constructor := func(*ServiceContext) (Service, error) { |
||||||
|
return &InstrumentedService{ |
||||||
|
protocols: protocols, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
if err := stack.Register(config.Maker(constructor)); err != nil { |
||||||
|
t.Fatalf("service %s: registration failed: %v", id, err) |
||||||
|
} |
||||||
|
} |
||||||
|
// Start the services and ensure all protocols start successfully
|
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
t.Fatalf("failed to start protocol stack: %v", err) |
||||||
|
} |
||||||
|
defer stack.Stop() |
||||||
|
|
||||||
|
protocols := stack.Server().Protocols |
||||||
|
if len(protocols) != 26 { |
||||||
|
t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) |
||||||
|
} |
||||||
|
for id, config := range services { |
||||||
|
for ver := 0; ver < config.Count; ver++ { |
||||||
|
launched := false |
||||||
|
for i := 0; i < len(protocols); i++ { |
||||||
|
if protocols[i].Name == id && protocols[i].Version == uint(ver) { |
||||||
|
launched = true |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if !launched { |
||||||
|
t.Errorf("configured protocol not launched: %s v%d", id, ver) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"path/filepath" |
||||||
|
"reflect" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb" |
||||||
|
"github.com/ethereum/go-ethereum/event" |
||||||
|
"github.com/ethereum/go-ethereum/p2p" |
||||||
|
) |
||||||
|
|
||||||
|
// ServiceContext is a collection of service independent options inherited from
|
||||||
|
// the protocol stack, that is passed to all constructors to be optionally used;
|
||||||
|
// as well as utility methods to operate on the service environment.
|
||||||
|
type ServiceContext struct { |
||||||
|
datadir string // Data directory for protocol persistence
|
||||||
|
services map[reflect.Type]Service // Index of the already constructed services
|
||||||
|
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
||||||
|
} |
||||||
|
|
||||||
|
// OpenDatabase opens an existing database with the given name (or creates one
|
||||||
|
// if no previous can be found) from within the node's data directory. If the
|
||||||
|
// node is an ephemeral one, a memory database is returned.
|
||||||
|
func (ctx *ServiceContext) OpenDatabase(name string, cache int) (ethdb.Database, error) { |
||||||
|
if ctx.datadir == "" { |
||||||
|
return ethdb.NewMemDatabase() |
||||||
|
} |
||||||
|
return ethdb.NewLDBDatabase(filepath.Join(ctx.datadir, name), cache) |
||||||
|
} |
||||||
|
|
||||||
|
// Service retrieves a currently running service registered of a specific type.
|
||||||
|
func (ctx *ServiceContext) Service(service interface{}) error { |
||||||
|
element := reflect.ValueOf(service).Elem() |
||||||
|
if running, ok := ctx.services[element.Type()]; ok { |
||||||
|
element.Set(reflect.ValueOf(running)) |
||||||
|
return nil |
||||||
|
} |
||||||
|
return ErrServiceUnknown |
||||||
|
} |
||||||
|
|
||||||
|
// ServiceConstructor is the function signature of the constructors needed to be
|
||||||
|
// registered for service instantiation.
|
||||||
|
type ServiceConstructor func(ctx *ServiceContext) (Service, error) |
||||||
|
|
||||||
|
// Service is an individual protocol that can be registered into a node.
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// - Service life-cycle management is delegated to the node. The service is
|
||||||
|
// allowed to initialize itself upon creation, but no goroutines should be
|
||||||
|
// spun up outside of the Start method.
|
||||||
|
// - Restart logic is not required as the node will create a fresh instance
|
||||||
|
// every time a service is started.
|
||||||
|
type Service interface { |
||||||
|
// Protocol retrieves the P2P protocols the service wishes to start.
|
||||||
|
Protocols() []p2p.Protocol |
||||||
|
|
||||||
|
// Start is called after all services have been constructed and the networking
|
||||||
|
// layer was also initialized to spawn any goroutines required by the service.
|
||||||
|
Start(server *p2p.Server) error |
||||||
|
|
||||||
|
// Stop terminates all goroutines belonging to the service, blocking until they
|
||||||
|
// are all terminated.
|
||||||
|
Stop() error |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
// Tests that databases are correctly created persistent or ephemeral based on
|
||||||
|
// the configured service context.
|
||||||
|
func TestContextDatabases(t *testing.T) { |
||||||
|
// Create a temporary folder and ensure no database is contained within
|
||||||
|
dir, err := ioutil.TempDir("", "") |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create temporary data directory: %v", err) |
||||||
|
} |
||||||
|
defer os.RemoveAll(dir) |
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "database")); err == nil { |
||||||
|
t.Fatalf("non-created database already exists") |
||||||
|
} |
||||||
|
// Request the opening/creation of a database and ensure it persists to disk
|
||||||
|
ctx := &ServiceContext{datadir: dir} |
||||||
|
db, err := ctx.OpenDatabase("persistent", 0) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to open persistent database: %v", err) |
||||||
|
} |
||||||
|
db.Close() |
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "persistent")); err != nil { |
||||||
|
t.Fatalf("persistent database doesn't exists: %v", err) |
||||||
|
} |
||||||
|
// Request th opening/creation of an ephemeral database and ensure it's not persisted
|
||||||
|
ctx = &ServiceContext{datadir: ""} |
||||||
|
db, err = ctx.OpenDatabase("ephemeral", 0) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to open ephemeral database: %v", err) |
||||||
|
} |
||||||
|
db.Close() |
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "ephemeral")); err == nil { |
||||||
|
t.Fatalf("ephemeral database exists") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Tests that already constructed services can be retrieves by later ones.
|
||||||
|
func TestContextServices(t *testing.T) { |
||||||
|
stack, err := New(testNodeConfig) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create protocol stack: %v", err) |
||||||
|
} |
||||||
|
// Define a verifier that ensures a NoopA is before it and NoopB after
|
||||||
|
verifier := func(ctx *ServiceContext) (Service, error) { |
||||||
|
var objA *NoopServiceA |
||||||
|
if ctx.Service(&objA) != nil { |
||||||
|
return nil, fmt.Errorf("former service not found") |
||||||
|
} |
||||||
|
var objB *NoopServiceB |
||||||
|
if err := ctx.Service(&objB); err != ErrServiceUnknown { |
||||||
|
return nil, fmt.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown) |
||||||
|
} |
||||||
|
return new(NoopService), nil |
||||||
|
} |
||||||
|
// Register the collection of services
|
||||||
|
if err := stack.Register(NewNoopServiceA); err != nil { |
||||||
|
t.Fatalf("former failed to register service: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Register(verifier); err != nil { |
||||||
|
t.Fatalf("failed to register service verifier: %v", err) |
||||||
|
} |
||||||
|
if err := stack.Register(NewNoopServiceB); err != nil { |
||||||
|
t.Fatalf("latter failed to register service: %v", err) |
||||||
|
} |
||||||
|
// Start the protocol stack and ensure services are constructed in order
|
||||||
|
if err := stack.Start(); err != nil { |
||||||
|
t.Fatalf("failed to start stack: %v", err) |
||||||
|
} |
||||||
|
defer stack.Stop() |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Contains a batch of utility type declarations used by the tests. As the node
|
||||||
|
// operates on unique types, a lot of them are needed to check various features.
|
||||||
|
|
||||||
|
package node |
||||||
|
|
||||||
|
import ( |
||||||
|
"reflect" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/p2p" |
||||||
|
) |
||||||
|
|
||||||
|
// NoopService is a trivial implementation of the Service interface.
|
||||||
|
type NoopService struct{} |
||||||
|
|
||||||
|
func (s *NoopService) Protocols() []p2p.Protocol { return nil } |
||||||
|
func (s *NoopService) Start(*p2p.Server) error { return nil } |
||||||
|
func (s *NoopService) Stop() error { return nil } |
||||||
|
|
||||||
|
func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil } |
||||||
|
|
||||||
|
// Set of services all wrapping the base NoopService resulting in the same method
|
||||||
|
// signatures but different outer types.
|
||||||
|
type NoopServiceA struct{ NoopService } |
||||||
|
type NoopServiceB struct{ NoopService } |
||||||
|
type NoopServiceC struct{ NoopService } |
||||||
|
type NoopServiceD struct{ NoopService } |
||||||
|
|
||||||
|
func NewNoopServiceA(*ServiceContext) (Service, error) { return new(NoopServiceA), nil } |
||||||
|
func NewNoopServiceB(*ServiceContext) (Service, error) { return new(NoopServiceB), nil } |
||||||
|
func NewNoopServiceC(*ServiceContext) (Service, error) { return new(NoopServiceC), nil } |
||||||
|
func NewNoopServiceD(*ServiceContext) (Service, error) { return new(NoopServiceD), nil } |
||||||
|
|
||||||
|
// InstrumentedService is an implementation of Service for which all interface
|
||||||
|
// methods can be instrumented both return value as well as event hook wise.
|
||||||
|
type InstrumentedService struct { |
||||||
|
protocols []p2p.Protocol |
||||||
|
start error |
||||||
|
stop error |
||||||
|
|
||||||
|
protocolsHook func() |
||||||
|
startHook func(*p2p.Server) |
||||||
|
stopHook func() |
||||||
|
} |
||||||
|
|
||||||
|
func NewInstrumentedService(*ServiceContext) (Service, error) { return new(InstrumentedService), nil } |
||||||
|
|
||||||
|
func (s *InstrumentedService) Protocols() []p2p.Protocol { |
||||||
|
if s.protocolsHook != nil { |
||||||
|
s.protocolsHook() |
||||||
|
} |
||||||
|
return s.protocols |
||||||
|
} |
||||||
|
|
||||||
|
func (s *InstrumentedService) Start(server *p2p.Server) error { |
||||||
|
if s.startHook != nil { |
||||||
|
s.startHook(server) |
||||||
|
} |
||||||
|
return s.start |
||||||
|
} |
||||||
|
|
||||||
|
func (s *InstrumentedService) Stop() error { |
||||||
|
if s.stopHook != nil { |
||||||
|
s.stopHook() |
||||||
|
} |
||||||
|
return s.stop |
||||||
|
} |
||||||
|
|
||||||
|
// InstrumentingWrapper is a method to specialize a service constructor returning
|
||||||
|
// a generic InstrumentedService into one returning a wrapping specific one.
|
||||||
|
type InstrumentingWrapper func(base ServiceConstructor) ServiceConstructor |
||||||
|
|
||||||
|
func InstrumentingWrapperMaker(base ServiceConstructor, kind reflect.Type) ServiceConstructor { |
||||||
|
return func(ctx *ServiceContext) (Service, error) { |
||||||
|
obj, err := base(ctx) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
wrapper := reflect.New(kind) |
||||||
|
wrapper.Elem().Field(0).Set(reflect.ValueOf(obj).Elem()) |
||||||
|
|
||||||
|
return wrapper.Interface().(Service), nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Set of services all wrapping the base InstrumentedService resulting in the
|
||||||
|
// same method signatures but different outer types.
|
||||||
|
type InstrumentedServiceA struct{ InstrumentedService } |
||||||
|
type InstrumentedServiceB struct{ InstrumentedService } |
||||||
|
type InstrumentedServiceC struct{ InstrumentedService } |
||||||
|
|
||||||
|
func InstrumentedServiceMakerA(base ServiceConstructor) ServiceConstructor { |
||||||
|
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceA{})) |
||||||
|
} |
||||||
|
|
||||||
|
func InstrumentedServiceMakerB(base ServiceConstructor) ServiceConstructor { |
||||||
|
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceB{})) |
||||||
|
} |
||||||
|
|
||||||
|
func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor { |
||||||
|
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{})) |
||||||
|
} |
Loading…
Reference in new issue