Unverified Commit c0c01612 authored by rene's avatar rene Committed by GitHub

node: refactor package node (#21105)

This PR significantly changes the APIs for instantiating Ethereum nodes in
a Go program. The new APIs are not backwards-compatible, but we feel that
this is made up for by the much simpler way of registering services on
node.Node. You can find more information and rationale in the design
document: https://gist.github.com/renaynay/5bec2de19fde66f4d04c535fd24f0775.

There is also a new feature in Node's Go API: it is now possible to
register arbitrary handlers on the user-facing HTTP server. In geth, this
facility is used to enable GraphQL.

There is a single minor change relevant for geth users in this PR: The
GraphQL API is no longer available separately from the JSON-RPC HTTP
server. If you want GraphQL, you need to enable it using the
./geth --http --graphql flag combination.

The --graphql.port and --graphql.addr flags are no longer available.
parent b2b14e6c
...@@ -235,23 +235,20 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u ...@@ -235,23 +235,20 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Assemble the Ethereum light client protocol // Assemble the Ethereum light client protocol
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { cfg := eth.DefaultConfig
cfg := eth.DefaultConfig cfg.SyncMode = downloader.LightSync
cfg.SyncMode = downloader.LightSync cfg.NetworkId = network
cfg.NetworkId = network cfg.Genesis = genesis
cfg.Genesis = genesis lesBackend, err := les.New(stack, &cfg)
return les.New(ctx, &cfg) if err != nil {
}); err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err)
return nil, err
} }
// Assemble the ethstats monitoring and reporting service' // Assemble the ethstats monitoring and reporting service'
if stats != "" { if stats != "" {
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil {
var serv *les.LightEthereum
ctx.Service(&serv)
return ethstats.New(stats, nil, serv)
}); err != nil {
return nil, err return nil, err
} }
} }
...@@ -268,7 +265,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u ...@@ -268,7 +265,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
// Attach to the client and retrieve and interesting metadatas // Attach to the client and retrieve and interesting metadatas
api, err := stack.Attach() api, err := stack.Attach()
if err != nil { if err != nil {
stack.Stop() stack.Close()
return nil, err return nil, err
} }
client := ethclient.NewClient(api) client := ethclient.NewClient(api)
......
...@@ -239,8 +239,9 @@ func initGenesis(ctx *cli.Context) error { ...@@ -239,8 +239,9 @@ func initGenesis(ctx *cli.Context) error {
if err := json.NewDecoder(file).Decode(genesis); err != nil { if err := json.NewDecoder(file).Decode(genesis); err != nil {
utils.Fatalf("invalid genesis file: %v", err) utils.Fatalf("invalid genesis file: %v", err)
} }
// Open an initialise both full and light databases // Open an initialise both full and light databases
stack := makeFullNode(ctx) stack, _ := makeConfigNode(ctx)
defer stack.Close() defer stack.Close()
for _, name := range []string{"chaindata", "lightchaindata"} { for _, name := range []string{"chaindata", "lightchaindata"} {
...@@ -277,7 +278,7 @@ func importChain(ctx *cli.Context) error { ...@@ -277,7 +278,7 @@ func importChain(ctx *cli.Context) error {
utils.SetupMetrics(ctx) utils.SetupMetrics(ctx)
// Start system runtime metrics collection // Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second) go metrics.CollectProcessMetrics(3 * time.Second)
stack := makeFullNode(ctx) stack, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
chain, db := utils.MakeChain(ctx, stack, false) chain, db := utils.MakeChain(ctx, stack, false)
...@@ -371,7 +372,7 @@ func exportChain(ctx *cli.Context) error { ...@@ -371,7 +372,7 @@ func exportChain(ctx *cli.Context) error {
if len(ctx.Args()) < 1 { if len(ctx.Args()) < 1 {
utils.Fatalf("This command requires an argument.") utils.Fatalf("This command requires an argument.")
} }
stack := makeFullNode(ctx) stack, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
chain, _ := utils.MakeChain(ctx, stack, true) chain, _ := utils.MakeChain(ctx, stack, true)
...@@ -406,7 +407,7 @@ func importPreimages(ctx *cli.Context) error { ...@@ -406,7 +407,7 @@ func importPreimages(ctx *cli.Context) error {
if len(ctx.Args()) < 1 { if len(ctx.Args()) < 1 {
utils.Fatalf("This command requires an argument.") utils.Fatalf("This command requires an argument.")
} }
stack := makeFullNode(ctx) stack, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack) db := utils.MakeChainDatabase(ctx, stack)
...@@ -424,7 +425,7 @@ func exportPreimages(ctx *cli.Context) error { ...@@ -424,7 +425,7 @@ func exportPreimages(ctx *cli.Context) error {
if len(ctx.Args()) < 1 { if len(ctx.Args()) < 1 {
utils.Fatalf("This command requires an argument.") utils.Fatalf("This command requires an argument.")
} }
stack := makeFullNode(ctx) stack, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack) db := utils.MakeChainDatabase(ctx, stack)
...@@ -446,7 +447,7 @@ func copyDb(ctx *cli.Context) error { ...@@ -446,7 +447,7 @@ func copyDb(ctx *cli.Context) error {
utils.Fatalf("Source ancient chain directory path argument missing") utils.Fatalf("Source ancient chain directory path argument missing")
} }
// Initialize a new chain for the running node to sync into // Initialize a new chain for the running node to sync into
stack := makeFullNode(ctx) stack, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
chain, chainDb := utils.MakeChain(ctx, stack, false) chain, chainDb := utils.MakeChain(ctx, stack, false)
...@@ -554,7 +555,7 @@ func confirmAndRemoveDB(database string, kind string) { ...@@ -554,7 +555,7 @@ func confirmAndRemoveDB(database string, kind string) {
} }
func dump(ctx *cli.Context) error { func dump(ctx *cli.Context) error {
stack := makeFullNode(ctx) stack, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
chain, chainDb := utils.MakeChain(ctx, stack, true) chain, chainDb := utils.MakeChain(ctx, stack, true)
......
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
...@@ -144,9 +145,10 @@ func enableWhisper(ctx *cli.Context) bool { ...@@ -144,9 +145,10 @@ func enableWhisper(ctx *cli.Context) bool {
return false return false
} }
func makeFullNode(ctx *cli.Context) *node.Node { func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
stack, cfg := makeConfigNode(ctx) stack, cfg := makeConfigNode(ctx)
utils.RegisterEthService(stack, &cfg.Eth)
backend := utils.RegisterEthService(stack, &cfg.Eth)
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
shhEnabled := enableWhisper(ctx) shhEnabled := enableWhisper(ctx)
...@@ -165,13 +167,13 @@ func makeFullNode(ctx *cli.Context) *node.Node { ...@@ -165,13 +167,13 @@ func makeFullNode(ctx *cli.Context) *node.Node {
} }
// Configure GraphQL if requested // Configure GraphQL if requested
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) utils.RegisterGraphQLService(stack, backend, cfg.Node)
} }
// Add the Ethereum Stats daemon if requested. // Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != "" { if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
} }
return stack return stack, backend
} }
// dumpConfig is the dumpconfig command. // dumpConfig is the dumpconfig command.
......
...@@ -78,12 +78,12 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons ...@@ -78,12 +78,12 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons
func localConsole(ctx *cli.Context) error { func localConsole(ctx *cli.Context) error {
// Create and start the node based on the CLI flags // Create and start the node based on the CLI flags
prepare(ctx) prepare(ctx)
node := makeFullNode(ctx) stack, backend := makeFullNode(ctx)
startNode(ctx, node) startNode(ctx, stack, backend)
defer node.Close() defer stack.Close()
// Attach to the newly started node and start the JavaScript console // Attach to the newly started node and start the JavaScript console
client, err := node.Attach() client, err := stack.Attach()
if err != nil { if err != nil {
utils.Fatalf("Failed to attach to the inproc geth: %v", err) utils.Fatalf("Failed to attach to the inproc geth: %v", err)
} }
...@@ -190,12 +190,12 @@ func dialRPC(endpoint string) (*rpc.Client, error) { ...@@ -190,12 +190,12 @@ func dialRPC(endpoint string) (*rpc.Client, error) {
// everything down. // everything down.
func ephemeralConsole(ctx *cli.Context) error { func ephemeralConsole(ctx *cli.Context) error {
// Create and start the node based on the CLI flags // Create and start the node based on the CLI flags
node := makeFullNode(ctx) stack, backend := makeFullNode(ctx)
startNode(ctx, node) startNode(ctx, stack, backend)
defer node.Close() defer stack.Close()
// Attach to the newly started node and start the JavaScript console // Attach to the newly started node and start the JavaScript console
client, err := node.Attach() client, err := stack.Attach()
if err != nil { if err != nil {
utils.Fatalf("Failed to attach to the inproc geth: %v", err) utils.Fatalf("Failed to attach to the inproc geth: %v", err)
} }
......
...@@ -119,8 +119,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc ...@@ -119,8 +119,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc
} else { } else {
// Force chain initialization // Force chain initialization
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...) runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit()
geth.WaitExit()
} }
// Retrieve the DAO config flag from the database // Retrieve the DAO config flag from the database
path := filepath.Join(datadir, "geth", "chaindata") path := filepath.Join(datadir, "geth", "chaindata")
......
...@@ -36,8 +36,8 @@ import ( ...@@ -36,8 +36,8 @@ import (
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
...@@ -171,8 +171,6 @@ var ( ...@@ -171,8 +171,6 @@ var (
utils.LegacyRPCCORSDomainFlag, utils.LegacyRPCCORSDomainFlag,
utils.LegacyRPCVirtualHostsFlag, utils.LegacyRPCVirtualHostsFlag,
utils.GraphQLEnabledFlag, utils.GraphQLEnabledFlag,
utils.GraphQLListenAddrFlag,
utils.GraphQLPortFlag,
utils.GraphQLCORSDomainFlag, utils.GraphQLCORSDomainFlag,
utils.GraphQLVirtualHostsFlag, utils.GraphQLVirtualHostsFlag,
utils.HTTPApiFlag, utils.HTTPApiFlag,
...@@ -350,18 +348,20 @@ func geth(ctx *cli.Context) error { ...@@ -350,18 +348,20 @@ func geth(ctx *cli.Context) error {
if args := ctx.Args(); len(args) > 0 { if args := ctx.Args(); len(args) > 0 {
return fmt.Errorf("invalid command: %q", args[0]) return fmt.Errorf("invalid command: %q", args[0])
} }
prepare(ctx) prepare(ctx)
node := makeFullNode(ctx) stack, backend := makeFullNode(ctx)
defer node.Close() defer stack.Close()
startNode(ctx, node)
node.Wait() startNode(ctx, stack, backend)
stack.Wait()
return nil return nil
} }
// startNode boots up the system node and all registered protocols, after which // 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 // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner. // miner.
func startNode(ctx *cli.Context, stack *node.Node) { func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) {
debug.Memsize.Add("node", stack) debug.Memsize.Add("node", stack)
// Start up the node itself // Start up the node itself
...@@ -381,25 +381,6 @@ func startNode(ctx *cli.Context, stack *node.Node) { ...@@ -381,25 +381,6 @@ func startNode(ctx *cli.Context, stack *node.Node) {
} }
ethClient := ethclient.NewClient(rpcClient) ethClient := ethclient.NewClient(rpcClient)
// Set contract backend for ethereum service if local node
// is serving LES requests.
if ctx.GlobalInt(utils.LegacyLightServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 {
var ethService *eth.Ethereum
if err := stack.Service(&ethService); err != nil {
utils.Fatalf("Failed to retrieve ethereum service: %v", err)
}
ethService.SetContractBackend(ethClient)
}
// Set contract backend for les service if local node is
// running as a light client.
if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
var lesService *les.LightEthereum
if err := stack.Service(&lesService); err != nil {
utils.Fatalf("Failed to retrieve light ethereum service: %v", err)
}
lesService.SetContractBackend(ethClient)
}
go func() { go func() {
// Open any wallets already attached // Open any wallets already attached
for _, wallet := range stack.AccountManager().Wallets() { for _, wallet := range stack.AccountManager().Wallets() {
...@@ -451,7 +432,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { ...@@ -451,7 +432,7 @@ func startNode(ctx *cli.Context, stack *node.Node) {
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute { if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(), log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
"age", common.PrettyAge(timestamp)) "age", common.PrettyAge(timestamp))
stack.Stop() stack.Close()
} }
} }
}() }()
...@@ -463,24 +444,24 @@ func startNode(ctx *cli.Context, stack *node.Node) { ...@@ -463,24 +444,24 @@ func startNode(ctx *cli.Context, stack *node.Node) {
if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
utils.Fatalf("Light clients do not support mining") utils.Fatalf("Light clients do not support mining")
} }
var ethereum *eth.Ethereum ethBackend, ok := backend.(*eth.EthAPIBackend)
if err := stack.Service(&ethereum); err != nil { if !ok {
utils.Fatalf("Ethereum service not running: %v", err) utils.Fatalf("Ethereum service not running: %v", err)
} }
// Set the gas price to the limits from the CLI and start mining // Set the gas price to the limits from the CLI and start mining
gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) { if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) {
gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name) gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name)
} }
ethereum.TxPool().SetGasPrice(gasprice) ethBackend.TxPool().SetGasPrice(gasprice)
// start mining
threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name)
if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) {
threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name) threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name)
log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads") log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads")
} }
if err := ethBackend.StartMining(threads); err != nil {
if err := ethereum.StartMining(threads); err != nil {
utils.Fatalf("Failed to start mining: %v", err) utils.Fatalf("Failed to start mining: %v", err)
} }
} }
......
...@@ -142,8 +142,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ ...@@ -142,8 +142,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.WSApiFlag, utils.WSApiFlag,
utils.WSAllowedOriginsFlag, utils.WSAllowedOriginsFlag,
utils.GraphQLEnabledFlag, utils.GraphQLEnabledFlag,
utils.GraphQLListenAddrFlag,
utils.GraphQLPortFlag,
utils.GraphQLCORSDomainFlag, utils.GraphQLCORSDomainFlag,
utils.GraphQLVirtualHostsFlag, utils.GraphQLVirtualHostsFlag,
utils.RPCGlobalGasCap, utils.RPCGlobalGasCap,
...@@ -231,6 +229,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{ ...@@ -231,6 +229,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.LegacyWSApiFlag, utils.LegacyWSApiFlag,
utils.LegacyGpoBlocksFlag, utils.LegacyGpoBlocksFlag,
utils.LegacyGpoPercentileFlag, utils.LegacyGpoPercentileFlag,
utils.LegacyGraphQLListenAddrFlag,
utils.LegacyGraphQLPortFlag,
}, debug.DeprecatedFlags...), }, debug.DeprecatedFlags...),
}, },
{ {
......
...@@ -289,7 +289,7 @@ func createNode(ctx *cli.Context) error { ...@@ -289,7 +289,7 @@ func createNode(ctx *cli.Context) error {
config.PrivateKey = privKey config.PrivateKey = privKey
} }
if services := ctx.String("services"); services != "" { if services := ctx.String("services"); services != "" {
config.Services = strings.Split(services, ",") config.Lifecycles = strings.Split(services, ",")
} }
node, err := client.CreateNode(config) node, err := client.CreateNode(config)
if err != nil { if err != nil {
......
...@@ -73,7 +73,7 @@ func StartNode(stack *node.Node) { ...@@ -73,7 +73,7 @@ func StartNode(stack *node.Node) {
defer signal.Stop(sigc) defer signal.Stop(sigc)
<-sigc <-sigc
log.Info("Got interrupt, shutting down...") log.Info("Got interrupt, shutting down...")
go stack.Stop() go stack.Close()
for i := 10; i > 0; i-- { for i := 10; i > 0; i-- {
<-sigc <-sigc
if i > 1 { if i > 1 {
......
...@@ -19,7 +19,6 @@ package utils ...@@ -19,7 +19,6 @@ package utils
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
...@@ -49,6 +48,7 @@ import ( ...@@ -49,6 +48,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/ethstats"
"github.com/ethereum/go-ethereum/graphql" "github.com/ethereum/go-ethereum/graphql"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -63,7 +63,6 @@ import ( ...@@ -63,7 +63,6 @@ import (
"github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
pcsclite "github.com/gballet/go-libpcsclite" pcsclite "github.com/gballet/go-libpcsclite"
cli "gopkg.in/urfave/cli.v1" cli "gopkg.in/urfave/cli.v1"
...@@ -517,6 +516,20 @@ var ( ...@@ -517,6 +516,20 @@ var (
Usage: "API's offered over the HTTP-RPC interface", Usage: "API's offered over the HTTP-RPC interface",
Value: "", Value: "",
} }
GraphQLEnabledFlag = cli.BoolFlag{
Name: "graphql",
Usage: "Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.",
}
GraphQLCORSDomainFlag = cli.StringFlag{
Name: "graphql.corsdomain",
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
Value: "",
}
GraphQLVirtualHostsFlag = cli.StringFlag{
Name: "graphql.vhosts",
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","),
}
WSEnabledFlag = cli.BoolFlag{ WSEnabledFlag = cli.BoolFlag{
Name: "ws", Name: "ws",
Usage: "Enable the WS-RPC server", Usage: "Enable the WS-RPC server",
...@@ -541,30 +554,6 @@ var ( ...@@ -541,30 +554,6 @@ var (
Usage: "Origins from which to accept websockets requests", Usage: "Origins from which to accept websockets requests",
Value: "", Value: "",
} }
GraphQLEnabledFlag = cli.BoolFlag{
Name: "graphql",
Usage: "Enable the GraphQL server",
}
GraphQLListenAddrFlag = cli.StringFlag{
Name: "graphql.addr",
Usage: "GraphQL server listening interface",
Value: node.DefaultGraphQLHost,
}
GraphQLPortFlag = cli.IntFlag{
Name: "graphql.port",
Usage: "GraphQL server listening port",
Value: node.DefaultGraphQLPort,
}
GraphQLCORSDomainFlag = cli.StringFlag{
Name: "graphql.corsdomain",
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
Value: "",
}
GraphQLVirtualHostsFlag = cli.StringFlag{
Name: "graphql.vhosts",
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","),
}
ExecFlag = cli.StringFlag{ ExecFlag = cli.StringFlag{
Name: "exec", Name: "exec",
Usage: "Execute JavaScript statement", Usage: "Execute JavaScript statement",
...@@ -951,13 +940,6 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { ...@@ -951,13 +940,6 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
// setGraphQL creates the GraphQL listener interface string from the set // setGraphQL creates the GraphQL listener interface string from the set
// command line flags, returning empty if the GraphQL endpoint is disabled. // command line flags, returning empty if the GraphQL endpoint is disabled.
func setGraphQL(ctx *cli.Context, cfg *node.Config) { func setGraphQL(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalBool(GraphQLEnabledFlag.Name) && cfg.GraphQLHost == "" {
cfg.GraphQLHost = "127.0.0.1"
if ctx.GlobalIsSet(GraphQLListenAddrFlag.Name) {
cfg.GraphQLHost = ctx.GlobalString(GraphQLListenAddrFlag.Name)
}
}
cfg.GraphQLPort = ctx.GlobalInt(GraphQLPortFlag.Name)
if ctx.GlobalIsSet(GraphQLCORSDomainFlag.Name) { if ctx.GlobalIsSet(GraphQLCORSDomainFlag.Name) {
cfg.GraphQLCors = splitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name)) cfg.GraphQLCors = splitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name))
} }
...@@ -1692,70 +1674,46 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { ...@@ -1692,70 +1674,46 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) {
} }
// RegisterEthService adds an Ethereum client to the stack. // RegisterEthService adds an Ethereum client to the stack.
func RegisterEthService(stack *node.Node, cfg *eth.Config) { func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend {
var err error
if cfg.SyncMode == downloader.LightSync { if cfg.SyncMode == downloader.LightSync {
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { backend, err := les.New(stack, cfg)
return les.New(ctx, cfg) if err != nil {
}) Fatalf("Failed to register the Ethereum service: %v", err)
}
return backend.ApiBackend
} else { } else {
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { backend, err := eth.New(stack, cfg)
fullNode, err := eth.New(ctx, cfg) if err != nil {
if fullNode != nil && cfg.LightServ > 0 { Fatalf("Failed to register the Ethereum service: %v", err)
ls, _ := les.NewLesServer(fullNode, cfg) }
fullNode.AddLesServer(ls) if cfg.LightServ > 0 {
_, err := les.NewLesServer(stack, backend, cfg)
if err != nil {
Fatalf("Failed to create the LES server: %v", err)
} }
return fullNode, err }
}) return backend.APIBackend
}
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
} }
} }
// RegisterShhService configures Whisper and adds it to the given node. // RegisterShhService configures Whisper and adds it to the given node.
func RegisterShhService(stack *node.Node, cfg *whisper.Config) { func RegisterShhService(stack *node.Node, cfg *whisper.Config) {
if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) { if _, err := whisper.New(stack, cfg); err != nil {
return whisper.New(cfg), nil
}); err != nil {
Fatalf("Failed to register the Whisper service: %v", err) Fatalf("Failed to register the Whisper service: %v", err)
} }
} }
// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to
// the given node. // the given node.
func RegisterEthStatsService(stack *node.Node, url string) { func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) {
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { if err := ethstats.New(stack, backend, backend.Engine(), url); err != nil {
// Retrieve both eth and les services
var ethServ *eth.Ethereum
ctx.Service(&ethServ)
var lesServ *les.LightEthereum
ctx.Service(&lesServ)
// Let ethstats use whichever is not nil
return ethstats.New(url, ethServ, lesServ)
}); err != nil {
Fatalf("Failed to register the Ethereum Stats service: %v", err) Fatalf("Failed to register the Ethereum Stats service: %v", err)
} }
} }
// RegisterGraphQLService is a utility function to construct a new service and register it against a node. // RegisterGraphQLService is a utility function to construct a new service and register it against a node.
func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.Config) {
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { if err := graphql.New(stack, backend, cfg.GraphQLCors, cfg.GraphQLVirtualHosts); err != nil {
// Try to construct the GraphQL service backed by a full node
var ethServ *eth.Ethereum
if err := ctx.Service(&ethServ); err == nil {
return graphql.New(ethServ.APIBackend, endpoint, cors, vhosts, timeouts)
}
// Try to construct the GraphQL service backed by a light node
var lesServ *les.LightEthereum
if err := ctx.Service(&lesServ); err == nil {
return graphql.New(lesServ.ApiBackend, endpoint, cors, vhosts, timeouts)
}
// Well, this should not have happened, bail out
return nil, errors.New("no Ethereum service")
}); err != nil {
Fatalf("Failed to register the GraphQL service: %v", err) Fatalf("Failed to register the GraphQL service: %v", err)
} }
} }
......
...@@ -89,6 +89,8 @@ var ( ...@@ -89,6 +89,8 @@ var (
Name: "testnet", Name: "testnet",
Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)", Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)",
} }
// (Deprecated May 2020, shown in aliased flags section)
LegacyRPCEnabledFlag = cli.BoolFlag{ LegacyRPCEnabledFlag = cli.BoolFlag{
Name: "rpc", Name: "rpc",
Usage: "Enable the HTTP-RPC server (deprecated, use --http)", Usage: "Enable the HTTP-RPC server (deprecated, use --http)",
...@@ -158,6 +160,17 @@ var ( ...@@ -158,6 +160,17 @@ var (
Usage: "Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes) (deprecated, use --bootnodes)", Usage: "Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes) (deprecated, use --bootnodes)",
Value: "", Value: "",
} }
// (Deprecated July 2020, shown in aliased flags section)
LegacyGraphQLListenAddrFlag = cli.StringFlag{
Name: "graphql.addr",
Usage: "GraphQL server listening interface (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)",
}
LegacyGraphQLPortFlag = cli.IntFlag{
Name: "graphql.port",
Usage: "GraphQL server listening port (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)",
Value: node.DefaultHTTPPort,
}
) )
// showDeprecated displays deprecated flags that will be soon removed from the codebase. // showDeprecated displays deprecated flags that will be soon removed from the codebase.
......
...@@ -221,8 +221,7 @@ func initialize() { ...@@ -221,8 +221,7 @@ func initialize() {
MaxMessageSize: uint32(*argMaxSize), MaxMessageSize: uint32(*argMaxSize),
MinimumAcceptedPOW: *argPoW, MinimumAcceptedPOW: *argPoW,
} }
shh = whisper.StandaloneWhisperService(cfg)
shh = whisper.New(cfg)
if *argPoW != whisper.DefaultMinimumPoW { if *argPoW != whisper.DefaultMinimumPoW {
err := shh.SetMinimumPoW(*argPoW) err := shh.SetMinimumPoW(*argPoW)
...@@ -433,7 +432,7 @@ func run() { ...@@ -433,7 +432,7 @@ func run() {
return return
} }
defer server.Stop() defer server.Stop()
shh.Start(nil) shh.Start()
defer shh.Stop() defer shh.Stop()
if !*forwarderMode { if !*forwarderMode {
......
...@@ -109,7 +109,8 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { ...@@ -109,7 +109,8 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
if confOverride != nil { if confOverride != nil {
confOverride(ethConf) confOverride(ethConf)
} }
if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { ethBackend, err := eth.New(stack, ethConf)
if err != nil {
t.Fatalf("failed to register Ethereum protocol: %v", err) t.Fatalf("failed to register Ethereum protocol: %v", err)
} }
// Start the node and assemble the JavaScript console around it // Start the node and assemble the JavaScript console around it
...@@ -135,13 +136,10 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { ...@@ -135,13 +136,10 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
t.Fatalf("failed to create JavaScript console: %v", err) t.Fatalf("failed to create JavaScript console: %v", err)
} }
// Create the final tester and return // Create the final tester and return
var ethereum *eth.Ethereum
stack.Service(&ethereum)
return &tester{ return &tester{
workspace: workspace, workspace: workspace,
stack: stack, stack: stack,
ethereum: ethereum, ethereum: ethBackend,
console: console, console: console,
input: prompter, input: prompter,
output: printer, output: printer,
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
...@@ -74,6 +75,7 @@ type freezer struct { ...@@ -74,6 +75,7 @@ type freezer struct {
tables map[string]*freezerTable // Data tables for storing everything tables map[string]*freezerTable // Data tables for storing everything
instanceLock fileutil.Releaser // File-system lock to prevent double opens instanceLock fileutil.Releaser // File-system lock to prevent double opens
quit chan struct{} quit chan struct{}
closeOnce sync.Once
} }
// newFreezer creates a chain freezer that moves ancient chain data into // newFreezer creates a chain freezer that moves ancient chain data into
...@@ -128,16 +130,18 @@ func newFreezer(datadir string, namespace string) (*freezer, error) { ...@@ -128,16 +130,18 @@ func newFreezer(datadir string, namespace string) (*freezer, error) {
// Close terminates the chain freezer, unmapping all the data files. // Close terminates the chain freezer, unmapping all the data files.
func (f *freezer) Close() error { func (f *freezer) Close() error {
f.quit <- struct{}{}
var errs []error var errs []error
for _, table := range f.tables { f.closeOnce.Do(func() {
if err := table.Close(); err != nil { f.quit <- struct{}{}
for _, table := range f.tables {
if err := table.Close(); err != nil {
errs = append(errs, err)
}
}
if err := f.instanceLock.Release(); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
} })
if err := f.instanceLock.Release(); err != nil {
errs = append(errs, err)
}
if errs != nil { if errs != nil {
return fmt.Errorf("%v", errs) return fmt.Errorf("%v", errs)
} }
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
...@@ -33,6 +34,7 @@ import ( ...@@ -33,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
...@@ -257,6 +259,10 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, ...@@ -257,6 +259,10 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions,
return b.eth.TxPool().Content() return b.eth.TxPool().Content()
} }
func (b *EthAPIBackend) TxPool() *core.TxPool {
return b.eth.TxPool()
}
func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
return b.eth.TxPool().SubscribeNewTxsEvent(ch) return b.eth.TxPool().SubscribeNewTxsEvent(ch)
} }
...@@ -307,3 +313,19 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma ...@@ -307,3 +313,19 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
} }
} }
func (b *EthAPIBackend) Engine() consensus.Engine {
return b.eth.engine
}
func (b *EthAPIBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader()
}
func (b *EthAPIBackend) Miner() *miner.Miner {
return b.eth.Miner()
}
func (b *EthAPIBackend) StartMining(threads int) error {
return b.eth.StartMining(threads)
}
...@@ -26,7 +26,6 @@ import ( ...@@ -26,7 +26,6 @@ import (
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
...@@ -54,15 +53,6 @@ import ( ...@@ -54,15 +53,6 @@ import (
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
type LesServer interface {
Start(srvr *p2p.Server)
Stop()
APIs() []rpc.API
Protocols() []p2p.Protocol
SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
SetContractBackend(bind.ContractBackend)
}
// Ethereum implements the Ethereum full node service. // Ethereum implements the Ethereum full node service.
type Ethereum struct { type Ethereum struct {
config *Config config *Config
...@@ -71,7 +61,6 @@ type Ethereum struct { ...@@ -71,7 +61,6 @@ type Ethereum struct {
txPool *core.TxPool txPool *core.TxPool
blockchain *core.BlockChain blockchain *core.BlockChain
protocolManager *ProtocolManager protocolManager *ProtocolManager
lesServer LesServer
dialCandidates enode.Iterator dialCandidates enode.Iterator
// DB interfaces // DB interfaces
...@@ -94,25 +83,14 @@ type Ethereum struct { ...@@ -94,25 +83,14 @@ type Ethereum struct {
networkID uint64 networkID uint64
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) p2pServer *p2p.Server
}
func (s *Ethereum) AddLesServer(ls LesServer) { lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
s.lesServer = ls
ls.SetBloomBitsIndexer(s.bloomIndexer)
}
// SetClient sets a rpc client which connecting to our local node.
func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) {
// Pass the rpc client to les server if it is enabled.
if s.lesServer != nil {
s.lesServer.SetContractBackend(backend)
}
} }
// New creates a new Ethereum object (including the // New creates a new Ethereum object (including the
// initialisation of the common Ethereum object) // initialisation of the common Ethereum object)
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { func New(stack *node.Node, config *Config) (*Ethereum, error) {
// Ensure configuration values are compatible and sane // Ensure configuration values are compatible and sane
if config.SyncMode == downloader.LightSync { if config.SyncMode == downloader.LightSync {
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
...@@ -136,7 +114,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -136,7 +114,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
// Assemble the Ethereum object // Assemble the Ethereum object
chainDb, err := ctx.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/") chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/")
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -149,15 +127,16 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -149,15 +127,16 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth := &Ethereum{ eth := &Ethereum{
config: config, config: config,
chainDb: chainDb, chainDb: chainDb,
eventMux: ctx.EventMux, eventMux: stack.EventMux(),
accountManager: ctx.AccountManager, accountManager: stack.AccountManager(),
engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), engine: CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb),
closeBloomHandler: make(chan struct{}), closeBloomHandler: make(chan struct{}),
networkID: config.NetworkId, networkID: config.NetworkId,
gasPrice: config.Miner.GasPrice, gasPrice: config.Miner.GasPrice,
etherbase: config.Miner.Etherbase, etherbase: config.Miner.Etherbase,
bloomRequests: make(chan chan *bloombits.Retrieval), bloomRequests: make(chan chan *bloombits.Retrieval),
bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
p2pServer: stack.Server(),
} }
bcVersion := rawdb.ReadDatabaseVersion(chainDb) bcVersion := rawdb.ReadDatabaseVersion(chainDb)
...@@ -183,7 +162,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -183,7 +162,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
} }
cacheConfig = &core.CacheConfig{ cacheConfig = &core.CacheConfig{
TrieCleanLimit: config.TrieCleanCache, TrieCleanLimit: config.TrieCleanCache,
TrieCleanJournal: ctx.ResolvePath(config.TrieCleanCacheJournal), TrieCleanJournal: stack.ResolvePath(config.TrieCleanCacheJournal),
TrieCleanRejournal: config.TrieCleanCacheRejournal, TrieCleanRejournal: config.TrieCleanCacheRejournal,
TrieCleanNoPrefetch: config.NoPrefetch, TrieCleanNoPrefetch: config.NoPrefetch,
TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyLimit: config.TrieDirtyCache,
...@@ -205,7 +184,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -205,7 +184,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth.bloomIndexer.Start(eth.blockchain) eth.bloomIndexer.Start(eth.blockchain)
if config.TxPool.Journal != "" { if config.TxPool.Journal != "" {
config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal) config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
} }
eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain)
...@@ -221,18 +200,25 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { ...@@ -221,18 +200,25 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil} eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), eth, nil}
gpoParams := config.GPO gpoParams := config.GPO
if gpoParams.Default == nil { if gpoParams.Default == nil {
gpoParams.Default = config.Miner.GasPrice gpoParams.Default = config.Miner.GasPrice
} }
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
eth.dialCandidates, err = eth.setupDiscovery(&ctx.Config.P2P) eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Start the RPC service
eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, eth.NetVersion())
// Register the backend on the node
stack.RegisterAPIs(eth.APIs())
stack.RegisterProtocols(eth.Protocols())
stack.RegisterLifecycle(eth)
return eth, nil return eth, nil
} }
...@@ -254,7 +240,7 @@ func makeExtraData(extra []byte) []byte { ...@@ -254,7 +240,7 @@ func makeExtraData(extra []byte) []byte {
} }
// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service // CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service
func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine {
// If proof-of-authority is requested, set it up // If proof-of-authority is requested, set it up
if chainConfig.Clique != nil { if chainConfig.Clique != nil {
return clique.New(chainConfig.Clique, db) return clique.New(chainConfig.Clique, db)
...@@ -272,7 +258,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo ...@@ -272,7 +258,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo
return ethash.NewShared() return ethash.NewShared()
default: default:
engine := ethash.New(ethash.Config{ engine := ethash.New(ethash.Config{
CacheDir: ctx.ResolvePath(config.CacheDir), CacheDir: stack.ResolvePath(config.CacheDir),
CachesInMem: config.CachesInMem, CachesInMem: config.CachesInMem,
CachesOnDisk: config.CachesOnDisk, CachesOnDisk: config.CachesOnDisk,
CachesLockMmap: config.CachesLockMmap, CachesLockMmap: config.CachesLockMmap,
...@@ -291,18 +277,9 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo ...@@ -291,18 +277,9 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo
func (s *Ethereum) APIs() []rpc.API { func (s *Ethereum) APIs() []rpc.API {
apis := ethapi.GetAPIs(s.APIBackend) apis := ethapi.GetAPIs(s.APIBackend)
// Append any APIs exposed explicitly by the les server
if s.lesServer != nil {
apis = append(apis, s.lesServer.APIs()...)
}
// Append any APIs exposed explicitly by the consensus engine // Append any APIs exposed explicitly by the consensus engine
apis = append(apis, s.engine.APIs(s.BlockChain())...) apis = append(apis, s.engine.APIs(s.BlockChain())...)
// Append any APIs exposed explicitly by the les server
if s.lesServer != nil {
apis = append(apis, s.lesServer.APIs()...)
}
// Append all the local APIs and return // Append all the local APIs and return
return append(apis, []rpc.API{ return append(apis, []rpc.API{
{ {
...@@ -517,8 +494,9 @@ func (s *Ethereum) NetVersion() uint64 { return s.networkID } ...@@ -517,8 +494,9 @@ func (s *Ethereum) NetVersion() uint64 { return s.networkID }
func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 }
func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning }
func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
// Protocols implements node.Service, returning all the currently configured // Protocols returns all the currently configured
// network protocols to start. // network protocols to start.
func (s *Ethereum) Protocols() []p2p.Protocol { func (s *Ethereum) Protocols() []p2p.Protocol {
protos := make([]p2p.Protocol, len(ProtocolVersions)) protos := make([]p2p.Protocol, len(ProtocolVersions))
...@@ -527,47 +505,35 @@ func (s *Ethereum) Protocols() []p2p.Protocol { ...@@ -527,47 +505,35 @@ func (s *Ethereum) Protocols() []p2p.Protocol {
protos[i].Attributes = []enr.Entry{s.currentEthEntry()} protos[i].Attributes = []enr.Entry{s.currentEthEntry()}
protos[i].DialCandidates = s.dialCandidates protos[i].DialCandidates = s.dialCandidates
} }
if s.lesServer != nil {
protos = append(protos, s.lesServer.Protocols()...)
}
return protos return protos
} }
// Start implements node.Service, starting all internal goroutines needed by the // Start implements node.Lifecycle, starting all internal goroutines needed by the
// Ethereum protocol implementation. // Ethereum protocol implementation.
func (s *Ethereum) Start(srvr *p2p.Server) error { func (s *Ethereum) Start() error {
s.startEthEntryUpdate(srvr.LocalNode()) s.startEthEntryUpdate(s.p2pServer.LocalNode())
// Start the bloom bits servicing goroutines // Start the bloom bits servicing goroutines
s.startBloomHandlers(params.BloomBitsBlocks) s.startBloomHandlers(params.BloomBitsBlocks)
// Start the RPC service
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.NetVersion())
// Figure out a max peers count based on the server limits // Figure out a max peers count based on the server limits
maxPeers := srvr.MaxPeers maxPeers := s.p2pServer.MaxPeers
if s.config.LightServ > 0 { if s.config.LightServ > 0 {
if s.config.LightPeers >= srvr.MaxPeers { if s.config.LightPeers >= s.p2pServer.MaxPeers {
return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, srvr.MaxPeers) return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers)
} }
maxPeers -= s.config.LightPeers maxPeers -= s.config.LightPeers
} }
// Start the networking layer and the light server if requested // Start the networking layer and the light server if requested
s.protocolManager.Start(maxPeers) s.protocolManager.Start(maxPeers)
if s.lesServer != nil {
s.lesServer.Start(srvr)
}
return nil return nil
} }
// Stop implements node.Service, terminating all internal goroutines used by the // Stop implements node.Lifecycle, terminating all internal goroutines used by the
// Ethereum protocol. // Ethereum protocol.
func (s *Ethereum) Stop() error { func (s *Ethereum) Stop() error {
// Stop all the peer-related stuff first. // Stop all the peer-related stuff first.
s.protocolManager.Stop() s.protocolManager.Stop()
if s.lesServer != nil {
s.lesServer.Stop()
}
// Then stop everything else. // Then stop everything else.
s.bloomIndexer.Close() s.bloomIndexer.Close()
......
...@@ -187,17 +187,18 @@ var ( ...@@ -187,17 +187,18 @@ var (
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
// Generate test chain. // Generate test chain.
genesis, blocks := generateTestChain() genesis, blocks := generateTestChain()
// Create node
// Start Ethereum service.
var ethservice *eth.Ethereum
n, err := node.New(&node.Config{}) n, err := node.New(&node.Config{})
n.Register(func(ctx *node.ServiceContext) (node.Service, error) { if err != nil {
config := &eth.Config{Genesis: genesis} t.Fatalf("can't create new node: %v", err)
config.Ethash.PowMode = ethash.ModeFake }
ethservice, err = eth.New(ctx, config) // Create Ethereum Service
return ethservice, err config := &eth.Config{Genesis: genesis}
}) config.Ethash.PowMode = ethash.ModeFake
ethservice, err := eth.New(n, config)
if err != nil {
t.Fatalf("can't create new ethereum service: %v", err)
}
// Import the test chain. // Import the test chain.
if err := n.Start(); err != nil { if err := n.Start(); err != nil {
t.Fatalf("can't start test node: %v", err) t.Fatalf("can't start test node: %v", err)
...@@ -231,7 +232,7 @@ func generateTestChain() (*core.Genesis, []*types.Block) { ...@@ -231,7 +232,7 @@ func generateTestChain() (*core.Genesis, []*types.Block) {
func TestHeader(t *testing.T) { func TestHeader(t *testing.T) {
backend, chain := newTestBackend(t) backend, chain := newTestBackend(t)
client, _ := backend.Attach() client, _ := backend.Attach()
defer backend.Stop() defer backend.Close()
defer client.Close() defer client.Close()
tests := map[string]struct { tests := map[string]struct {
...@@ -275,7 +276,7 @@ func TestHeader(t *testing.T) { ...@@ -275,7 +276,7 @@ func TestHeader(t *testing.T) {
func TestBalanceAt(t *testing.T) { func TestBalanceAt(t *testing.T) {
backend, _ := newTestBackend(t) backend, _ := newTestBackend(t)
client, _ := backend.Attach() client, _ := backend.Attach()
defer backend.Stop() defer backend.Close()
defer client.Close() defer client.Close()
tests := map[string]struct { tests := map[string]struct {
...@@ -321,7 +322,7 @@ func TestBalanceAt(t *testing.T) { ...@@ -321,7 +322,7 @@ func TestBalanceAt(t *testing.T) {
func TestTransactionInBlockInterrupted(t *testing.T) { func TestTransactionInBlockInterrupted(t *testing.T) {
backend, _ := newTestBackend(t) backend, _ := newTestBackend(t)
client, _ := backend.Attach() client, _ := backend.Attach()
defer backend.Stop() defer backend.Close()
defer client.Close() defer client.Close()
ec := NewClient(client) ec := NewClient(client)
...@@ -339,7 +340,7 @@ func TestTransactionInBlockInterrupted(t *testing.T) { ...@@ -339,7 +340,7 @@ func TestTransactionInBlockInterrupted(t *testing.T) {
func TestChainID(t *testing.T) { func TestChainID(t *testing.T) {
backend, _ := newTestBackend(t) backend, _ := newTestBackend(t)
client, _ := backend.Attach() client, _ := backend.Attach()
defer backend.Stop() defer backend.Close()
defer client.Close() defer client.Close()
ec := NewClient(client) ec := NewClient(client)
......
This diff is collapsed.
...@@ -17,12 +17,118 @@ ...@@ -17,12 +17,118 @@
package graphql package graphql
import ( import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/node"
"github.com/stretchr/testify/assert"
) )
func TestBuildSchema(t *testing.T) { func TestBuildSchema(t *testing.T) {
stack, err := node.New(&node.DefaultConfig)
if err != nil {
t.Fatalf("could not create new node: %v", err)
}
// Make sure the schema can be parsed and matched up to the object model. // Make sure the schema can be parsed and matched up to the object model.
if _, err := newHandler(nil); err != nil { if err := newHandler(stack, nil, []string{}, []string{}); err != nil {
t.Errorf("Could not construct GraphQL handler: %v", err) t.Errorf("Could not construct GraphQL handler: %v", err)
} }
} }
// Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint
func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) {
stack := createNode(t, true)
defer stack.Close()
// start node
if err := stack.Start(); err != nil {
t.Fatalf("could not start node: %v", err)
}
// create http request
body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}")
gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body)
if err != nil {
t.Error("could not issue new http request ", err)
}
gqlReq.Header.Set("Content-Type", "application/json")
// read from response
resp := doHTTPRequest(t, gqlReq)
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("could not read from response body: %v", err)
}
expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}"
assert.Equal(t, expected, string(bodyBytes))
}
// Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint
func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
stack := createNode(t, false)
defer stack.Close()
if err := stack.Start(); err != nil {
t.Fatalf("could not start node: %v", err)
}
// create http request
body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}")
gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body)
if err != nil {
t.Error("could not issue new http request ", err)
}
gqlReq.Header.Set("Content-Type", "application/json")
// read from response
resp := doHTTPRequest(t, gqlReq)
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("could not read from response body: %v", err)
}
// make sure the request is not handled successfully
assert.Equal(t, 404, resp.StatusCode)
assert.Equal(t, "404 page not found\n", string(bodyBytes))
}
func createNode(t *testing.T, gqlEnabled bool) *node.Node {
stack, err := node.New(&node.Config{
HTTPHost: "127.0.0.1",
HTTPPort: 9393,
WSHost: "127.0.0.1",
WSPort: 9393,
})
if err != nil {
t.Fatalf("could not create node: %v", err)
}
if !gqlEnabled {
return stack
}
createGQLService(t, stack, "127.0.0.1:9393")
return stack
}
func createGQLService(t *testing.T, stack *node.Node, endpoint string) {
// create backend
ethBackend, err := eth.New(stack, &eth.DefaultConfig)
if err != nil {
t.Fatalf("could not create eth backend: %v", err)
}
// create gql service
err = New(stack, ethBackend.APIBackend, []string{}, []string{})
if err != nil {
t.Fatalf("could not create graphql service: %v", err)
}
}
func doHTTPRequest(t *testing.T, req *http.Request) *http.Response {
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
t.Fatal("could not issue a GET request to the given endpoint", err)
}
return resp
}
...@@ -17,99 +17,36 @@ ...@@ -17,99 +17,36 @@
package graphql package graphql
import ( import (
"fmt"
"net"
"net/http"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay" "github.com/graph-gophers/graphql-go/relay"
) )
// Service encapsulates a GraphQL service.
type Service struct {
endpoint string // The host:port endpoint for this service.
cors []string // Allowed CORS domains
vhosts []string // Recognised vhosts
timeouts rpc.HTTPTimeouts // Timeout settings for HTTP requests.
backend ethapi.Backend // The backend that queries will operate on.
handler http.Handler // The `http.Handler` used to answer queries.
listener net.Listener // The listening socket.
}
// New constructs a new GraphQL service instance. // New constructs a new GraphQL service instance.
func New(backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) (*Service, error) { func New(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error {
return &Service{ if backend == nil {
endpoint: endpoint, panic("missing backend")
cors: cors,
vhosts: vhosts,
timeouts: timeouts,
backend: backend,
}, nil
}
// Protocols returns the list of protocols exported by this service.
func (s *Service) Protocols() []p2p.Protocol { return nil }
// APIs returns the list of APIs exported by this service.
func (s *Service) APIs() []rpc.API { return nil }
// Start is called after all services have been constructed and the networking
// layer was also initialized to spawn any goroutines required by the service.
func (s *Service) Start(server *p2p.Server) error {
var err error
s.handler, err = newHandler(s.backend)
if err != nil {
return err
}
if s.listener, err = net.Listen("tcp", s.endpoint); err != nil {
return err
}
// create handler stack and wrap the graphql handler
handler := node.NewHTTPHandlerStack(s.handler, s.cors, s.vhosts)
// make sure timeout values are meaningful
node.CheckTimeouts(&s.timeouts)
// create http server
httpSrv := &http.Server{
Handler: handler,
ReadTimeout: s.timeouts.ReadTimeout,
WriteTimeout: s.timeouts.WriteTimeout,
IdleTimeout: s.timeouts.IdleTimeout,
} }
go httpSrv.Serve(s.listener) // check if http server with given endpoint exists and enable graphQL on it
log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%s", s.endpoint)) return newHandler(stack, backend, cors, vhosts)
return nil
} }
// newHandler returns a new `http.Handler` that will answer GraphQL queries. // newHandler returns a new `http.Handler` that will answer GraphQL queries.
// It additionally exports an interactive query browser on the / endpoint. // It additionally exports an interactive query browser on the / endpoint.
func newHandler(backend ethapi.Backend) (http.Handler, error) { func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error {
q := Resolver{backend} q := Resolver{backend}
s, err := graphql.ParseSchema(schema, &q) s, err := graphql.ParseSchema(schema, &q)
if err != nil { if err != nil {
return nil, err return err
} }
h := &relay.Handler{Schema: s} h := &relay.Handler{Schema: s}
handler := node.NewHTTPHandlerStack(h, cors, vhosts)
mux := http.NewServeMux() stack.RegisterHandler("GraphQL UI", "/graphql/ui", GraphiQL{})
mux.Handle("/", GraphiQL{}) stack.RegisterHandler("GraphQL", "/graphql", handler)
mux.Handle("/graphql", h) stack.RegisterHandler("GraphQL", "/graphql/", handler)
mux.Handle("/graphql/", h)
return mux, nil
}
// Stop terminates all goroutines belonging to the service, blocking until they
// are all terminated.
func (s *Service) Stop() error {
if s.listener != nil {
s.listener.Close()
s.listener = nil
log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%s", s.endpoint))
}
return nil return nil
} }
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
...@@ -45,14 +46,16 @@ type Backend interface { ...@@ -45,14 +46,16 @@ type Backend interface {
ChainDb() ethdb.Database ChainDb() ethdb.Database
AccountManager() *accounts.Manager AccountManager() *accounts.Manager
ExtRPCEnabled() bool ExtRPCEnabled() bool
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
// Blockchain API // Blockchain API
SetHead(number uint64) SetHead(number uint64)
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error)
CurrentHeader() *types.Header
CurrentBlock() *types.Block
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)
...@@ -84,7 +87,7 @@ type Backend interface { ...@@ -84,7 +87,7 @@ type Backend interface {
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
CurrentBlock() *types.Block Engine() consensus.Engine
} }
func GetAPIs(apiBackend Backend) []rpc.API { func GetAPIs(apiBackend Backend) []rpc.API {
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
...@@ -282,3 +283,11 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma ...@@ -282,3 +283,11 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
} }
} }
func (b *LesApiBackend) Engine() consensus.Engine {
return b.eth.engine
}
func (b *LesApiBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader()
}
...@@ -55,7 +55,7 @@ func TestMain(m *testing.M) { ...@@ -55,7 +55,7 @@ func TestMain(m *testing.M) {
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
// register the Delivery service which will run as a devp2p // register the Delivery service which will run as a devp2p
// protocol when using the exec adapter // protocol when using the exec adapter
adapters.RegisterServices(services) adapters.RegisterLifecycles(services)
os.Exit(m.Run()) os.Exit(m.Run())
} }
...@@ -392,7 +392,7 @@ func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (min ...@@ -392,7 +392,7 @@ func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (min
return return
} }
var services = adapters.Services{ var services = adapters.LifecycleConstructors{
"lesclient": newLesClientService, "lesclient": newLesClientService,
"lesserver": newLesServerService, "lesserver": newLesServerService,
} }
...@@ -414,7 +414,7 @@ func NewNetwork() (*simulations.Network, func(), error) { ...@@ -414,7 +414,7 @@ func NewNetwork() (*simulations.Network, func(), error) {
return net, teardown, nil return net, teardown, nil
} }
func NewAdapter(adapterType string, services adapters.Services) (adapter adapters.NodeAdapter, teardown func(), err error) { func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (adapter adapters.NodeAdapter, teardown func(), err error) {
teardown = func() {} teardown = func() {}
switch adapterType { switch adapterType {
case "sim": case "sim":
...@@ -454,7 +454,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] ...@@ -454,7 +454,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []
for i := range clients { for i := range clients {
clientconf := adapters.RandomNodeConfig() clientconf := adapters.RandomNodeConfig()
clientconf.Services = []string{"lesclient"} clientconf.Lifecycles = []string{"lesclient"}
if len(clientDir) == clientCount { if len(clientDir) == clientCount {
clientconf.DataDir = clientDir[i] clientconf.DataDir = clientDir[i]
} }
...@@ -467,7 +467,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] ...@@ -467,7 +467,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []
for i := range servers { for i := range servers {
serverconf := adapters.RandomNodeConfig() serverconf := adapters.RandomNodeConfig()
serverconf.Services = []string{"lesserver"} serverconf.Lifecycles = []string{"lesserver"}
if len(serverDir) == serverCount { if len(serverDir) == serverCount {
serverconf.DataDir = serverDir[i] serverconf.DataDir = serverDir[i]
} }
...@@ -492,26 +492,25 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] ...@@ -492,26 +492,25 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []
return test(ctx, net, servers, clients) return test(ctx, net, servers, clients)
} }
func newLesClientService(ctx *adapters.ServiceContext) (node.Service, error) { func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
config := eth.DefaultConfig config := eth.DefaultConfig
config.SyncMode = downloader.LightSync config.SyncMode = downloader.LightSync
config.Ethash.PowMode = ethash.ModeFake config.Ethash.PowMode = ethash.ModeFake
return New(ctx.NodeContext, &config) return New(stack, &config)
} }
func newLesServerService(ctx *adapters.ServiceContext) (node.Service, error) { func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
config := eth.DefaultConfig config := eth.DefaultConfig
config.SyncMode = downloader.FullSync config.SyncMode = downloader.FullSync
config.LightServ = testServerCapacity config.LightServ = testServerCapacity
config.LightPeers = testMaxClients config.LightPeers = testMaxClients
ethereum, err := eth.New(ctx.NodeContext, &config) ethereum, err := eth.New(stack, &config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
server, err := NewLesServer(ethereum, &config) _, err = NewLesServer(stack, ethereum, &config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ethereum.AddLesServer(server)
return ethereum, nil return ethereum, nil
} }
...@@ -51,16 +51,6 @@ type CheckpointOracle struct { ...@@ -51,16 +51,6 @@ type CheckpointOracle struct {
// New creates a checkpoint oracle handler with given configs and callback. // New creates a checkpoint oracle handler with given configs and callback.
func New(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *CheckpointOracle { func New(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *CheckpointOracle {
if config == nil {
log.Info("Checkpoint registrar is not enabled")
return nil
}
if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold {
log.Warn("Invalid checkpoint registrar config")
return nil
}
log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold)
return &CheckpointOracle{ return &CheckpointOracle{
config: config, config: config,
getLocal: getLocal, getLocal: getLocal,
......
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
...@@ -37,7 +36,6 @@ import ( ...@@ -37,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/les/checkpointoracle"
lpc "github.com/ethereum/go-ethereum/les/lespay/client" lpc "github.com/ethereum/go-ethereum/les/lespay/client"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -72,14 +70,17 @@ type LightEthereum struct { ...@@ -72,14 +70,17 @@ type LightEthereum struct {
engine consensus.Engine engine consensus.Engine
accountManager *accounts.Manager accountManager *accounts.Manager
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI
p2pServer *p2p.Server
} }
func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { // New creates an instance of the light client.
chainDb, err := ctx.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) {
chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/")
if err != nil { if err != nil {
return nil, err return nil, err
} }
lespayDb, err := ctx.OpenDatabase("lespay", 0, 0, "eth/db/lespay") lespayDb, err := stack.OpenDatabase("lespay", 0, 0, "eth/db/lespay")
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -100,17 +101,18 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { ...@@ -100,17 +101,18 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
closeCh: make(chan struct{}), closeCh: make(chan struct{}),
}, },
peers: peers, peers: peers,
eventMux: ctx.EventMux, eventMux: stack.EventMux(),
reqDist: newRequestDistributor(peers, &mclock.System{}), reqDist: newRequestDistributor(peers, &mclock.System{}),
accountManager: ctx.AccountManager, accountManager: stack.AccountManager(),
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb), engine: eth.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb),
bloomRequests: make(chan chan *bloombits.Retrieval), bloomRequests: make(chan chan *bloombits.Retrieval),
bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)),
p2pServer: stack.Server(),
} }
peers.subscribe((*vtSubscription)(leth.valueTracker)) peers.subscribe((*vtSubscription)(leth.valueTracker))
dnsdisc, err := leth.setupDiscovery(&ctx.Config.P2P) dnsdisc, err := leth.setupDiscovery(&stack.Config().P2P)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -139,11 +141,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { ...@@ -139,11 +141,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
// Set up checkpoint oracle. // Set up checkpoint oracle.
oracle := config.CheckpointOracle leth.oracle = leth.setupOracle(stack, genesisHash, config)
if oracle == nil {
oracle = params.CheckpointOracles[genesisHash]
}
leth.oracle = checkpointoracle.New(oracle, leth.localCheckpoint)
// Note: AddChildIndexer starts the update process for the child // Note: AddChildIndexer starts the update process for the child
leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer) leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
...@@ -160,7 +158,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { ...@@ -160,7 +158,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
} }
leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil} leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), leth, nil}
gpoParams := config.GPO gpoParams := config.GPO
if gpoParams.Default == nil { if gpoParams.Default == nil {
gpoParams.Default = config.Miner.GasPrice gpoParams.Default = config.Miner.GasPrice
...@@ -172,6 +170,14 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { ...@@ -172,6 +170,14 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction) log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction)
leth.blockchain.DisableCheckFreq() leth.blockchain.DisableCheckFreq()
} }
leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId)
// Register the backend on the node
stack.RegisterAPIs(leth.APIs())
stack.RegisterProtocols(leth.Protocols())
stack.RegisterLifecycle(leth)
return leth, nil return leth, nil
} }
...@@ -265,8 +271,7 @@ func (s *LightEthereum) LesVersion() int { return int(ClientP ...@@ -265,8 +271,7 @@ func (s *LightEthereum) LesVersion() int { return int(ClientP
func (s *LightEthereum) Downloader() *downloader.Downloader { return s.handler.downloader } func (s *LightEthereum) Downloader() *downloader.Downloader { return s.handler.downloader }
func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux } func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux }
// Protocols implements node.Service, returning all the currently configured // Protocols returns all the currently configured network protocols to start.
// network protocols to start.
func (s *LightEthereum) Protocols() []p2p.Protocol { func (s *LightEthereum) Protocols() []p2p.Protocol {
return s.makeProtocols(ClientProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} { return s.makeProtocols(ClientProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
if p := s.peers.peer(id.String()); p != nil { if p := s.peers.peer(id.String()); p != nil {
...@@ -276,9 +281,9 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { ...@@ -276,9 +281,9 @@ func (s *LightEthereum) Protocols() []p2p.Protocol {
}, s.dialCandidates) }, s.dialCandidates)
} }
// Start implements node.Service, starting all internal goroutines needed by the // Start implements node.Lifecycle, starting all internal goroutines needed by the
// light ethereum protocol implementation. // light ethereum protocol implementation.
func (s *LightEthereum) Start(srvr *p2p.Server) error { func (s *LightEthereum) Start() error {
log.Warn("Light client mode is an experimental feature") log.Warn("Light client mode is an experimental feature")
s.serverPool.start() s.serverPool.start()
...@@ -287,11 +292,10 @@ func (s *LightEthereum) Start(srvr *p2p.Server) error { ...@@ -287,11 +292,10 @@ func (s *LightEthereum) Start(srvr *p2p.Server) error {
s.startBloomHandlers(params.BloomBitsBlocksClient) s.startBloomHandlers(params.BloomBitsBlocksClient)
s.handler.start() s.handler.start()
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.config.NetworkId)
return nil return nil
} }
// Stop implements node.Service, terminating all internal goroutines used by the // Stop implements node.Lifecycle, terminating all internal goroutines used by the
// Ethereum protocol. // Ethereum protocol.
func (s *LightEthereum) Stop() error { func (s *LightEthereum) Stop() error {
close(s.closeCh) close(s.closeCh)
...@@ -314,11 +318,3 @@ func (s *LightEthereum) Stop() error { ...@@ -314,11 +318,3 @@ func (s *LightEthereum) Stop() error {
log.Info("Light ethereum stopped") log.Info("Light ethereum stopped")
return nil return nil
} }
// SetClient sets the rpc client and binds the registrar contract.
func (s *LightEthereum) SetContractBackend(backend bind.ContractBackend) {
if s.oracle == nil {
return
}
s.oracle.Start(backend)
}
...@@ -26,9 +26,12 @@ import ( ...@@ -26,9 +26,12 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/les/checkpointoracle"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
...@@ -145,3 +148,26 @@ func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint { ...@@ -145,3 +148,26 @@ func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint {
BloomRoot: light.GetBloomTrieRoot(c.chainDb, index, sectionHead), BloomRoot: light.GetBloomTrieRoot(c.chainDb, index, sectionHead),
} }
} }
// setupOracle sets up the checkpoint oracle contract client.
func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig *eth.Config) *checkpointoracle.CheckpointOracle {
config := ethconfig.CheckpointOracle
if config == nil {
// Try loading default config.
config = params.CheckpointOracles[genesis]
}
if config == nil {
log.Info("Checkpoint registrar is not enabled")
return nil
}
if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold {
log.Warn("Invalid checkpoint registrar config")
return nil
}
oracle := checkpointoracle.New(config, c.localCheckpoint)
rpcClient, _ := node.Attach()
client := ethclient.NewClient(rpcClient)
oracle.Start(client)
log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold)
return oracle
}
...@@ -20,14 +20,12 @@ import ( ...@@ -20,14 +20,12 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/les/checkpointoracle"
"github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/les/flowcontrol"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
...@@ -55,9 +53,11 @@ type LesServer struct { ...@@ -55,9 +53,11 @@ type LesServer struct {
minCapacity, maxCapacity, freeCapacity uint64 minCapacity, maxCapacity, freeCapacity uint64
threadsIdle int // Request serving threads count when system is idle. threadsIdle int // Request serving threads count when system is idle.
threadsBusy int // Request serving threads count when system is busy(block insertion). threadsBusy int // Request serving threads count when system is busy(block insertion).
p2pSrv *p2p.Server
} }
func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) { func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
// Collect les protocol version information supported by local node. // Collect les protocol version information supported by local node.
lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
for i, pv := range AdvertiseProtocolVersions { for i, pv := range AdvertiseProtocolVersions {
...@@ -88,17 +88,15 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) { ...@@ -88,17 +88,15 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100),
threadsBusy: config.LightServ/100 + 1, threadsBusy: config.LightServ/100 + 1,
threadsIdle: threads, threadsIdle: threads,
p2pSrv: node.Server(),
} }
srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced) srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced)
srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config)
srv.freeCapacity = srv.minCapacity srv.freeCapacity = srv.minCapacity
srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config)
// Set up checkpoint oracle. // Initialize the bloom trie indexer.
oracle := config.CheckpointOracle e.BloomIndexer().AddChildIndexer(srv.bloomTrieIndexer)
if oracle == nil {
oracle = params.CheckpointOracles[e.BlockChain().Genesis().Hash()]
}
srv.oracle = checkpointoracle.New(oracle, srv.localCheckpoint)
// Initialize server capacity management fields. // Initialize server capacity management fields.
srv.defParams = flowcontrol.ServerParams{ srv.defParams = flowcontrol.ServerParams{
...@@ -125,6 +123,11 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) { ...@@ -125,6 +123,11 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot) "chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot)
} }
srv.chtIndexer.Start(e.BlockChain()) srv.chtIndexer.Start(e.BlockChain())
node.RegisterProtocols(srv.Protocols())
node.RegisterAPIs(srv.APIs())
node.RegisterLifecycle(srv)
return srv, nil return srv, nil
} }
...@@ -166,14 +169,14 @@ func (s *LesServer) Protocols() []p2p.Protocol { ...@@ -166,14 +169,14 @@ func (s *LesServer) Protocols() []p2p.Protocol {
} }
// Start starts the LES server // Start starts the LES server
func (s *LesServer) Start(srvr *p2p.Server) { func (s *LesServer) Start() error {
s.privateKey = srvr.PrivateKey s.privateKey = s.p2pSrv.PrivateKey
s.handler.start() s.handler.start()
s.wg.Add(1) s.wg.Add(1)
go s.capacityManagement() go s.capacityManagement()
if srvr.DiscV5 != nil { if s.p2pSrv.DiscV5 != nil {
for _, topic := range s.lesTopics { for _, topic := range s.lesTopics {
topic := topic topic := topic
go func() { go func() {
...@@ -181,14 +184,16 @@ func (s *LesServer) Start(srvr *p2p.Server) { ...@@ -181,14 +184,16 @@ func (s *LesServer) Start(srvr *p2p.Server) {
logger.Info("Starting topic registration") logger.Info("Starting topic registration")
defer logger.Info("Terminated topic registration") defer logger.Info("Terminated topic registration")
srvr.DiscV5.RegisterTopic(topic, s.closeCh) s.p2pSrv.DiscV5.RegisterTopic(topic, s.closeCh)
}() }()
} }
} }
return nil
} }
// Stop stops the LES service // Stop stops the LES service
func (s *LesServer) Stop() { func (s *LesServer) Stop() error {
close(s.closeCh) close(s.closeCh)
// Disconnect existing sessions. // Disconnect existing sessions.
...@@ -207,18 +212,8 @@ func (s *LesServer) Stop() { ...@@ -207,18 +212,8 @@ func (s *LesServer) Stop() {
s.chtIndexer.Close() s.chtIndexer.Close()
s.wg.Wait() s.wg.Wait()
log.Info("Les server stopped") log.Info("Les server stopped")
}
func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) {
bloomIndexer.AddChildIndexer(s.bloomTrieIndexer)
}
// SetClient sets the rpc client and starts running checkpoint contract if it is not yet watched. return nil
func (s *LesServer) SetContractBackend(backend bind.ContractBackend) {
if s.oracle == nil {
return
}
s.oracle.Start(backend)
} }
// capacityManagement starts an event handler loop that updates the recharge curve of // capacityManagement starts an event handler loop that updates the recharge curve of
......
...@@ -61,30 +61,31 @@ func main() { ...@@ -61,30 +61,31 @@ func main() {
genesis := makeGenesis(faucets, sealers) genesis := makeGenesis(faucets, sealers)
var ( var (
nodes []*node.Node nodes []*eth.Ethereum
enodes []*enode.Node enodes []*enode.Node
) )
for _, sealer := range sealers { for _, sealer := range sealers {
// Start the node and wait until it's up // Start the node and wait until it's up
node, err := makeSealer(genesis) stack, ethBackend, err := makeSealer(genesis)
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer node.Close() defer stack.Close()
for node.Server().NodeInfo().Ports.Listener == 0 { for stack.Server().NodeInfo().Ports.Listener == 0 {
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
} }
// Connect the node to al the previous ones // Connect the node to all the previous ones
for _, n := range enodes { for _, n := range enodes {
node.Server().AddPeer(n) stack.Server().AddPeer(n)
} }
// Start tracking the node and it's enode // Start tracking the node and its enode
nodes = append(nodes, node) nodes = append(nodes, ethBackend)
enodes = append(enodes, node.Server().Self()) enodes = append(enodes, stack.Server().Self())
// Inject the signer key and start sealing with it // Inject the signer key and start sealing with it
store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
signer, err := store.ImportECDSA(sealer, "") signer, err := store.ImportECDSA(sealer, "")
if err != nil { if err != nil {
panic(err) panic(err)
...@@ -93,15 +94,11 @@ func main() { ...@@ -93,15 +94,11 @@ func main() {
panic(err) panic(err)
} }
} }
// Iterate over all the nodes and start signing with them
time.Sleep(3 * time.Second)
// Iterate over all the nodes and start signing on them
time.Sleep(3 * time.Second)
for _, node := range nodes { for _, node := range nodes {
var ethereum *eth.Ethereum if err := node.StartMining(1); err != nil {
if err := node.Service(&ethereum); err != nil {
panic(err)
}
if err := ethereum.StartMining(1); err != nil {
panic(err) panic(err)
} }
} }
...@@ -110,25 +107,22 @@ func main() { ...@@ -110,25 +107,22 @@ func main() {
// Start injecting transactions from the faucet like crazy // Start injecting transactions from the faucet like crazy
nonces := make([]uint64, len(faucets)) nonces := make([]uint64, len(faucets))
for { for {
// Pick a random signer node
index := rand.Intn(len(faucets)) index := rand.Intn(len(faucets))
backend := nodes[index%len(nodes)]
// Fetch the accessor for the relevant signer
var ethereum *eth.Ethereum
if err := nodes[index%len(nodes)].Service(&ethereum); err != nil {
panic(err)
}
// Create a self transaction and inject into the pool // Create a self transaction and inject into the pool
tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index]) tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index])
if err != nil { if err != nil {
panic(err) panic(err)
} }
if err := ethereum.TxPool().AddLocal(tx); err != nil { if err := backend.TxPool().AddLocal(tx); err != nil {
panic(err) panic(err)
} }
nonces[index]++ nonces[index]++
// Wait if we're too saturated // Wait if we're too saturated
if pend, _ := ethereum.TxPool().Stats(); pend > 2048 { if pend, _ := backend.TxPool().Stats(); pend > 2048 {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
} }
...@@ -171,7 +165,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core ...@@ -171,7 +165,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core
return genesis return genesis
} }
func makeSealer(genesis *core.Genesis) (*node.Node, error) { func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) {
// Define the basic configurations for the Ethereum node // Define the basic configurations for the Ethereum node
datadir, _ := ioutil.TempDir("", "") datadir, _ := ioutil.TempDir("", "")
...@@ -189,27 +183,28 @@ func makeSealer(genesis *core.Genesis) (*node.Node, error) { ...@@ -189,27 +183,28 @@ func makeSealer(genesis *core.Genesis) (*node.Node, error) {
// Start the node and configure a full Ethereum node on it // Start the node and configure a full Ethereum node on it
stack, err := node.New(config) stack, err := node.New(config)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { // Create and register the backend
return eth.New(ctx, &eth.Config{ ethBackend, err := eth.New(stack, &eth.Config{
Genesis: genesis, Genesis: genesis,
NetworkId: genesis.Config.ChainID.Uint64(), NetworkId: genesis.Config.ChainID.Uint64(),
SyncMode: downloader.FullSync, SyncMode: downloader.FullSync,
DatabaseCache: 256, DatabaseCache: 256,
DatabaseHandles: 256, DatabaseHandles: 256,
TxPool: core.DefaultTxPoolConfig, TxPool: core.DefaultTxPoolConfig,
GPO: eth.DefaultConfig.GPO, GPO: eth.DefaultConfig.GPO,
Miner: miner.Config{ Miner: miner.Config{
GasFloor: genesis.GasLimit * 9 / 10, GasFloor: genesis.GasLimit * 9 / 10,
GasCeil: genesis.GasLimit * 11 / 10, GasCeil: genesis.GasLimit * 11 / 10,
GasPrice: big.NewInt(1), GasPrice: big.NewInt(1),
Recommit: time.Second, Recommit: time.Second,
}, },
}) })
}); err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Start the node and return if successful
return stack, stack.Start() err = stack.Start()
return stack, ethBackend, err
} }
...@@ -61,43 +61,39 @@ func main() { ...@@ -61,43 +61,39 @@ func main() {
genesis := makeGenesis(faucets) genesis := makeGenesis(faucets)
var ( var (
nodes []*node.Node nodes []*eth.Ethereum
enodes []*enode.Node enodes []*enode.Node
) )
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
// Start the node and wait until it's up // Start the node and wait until it's up
node, err := makeMiner(genesis) stack, ethBackend, err := makeMiner(genesis)
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer node.Close() defer stack.Close()
for node.Server().NodeInfo().Ports.Listener == 0 { for stack.Server().NodeInfo().Ports.Listener == 0 {
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
} }
// Connect the node to al the previous ones // Connect the node to all the previous ones
for _, n := range enodes { for _, n := range enodes {
node.Server().AddPeer(n) stack.Server().AddPeer(n)
} }
// Start tracking the node and it's enode // Start tracking the node and its enode
nodes = append(nodes, node) nodes = append(nodes, ethBackend)
enodes = append(enodes, node.Server().Self()) enodes = append(enodes, stack.Server().Self())
// Inject the signer key and start sealing with it // Inject the signer key and start sealing with it
store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
if _, err := store.NewAccount(""); err != nil { if _, err := store.NewAccount(""); err != nil {
panic(err) panic(err)
} }
} }
// Iterate over all the nodes and start signing with them
time.Sleep(3 * time.Second)
// Iterate over all the nodes and start mining
time.Sleep(3 * time.Second)
for _, node := range nodes { for _, node := range nodes {
var ethereum *eth.Ethereum if err := node.StartMining(1); err != nil {
if err := node.Service(&ethereum); err != nil {
panic(err)
}
if err := ethereum.StartMining(1); err != nil {
panic(err) panic(err)
} }
} }
...@@ -106,25 +102,22 @@ func main() { ...@@ -106,25 +102,22 @@ func main() {
// Start injecting transactions from the faucets like crazy // Start injecting transactions from the faucets like crazy
nonces := make([]uint64, len(faucets)) nonces := make([]uint64, len(faucets))
for { for {
// Pick a random mining node
index := rand.Intn(len(faucets)) index := rand.Intn(len(faucets))
backend := nodes[index%len(nodes)]
// Fetch the accessor for the relevant signer
var ethereum *eth.Ethereum
if err := nodes[index%len(nodes)].Service(&ethereum); err != nil {
panic(err)
}
// Create a self transaction and inject into the pool // Create a self transaction and inject into the pool
tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index]) tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index])
if err != nil { if err != nil {
panic(err) panic(err)
} }
if err := ethereum.TxPool().AddLocal(tx); err != nil { if err := backend.TxPool().AddLocal(tx); err != nil {
panic(err) panic(err)
} }
nonces[index]++ nonces[index]++
// Wait if we're too saturated // Wait if we're too saturated
if pend, _ := ethereum.TxPool().Stats(); pend > 2048 { if pend, _ := backend.TxPool().Stats(); pend > 2048 {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
} }
...@@ -149,7 +142,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { ...@@ -149,7 +142,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis {
return genesis return genesis
} }
func makeMiner(genesis *core.Genesis) (*node.Node, error) { func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) {
// Define the basic configurations for the Ethereum node // Define the basic configurations for the Ethereum node
datadir, _ := ioutil.TempDir("", "") datadir, _ := ioutil.TempDir("", "")
...@@ -165,31 +158,31 @@ func makeMiner(genesis *core.Genesis) (*node.Node, error) { ...@@ -165,31 +158,31 @@ func makeMiner(genesis *core.Genesis) (*node.Node, error) {
NoUSB: true, NoUSB: true,
UseLightweightKDF: true, UseLightweightKDF: true,
} }
// Start the node and configure a full Ethereum node on it // Create the node and configure a full Ethereum node on it
stack, err := node.New(config) stack, err := node.New(config)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { ethBackend, err := eth.New(stack, &eth.Config{
return eth.New(ctx, &eth.Config{ Genesis: genesis,
Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(),
NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync,
SyncMode: downloader.FullSync, DatabaseCache: 256,
DatabaseCache: 256, DatabaseHandles: 256,
DatabaseHandles: 256, TxPool: core.DefaultTxPoolConfig,
TxPool: core.DefaultTxPoolConfig, GPO: eth.DefaultConfig.GPO,
GPO: eth.DefaultConfig.GPO, Ethash: eth.DefaultConfig.Ethash,
Ethash: eth.DefaultConfig.Ethash, Miner: miner.Config{
Miner: miner.Config{ GasFloor: genesis.GasLimit * 9 / 10,
GasFloor: genesis.GasLimit * 9 / 10, GasCeil: genesis.GasLimit * 11 / 10,
GasCeil: genesis.GasLimit * 11 / 10, GasPrice: big.NewInt(1),
GasPrice: big.NewInt(1), Recommit: time.Second,
Recommit: time.Second, },
}, })
}) if err != nil {
}); err != nil { return nil, nil, err
return nil, err
} }
// Start the node and return if successful
return stack, stack.Start() err = stack.Start()
return stack, ethBackend, err
} }
...@@ -175,49 +175,44 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ...@@ -175,49 +175,44 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
ethConf.SyncMode = downloader.LightSync ethConf.SyncMode = downloader.LightSync
ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.NetworkId = uint64(config.EthereumNetworkID)
ethConf.DatabaseCache = config.EthereumDatabaseCache ethConf.DatabaseCache = config.EthereumDatabaseCache
if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { lesBackend, err := les.New(rawStack, &ethConf)
return les.New(ctx, &ethConf) if err != nil {
}); err != nil {
return nil, fmt.Errorf("ethereum init: %v", err) return nil, fmt.Errorf("ethereum init: %v", err)
} }
// If netstats reporting is requested, do it // If netstats reporting is requested, do it
if config.EthereumNetStats != "" { if config.EthereumNetStats != "" {
if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { if err := ethstats.New(rawStack, lesBackend.ApiBackend, lesBackend.Engine(), config.EthereumNetStats); err != nil {
var lesServ *les.LightEthereum
ctx.Service(&lesServ)
return ethstats.New(config.EthereumNetStats, nil, lesServ)
}); err != nil {
return nil, fmt.Errorf("netstats init: %v", err) return nil, fmt.Errorf("netstats init: %v", err)
} }
} }
} }
// Register the Whisper protocol if requested // Register the Whisper protocol if requested
if config.WhisperEnabled { if config.WhisperEnabled {
if err := rawStack.Register(func(*node.ServiceContext) (node.Service, error) { if _, err := whisper.New(rawStack, &whisper.DefaultConfig); err != nil {
return whisper.New(&whisper.DefaultConfig), nil
}); err != nil {
return nil, fmt.Errorf("whisper init: %v", err) return nil, fmt.Errorf("whisper init: %v", err)
} }
} }
return &Node{rawStack}, nil return &Node{rawStack}, nil
} }
// Close terminates a running node along with all it's services, tearing internal // Close terminates a running node along with all it's services, tearing internal state
// state doen too. It's not possible to restart a closed node. // down. It is not possible to restart a closed node.
func (n *Node) Close() error { func (n *Node) Close() error {
return n.node.Close() return n.node.Close()
} }
// Start creates a live P2P node and starts running it. // Start creates a live P2P node and starts running it.
func (n *Node) Start() error { func (n *Node) Start() error {
// TODO: recreate the node so it can be started multiple times
return n.node.Start() return n.node.Start()
} }
// Stop terminates a running node along with all it's services. If the node was // Stop terminates a running node along with all its services. If the node was not started,
// not started, an error is returned. // an error is returned. It is not possible to restart a stopped node.
//
// Deprecated: use Close()
func (n *Node) Stop() error { func (n *Node) Stop() error {
return n.node.Stop() return n.node.Close()
} }
// GetEthereumClient retrieves a client to access the Ethereum subsystem. // GetEthereumClient retrieves a client to access the Ethereum subsystem.
......
This diff is collapsed.
// Copyright 2020 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"
"net"
"net/http"
"net/url"
"strings"
"testing"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/assert"
)
// This test uses the admin_startRPC and admin_startWS APIs,
// checking whether the HTTP server is started correctly.
func TestStartRPC(t *testing.T) {
type test struct {
name string
cfg Config
fn func(*testing.T, *Node, *privateAdminAPI)
// Checks. These run after the node is configured and all API calls have been made.
wantReachable bool // whether the HTTP server should be reachable at all
wantHandlers bool // whether RegisterHandler handlers should be accessible
wantRPC bool // whether JSON-RPC/HTTP should be accessible
wantWS bool // whether JSON-RPC/WS should be accessible
}
tests := []test{
{
name: "all off",
cfg: Config{},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
},
wantReachable: false,
wantHandlers: false,
wantRPC: false,
wantWS: false,
},
{
name: "rpc enabled through config",
cfg: Config{HTTPHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
},
wantReachable: true,
wantHandlers: true,
wantRPC: true,
wantWS: false,
},
{
name: "rpc enabled through API",
cfg: Config{},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
wantHandlers: true,
wantRPC: true,
wantWS: false,
},
{
name: "rpc start again after failure",
cfg: Config{},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
// Listen on a random port.
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal("can't listen:", err)
}
defer listener.Close()
port := listener.Addr().(*net.TCPAddr).Port
// Now try to start RPC on that port. This should fail.
_, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil)
if err == nil {
t.Fatal("StartRPC should have failed on port", port)
}
// Try again after unblocking the port. It should work this time.
listener.Close()
_, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
wantHandlers: true,
wantRPC: true,
wantWS: false,
},
{
name: "rpc stopped through API",
cfg: Config{HTTPHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StopRPC()
assert.NoError(t, err)
},
wantReachable: false,
wantHandlers: false,
wantRPC: false,
wantWS: false,
},
{
name: "rpc stopped twice",
cfg: Config{HTTPHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StopRPC()
assert.NoError(t, err)
_, err = api.StopRPC()
assert.NoError(t, err)
},
wantReachable: false,
wantHandlers: false,
wantRPC: false,
wantWS: false,
},
{
name: "ws enabled through config",
cfg: Config{WSHost: "127.0.0.1"},
wantReachable: true,
wantHandlers: false,
wantRPC: false,
wantWS: true,
},
{
name: "ws enabled through API",
cfg: Config{},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
wantHandlers: false,
wantRPC: false,
wantWS: true,
},
{
name: "ws stopped through API",
cfg: Config{WSHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StopWS()
assert.NoError(t, err)
},
wantReachable: false,
wantHandlers: false,
wantRPC: false,
wantWS: false,
},
{
name: "ws stopped twice",
cfg: Config{WSHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StopWS()
assert.NoError(t, err)
_, err = api.StopWS()
assert.NoError(t, err)
},
wantReachable: false,
wantHandlers: false,
wantRPC: false,
wantWS: false,
},
{
name: "ws enabled after RPC",
cfg: Config{HTTPHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
wsport := n.http.port
_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
wantHandlers: true,
wantRPC: true,
wantWS: true,
},
{
name: "ws enabled after RPC then stopped",
cfg: Config{HTTPHost: "127.0.0.1"},
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
wsport := n.http.port
_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
assert.NoError(t, err)
_, err = api.StopWS()
assert.NoError(t, err)
},
wantReachable: true,
wantHandlers: true,
wantRPC: true,
wantWS: false,
},
{
name: "rpc stopped with ws enabled",
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil)
assert.NoError(t, err)
wsport := n.http.port
_, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
assert.NoError(t, err)
_, err = api.StopRPC()
assert.NoError(t, err)
},
wantReachable: false,
wantHandlers: false,
wantRPC: false,
wantWS: false,
},
{
name: "rpc enabled after ws",
fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
assert.NoError(t, err)
wsport := n.http.port
_, err = api.StartRPC(sp("127.0.0.1"), ip(wsport), nil, nil, nil)
assert.NoError(t, err)
},
wantReachable: true,
wantHandlers: true,
wantRPC: true,
wantWS: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Apply some sane defaults.
config := test.cfg
// config.Logger = testlog.Logger(t, log.LvlDebug)
config.NoUSB = true
config.P2P.NoDiscovery = true
// Create Node.
stack, err := New(&config)
if err != nil {
t.Fatal("can't create node:", err)
}
defer stack.Close()
// Register the test handler.
stack.RegisterHandler("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
}))
if err := stack.Start(); err != nil {
t.Fatal("can't start node:", err)
}
// Run the API call hook.
if test.fn != nil {
test.fn(t, stack, &privateAdminAPI{stack})
}
// Check if the HTTP endpoints are available.
baseURL := stack.HTTPEndpoint()
reachable := checkReachable(baseURL)
handlersAvailable := checkBodyOK(baseURL + "/test")
rpcAvailable := checkRPC(baseURL)
wsAvailable := checkRPC(strings.Replace(baseURL, "http://", "ws://", 1))
if reachable != test.wantReachable {
t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable))
}
if handlersAvailable != test.wantHandlers {
t.Errorf("RegisterHandler handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers))
}
if rpcAvailable != test.wantRPC {
t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC))
}
if wsAvailable != test.wantWS {
t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS))
}
})
}
}
// checkReachable checks if the TCP endpoint in rawurl is open.
func checkReachable(rawurl string) bool {
u, err := url.Parse(rawurl)
if err != nil {
panic(err)
}
conn, err := net.Dial("tcp", u.Host)
if err != nil {
return false
}
conn.Close()
return true
}
// checkBodyOK checks whether the given HTTP URL responds with 200 OK and body "OK".
func checkBodyOK(url string) bool {
resp, err := http.Get(url)
if err != nil {
return false
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return false
}
buf := make([]byte, 2)
if _, err = io.ReadFull(resp.Body, buf); err != nil {
return false
}
return bytes.Equal(buf, []byte("OK"))
}
// checkRPC checks whether JSON-RPC works against the given URL.
func checkRPC(url string) bool {
c, err := rpc.Dial(url)
if err != nil {
return false
}
defer c.Close()
_, err = c.SupportedModules()
return err == nil
}
// string/int pointer helpers.
func sp(s string) *string { return &s }
func ip(i int) *int { return &i }
func not(ok bool) string {
if ok {
return ""
}
return "not "
}
...@@ -162,15 +162,6 @@ type Config struct { ...@@ -162,15 +162,6 @@ type Config struct {
// private APIs to untrusted users is a major security risk. // private APIs to untrusted users is a major security risk.
WSExposeAll bool `toml:",omitempty"` WSExposeAll bool `toml:",omitempty"`
// GraphQLHost is the host interface on which to start the GraphQL server. If this
// field is empty, no GraphQL API endpoint will be started.
GraphQLHost string
// GraphQLPort is the TCP port number on which to start the GraphQL server. The
// default zero value is/ valid and will pick a port number randomly (useful
// for ephemeral nodes).
GraphQLPort int `toml:",omitempty"`
// GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting // GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting
// clients. Please be aware that CORS is a browser enforced security, it's fully // clients. Please be aware that CORS is a browser enforced security, it's fully
// useless for custom HTTP clients. // useless for custom HTTP clients.
...@@ -247,15 +238,6 @@ func (c *Config) HTTPEndpoint() string { ...@@ -247,15 +238,6 @@ func (c *Config) HTTPEndpoint() string {
return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort)
} }
// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface
// and port parameters.
func (c *Config) GraphQLEndpoint() string {
if c.GraphQLHost == "" {
return ""
}
return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort)
}
// DefaultHTTPEndpoint returns the HTTP endpoint used by default. // DefaultHTTPEndpoint returns the HTTP endpoint used by default.
func DefaultHTTPEndpoint() string { func DefaultHTTPEndpoint() string {
config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort}
...@@ -280,7 +262,7 @@ func DefaultWSEndpoint() string { ...@@ -280,7 +262,7 @@ func DefaultWSEndpoint() string {
// ExtRPCEnabled returns the indicator whether node enables the external // ExtRPCEnabled returns the indicator whether node enables the external
// RPC(http, ws or graphql). // RPC(http, ws or graphql).
func (c *Config) ExtRPCEnabled() bool { func (c *Config) ExtRPCEnabled() bool {
return c.HTTPHost != "" || c.WSHost != "" || c.GraphQLHost != "" return c.HTTPHost != "" || c.WSHost != ""
} }
// NodeName returns the devp2p node identifier. // NodeName returns the devp2p node identifier.
......
...@@ -45,7 +45,6 @@ var DefaultConfig = Config{ ...@@ -45,7 +45,6 @@ var DefaultConfig = Config{
HTTPTimeouts: rpc.DefaultHTTPTimeouts, HTTPTimeouts: rpc.DefaultHTTPTimeouts,
WSPort: DefaultWSPort, WSPort: DefaultWSPort,
WSModules: []string{"net", "web3"}, WSModules: []string{"net", "web3"},
GraphQLPort: DefaultGraphQLPort,
GraphQLVirtualHosts: []string{"localhost"}, GraphQLVirtualHosts: []string{"localhost"},
P2P: p2p.Config{ P2P: p2p.Config{
ListenAddr: ":30303", ListenAddr: ":30303",
......
...@@ -22,6 +22,43 @@ resources to provide RPC APIs. Services can also offer devp2p protocols, which a ...@@ -22,6 +22,43 @@ resources to provide RPC APIs. Services can also offer devp2p protocols, which a
up to the devp2p network when the node instance is started. up to the devp2p network when the node instance is started.
Node Lifecycle
The Node object has a lifecycle consisting of three basic states, INITIALIZING, RUNNING
and CLOSED.
●───────┐
New()
INITIALIZING ────Start()─┐
│ │
│ ▼
Close() RUNNING
│ │
▼ │
CLOSED ◀──────Close()─┘
Creating a Node allocates basic resources such as the data directory and returns the node
in its INITIALIZING state. Lifecycle objects, RPC APIs and peer-to-peer networking
protocols can be registered in this state. Basic operations such as opening a key-value
database are permitted while initializing.
Once everything is registered, the node can be started, which moves it into the RUNNING
state. Starting the node starts all registered Lifecycle objects and enables RPC and
peer-to-peer networking. Note that no additional Lifecycles, APIs or p2p protocols can be
registered while the node is running.
Closing the node releases all held resources. The actions performed by Close depend on the
state it was in. When closing a node in INITIALIZING state, resources related to the data
directory are released. If the node was RUNNING, closing it also stops all Lifecycle
objects and shuts down RPC and peer-to-peer networking.
You must always call Close on Node, even if the node was not started.
Resources Managed By Node Resources Managed By Node
All file-system resources used by a node instance are located in a directory called the All file-system resources used by a node instance are located in a directory called the
......
...@@ -48,21 +48,6 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http. ...@@ -48,21 +48,6 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.
return httpSrv, listener.Addr(), err return httpSrv, listener.Addr(), err
} }
// startWSEndpoint starts a websocket endpoint.
func startWSEndpoint(endpoint string, handler http.Handler) (*http.Server, net.Addr, error) {
// start the HTTP listener
var (
listener net.Listener
err error
)
if listener, err = net.Listen("tcp", endpoint); err != nil {
return nil, nil, err
}
wsSrv := &http.Server{Handler: handler}
go wsSrv.Serve(listener)
return wsSrv, listener.Addr(), err
}
// checkModuleAvailability checks that all names given in modules are actually // checkModuleAvailability checks that all names given in modules are actually
// available API services. It assumes that the MetadataApi module ("rpc") is always available; // available API services. It assumes that the MetadataApi module ("rpc") is always available;
// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints. // the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints.
......
...@@ -39,17 +39,6 @@ func convertFileLockError(err error) error { ...@@ -39,17 +39,6 @@ func convertFileLockError(err error) error {
return err return err
} }
// 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 // StopError is returned if a Node fails to stop either any of its registered
// services or itself. // services or itself.
type StopError struct { type StopError struct {
......
// Copyright 2018 The go-ethereum Authors // Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
// The go-ethereum library is free software: you can redistribute it and/or modify // The go-ethereum library is free software: you can redistribute it and/or modify
...@@ -14,54 +14,18 @@ ...@@ -14,54 +14,18 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package testing package node
import ( // Lifecycle encompasses the behavior of services that can be started and stopped
"fmt" // on the node. Lifecycle management is delegated to the node, but it is the
"sync" // responsibility of the service-specific package to configure and register the
// service on the node using the `RegisterLifecycle` method.
type Lifecycle interface {
// 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() error
"github.com/ethereum/go-ethereum/log" // Stop terminates all goroutines belonging to the service, blocking until they
"github.com/ethereum/go-ethereum/p2p/enode" // are all terminated.
) Stop() error
type TestPeer interface {
ID() enode.ID
Drop()
}
// TestPeerPool is an example peerPool to demonstrate registration of peer connections
type TestPeerPool struct {
lock sync.Mutex
peers map[enode.ID]TestPeer
}
func NewTestPeerPool() *TestPeerPool {
return &TestPeerPool{peers: make(map[enode.ID]TestPeer)}
}
func (p *TestPeerPool) Add(peer TestPeer) {
p.lock.Lock()
defer p.lock.Unlock()
log.Trace(fmt.Sprintf("pp add peer %v", peer.ID()))
p.peers[peer.ID()] = peer
}
func (p *TestPeerPool) Remove(peer TestPeer) {
p.lock.Lock()
defer p.lock.Unlock()
delete(p.peers, peer.ID())
}
func (p *TestPeerPool) Has(id enode.ID) bool {
p.lock.Lock()
defer p.lock.Unlock()
_, ok := p.peers[id]
return ok
}
func (p *TestPeerPool) Get(id enode.ID) TestPeer {
p.lock.Lock()
defer p.lock.Unlock()
return p.peers[id]
} }
This diff is collapsed.
...@@ -21,26 +21,20 @@ import ( ...@@ -21,26 +21,20 @@ import (
"log" "log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
) )
// SampleService is a trivial network service that can be attached to a node for // SampleLifecycle is a trivial network service that can be attached to a node for
// life cycle management. // life cycle management.
// //
// The following methods are needed to implement a node.Service: // The following methods are needed to implement a node.Lifecycle:
// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on
// - APIs() []rpc.API - api methods the service wants to expose on rpc channels
// - Start() error - method invoked when the node is ready to start the service // - Start() error - method invoked when the node is ready to start the service
// - Stop() error - method invoked when the node terminates the service // - Stop() error - method invoked when the node terminates the service
type SampleService struct{} type SampleLifecycle struct{}
func (s *SampleService) Protocols() []p2p.Protocol { return nil } func (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil }
func (s *SampleService) APIs() []rpc.API { return nil } func (s *SampleLifecycle) Stop() error { fmt.Println("Service stopping..."); 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 ExampleService() { func ExampleLifecycle() {
// Create a network node to run protocols with the default values. // Create a network node to run protocols with the default values.
stack, err := node.New(&node.Config{}) stack, err := node.New(&node.Config{})
if err != nil { if err != nil {
...@@ -48,29 +42,18 @@ func ExampleService() { ...@@ -48,29 +42,18 @@ func ExampleService() {
} }
defer stack.Close() defer stack.Close()
// Create and register a simple network service. This is done through the definition // Create and register a simple network Lifecycle.
// of a node.ServiceConstructor that will instantiate a node.Service. The reason for service := new(SampleLifecycle)
// the factory method approach is to support service restarts without relying on the stack.RegisterLifecycle(service)
// 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 // Boot up the entire protocol stack, do a restart and terminate
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
log.Fatalf("Failed to start the protocol stack: %v", err) log.Fatalf("Failed to start the protocol stack: %v", err)
} }
if err := stack.Restart(); err != nil { if err := stack.Close(); 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) log.Fatalf("Failed to stop the protocol stack: %v", err)
} }
// Output: // Output:
// Service starting... // Service starting...
// Service stopping... // Service stopping...
// Service starting...
// Service stopping...
} }
This diff is collapsed.
This diff is collapsed.
// Copyright 2020 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 package node
import ( import (
"bytes"
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
"github.com/ethereum/go-ethereum/internal/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { // TestCorsHandler makes sure CORS are properly handled on the http server.
srv := rpc.NewServer() func TestCorsHandler(t *testing.T) {
srv := createAndStartServer(t, httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, wsConfig{})
defer srv.stop()
resp := testRequest(t, "origin", "test.com", "", srv)
assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin"))
resp2 := testRequest(t, "origin", "bad", "", srv)
assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin"))
}
// TestVhosts makes sure vhosts are properly handled on the http server.
func TestVhosts(t *testing.T) {
srv := createAndStartServer(t, httpConfig{Vhosts: []string{"test"}}, false, wsConfig{})
defer srv.stop()
resp := testRequest(t, "", "", "test", srv)
assert.Equal(t, resp.StatusCode, http.StatusOK)
resp2 := testRequest(t, "", "", "bad", srv)
assert.Equal(t, resp2.StatusCode, http.StatusForbidden)
}
handler := NewWebsocketUpgradeHandler(nil, srv.WebsocketHandler([]string{})) // TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server.
ts := httptest.NewServer(handler) func TestWebsocketOrigins(t *testing.T) {
defer ts.Close() srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: []string{"test"}})
defer srv.stop()
responses := make(chan *http.Response) dialer := websocket.DefaultDialer
go func(responses chan *http.Response) { _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{
client := &http.Client{} "Content-type": []string{"application/json"},
"Sec-WebSocket-Version": []string{"13"},
"Origin": []string{"test"},
})
assert.NoError(t, err)
_, _, err = dialer.Dial("ws://"+srv.listenAddr(), http.Header{
"Content-type": []string{"application/json"},
"Sec-WebSocket-Version": []string{"13"},
"Origin": []string{"bad"},
})
assert.Error(t, err)
}
func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfig) *httpServer {
t.Helper()
srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts)
assert.NoError(t, srv.enableRPC(nil, conf))
if ws {
assert.NoError(t, srv.enableWS(nil, wsConf))
}
assert.NoError(t, srv.setListenAddr("localhost", 0))
assert.NoError(t, srv.start())
return srv
}
req, _ := http.NewRequest(http.MethodGet, ts.URL, nil) func testRequest(t *testing.T, key, value, host string, srv *httpServer) *http.Response {
req.Header.Set("Connection", "upgrade") t.Helper()
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Sec-WebSocket-Version", "13")
req.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==")
resp, err := client.Do(req) body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,method":"rpc_modules"}`))
if err != nil { req, _ := http.NewRequest("POST", "http://"+srv.listenAddr(), body)
t.Error("could not issue a GET request to the test http server", err) req.Header.Set("content-type", "application/json")
} if key != "" && value != "" {
responses <- resp req.Header.Set(key, value)
}(responses) }
if host != "" {
req.Host = host
}
response := <-responses client := http.DefaultClient
assert.Equal(t, "websocket", response.Header.Get("Upgrade")) resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
return resp
} }
// 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/accounts"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
)
// 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 {
services map[reflect.Type]Service // Index of the already constructed services
Config Config
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
AccountManager *accounts.Manager // Account manager created by the node.
}
// 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, handles int, namespace string) (ethdb.Database, error) {
if ctx.Config.DataDir == "" {
return rawdb.NewMemoryDatabase(), nil
}
return rawdb.NewLevelDBDatabase(ctx.Config.ResolvePath(name), cache, handles, namespace)
}
// OpenDatabaseWithFreezer opens an existing database with the given name (or
// creates one if no previous can be found) from within the node's data directory,
// also attaching a chain freezer to it that moves ancient chain data from the
// database to immutable append-only files. If the node is an ephemeral one, a
// memory database is returned.
func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) {
if ctx.Config.DataDir == "" {
return rawdb.NewMemoryDatabase(), nil
}
root := ctx.Config.ResolvePath(name)
switch {
case freezer == "":
freezer = filepath.Join(root, "ancient")
case !filepath.IsAbs(freezer):
freezer = ctx.Config.ResolvePath(freezer)
}
return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace)
}
// ResolvePath resolves a user path into the data directory if that was relative
// and if the user actually uses persistent storage. It will return an empty string
// for emphemeral storage and the user's own input for absolute paths.
func (ctx *ServiceContext) ResolvePath(path string) string {
return ctx.Config.ResolvePath(path)
}
// 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
}
// ExtRPCEnabled returns the indicator whether node enables the external
// RPC(http, ws or graphql).
func (ctx *ServiceContext) ExtRPCEnabled() bool {
return ctx.Config.ExtRPCEnabled()
}
// 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 {
// Protocols retrieves the P2P protocols the service wishes to start.
Protocols() []p2p.Protocol
// APIs retrieves the list of RPC descriptors the service provides
APIs() []rpc.API
// 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
}
// 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{Config: Config{Name: "unit-test", DataDir: dir}}
db, err := ctx.OpenDatabase("persistent", 0, 0, "")
if err != nil {
t.Fatalf("failed to open persistent database: %v", err)
}
db.Close()
if _, err := os.Stat(filepath.Join(dir, "unit-test", "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{Config: Config{DataDir: ""}}
db, err = ctx.OpenDatabase("ephemeral", 0, 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)
}
defer stack.Close()
// 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()
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -96,11 +96,11 @@ type NodeConfig struct { ...@@ -96,11 +96,11 @@ type NodeConfig struct {
// Use an existing database instead of a temporary one if non-empty // Use an existing database instead of a temporary one if non-empty
DataDir string DataDir string
// Services are the names of the services which should be run when // Lifecycles are the names of the service lifecycles which should be run when
// starting the node (for SimNodes it should be the names of services // starting the node (for SimNodes it should be the names of service lifecycles
// contained in SimAdapter.services, for other nodes it should be // contained in SimAdapter.lifecycles, for other nodes it should be
// services registered by calling the RegisterService function) // service lifecycles registered by calling the RegisterLifecycle function)
Services []string Lifecycles []string
// Properties are the names of the properties this node should hold // Properties are the names of the properties this node should hold
// within running services (e.g. "bootnode", "lightnode" or any custom values) // within running services (e.g. "bootnode", "lightnode" or any custom values)
...@@ -137,7 +137,7 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) { ...@@ -137,7 +137,7 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) {
confJSON := nodeConfigJSON{ confJSON := nodeConfigJSON{
ID: n.ID.String(), ID: n.ID.String(),
Name: n.Name, Name: n.Name,
Services: n.Services, Services: n.Lifecycles,
Properties: n.Properties, Properties: n.Properties,
Port: n.Port, Port: n.Port,
EnableMsgEvents: n.EnableMsgEvents, EnableMsgEvents: n.EnableMsgEvents,
...@@ -175,7 +175,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { ...@@ -175,7 +175,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
} }
n.Name = confJSON.Name n.Name = confJSON.Name
n.Services = confJSON.Services n.Lifecycles = confJSON.Services
n.Properties = confJSON.Properties n.Properties = confJSON.Properties
n.Port = confJSON.Port n.Port = confJSON.Port
n.EnableMsgEvents = confJSON.EnableMsgEvents n.EnableMsgEvents = confJSON.EnableMsgEvents
...@@ -233,9 +233,8 @@ func assignTCPPort() (uint16, error) { ...@@ -233,9 +233,8 @@ func assignTCPPort() (uint16, error) {
type ServiceContext struct { type ServiceContext struct {
RPCDialer RPCDialer
NodeContext *node.ServiceContext Config *NodeConfig
Config *NodeConfig Snapshot []byte
Snapshot []byte
} }
// RPCDialer is used when initialising services which need to connect to // RPCDialer is used when initialising services which need to connect to
...@@ -245,27 +244,29 @@ type RPCDialer interface { ...@@ -245,27 +244,29 @@ type RPCDialer interface {
DialRPC(id enode.ID) (*rpc.Client, error) DialRPC(id enode.ID) (*rpc.Client, error)
} }
// Services is a collection of services which can be run in a simulation // LifecycleConstructor allows a Lifecycle to be constructed during node start-up.
type Services map[string]ServiceFunc // While the service-specific package usually takes care of Lifecycle creation and registration,
// for testing purposes, it is useful to be able to construct a Lifecycle on spot.
type LifecycleConstructor func(ctx *ServiceContext, stack *node.Node) (node.Lifecycle, error)
// ServiceFunc returns a node.Service which can be used to boot a devp2p node // LifecycleConstructors stores LifecycleConstructor functions to call during node start-up.
type ServiceFunc func(ctx *ServiceContext) (node.Service, error) type LifecycleConstructors map[string]LifecycleConstructor
// serviceFuncs is a map of registered services which are used to boot devp2p // lifecycleConstructorFuncs is a map of registered services which are used to boot devp2p
// nodes // nodes
var serviceFuncs = make(Services) var lifecycleConstructorFuncs = make(LifecycleConstructors)
// RegisterServices registers the given Services which can then be used to // RegisterLifecycles registers the given Services which can then be used to
// start devp2p nodes using either the Exec or Docker adapters. // start devp2p nodes using either the Exec or Docker adapters.
// //
// It should be called in an init function so that it has the opportunity to // It should be called in an init function so that it has the opportunity to
// execute the services before main() is called. // execute the services before main() is called.
func RegisterServices(services Services) { func RegisterLifecycles(lifecycles LifecycleConstructors) {
for name, f := range services { for name, f := range lifecycles {
if _, exists := serviceFuncs[name]; exists { if _, exists := lifecycleConstructorFuncs[name]; exists {
panic(fmt.Sprintf("node service already exists: %q", name)) panic(fmt.Sprintf("node service already exists: %q", name))
} }
serviceFuncs[name] = f lifecycleConstructorFuncs[name] = f
} }
// now we have registered the services, run reexec.Init() which will // now we have registered the services, run reexec.Init() which will
......
...@@ -26,8 +26,8 @@ import ( ...@@ -26,8 +26,8 @@ import (
func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) { func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) {
t.Helper() t.Helper()
adapter := adapters.NewSimAdapter(adapters.Services{ adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
return NewNoopService(nil), nil return NewNoopService(nil), nil
}, },
}) })
......
This diff is collapsed.
...@@ -64,12 +64,15 @@ type testService struct { ...@@ -64,12 +64,15 @@ type testService struct {
state atomic.Value state atomic.Value
} }
func newTestService(ctx *adapters.ServiceContext) (node.Service, error) { func newTestService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
svc := &testService{ svc := &testService{
id: ctx.Config.ID, id: ctx.Config.ID,
peers: make(map[enode.ID]*testPeer), peers: make(map[enode.ID]*testPeer),
} }
svc.state.Store(ctx.Snapshot) svc.state.Store(ctx.Snapshot)
stack.RegisterProtocols(svc.Protocols())
stack.RegisterAPIs(svc.APIs())
return svc, nil return svc, nil
} }
...@@ -126,7 +129,7 @@ func (t *testService) APIs() []rpc.API { ...@@ -126,7 +129,7 @@ func (t *testService) APIs() []rpc.API {
}} }}
} }
func (t *testService) Start(server *p2p.Server) error { func (t *testService) Start() error {
return nil return nil
} }
...@@ -288,7 +291,7 @@ func (t *TestAPI) Events(ctx context.Context) (*rpc.Subscription, error) { ...@@ -288,7 +291,7 @@ func (t *TestAPI) Events(ctx context.Context) (*rpc.Subscription, error) {
return rpcSub, nil return rpcSub, nil
} }
var testServices = adapters.Services{ var testServices = adapters.LifecycleConstructors{
"test": newTestService, "test": newTestService,
} }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment