diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 6f6cb39069f0..65c71ce4eb03 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -241,14 +241,14 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis - _, err = les.New(stack, &cfg) + lesBackend, err := les.New(stack, &cfg) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) } // Assemble the ethstats monitoring and reporting service' if stats != "" { - if err := ethstats.New(stack, stats); err != nil { + if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil { return nil, err } } diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 147587004060..6bc595566f15 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -278,7 +278,7 @@ func importChain(ctx *cli.Context) error { utils.SetupMetrics(ctx) // Start system runtime metrics collection go metrics.CollectProcessMetrics(3 * time.Second) - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() chain, db := utils.MakeChain(ctx, stack, false) @@ -372,7 +372,7 @@ func exportChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() chain, _ := utils.MakeChain(ctx, stack, true) @@ -407,7 +407,7 @@ func importPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -425,7 +425,7 @@ func exportPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -447,7 +447,7 @@ func copyDb(ctx *cli.Context) error { utils.Fatalf("Source ancient chain directory path argument missing") } // Initialize a new chain for the running node to sync into - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, false) @@ -555,7 +555,7 @@ func confirmAndRemoveDB(database string, kind string) { } func dump(ctx *cli.Context) error { - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, true) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index af08b7788611..a777f37050e5 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" @@ -144,10 +145,10 @@ func enableWhisper(ctx *cli.Context) bool { return false } -func makeFullNode(ctx *cli.Context) *node.Node { +func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { 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 shhEnabled := enableWhisper(ctx) @@ -166,13 +167,13 @@ func makeFullNode(ctx *cli.Context) *node.Node { } // Configure GraphQL if requested 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. 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. diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 5a88414a4230..e2f733f844a4 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -78,8 +78,8 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) - stack := makeFullNode(ctx) - startNode(ctx, stack) + stack, backend := makeFullNode(ctx) + startNode(ctx, stack, backend) defer stack.Close() // Attach to the newly started node and start the JavaScript console @@ -190,8 +190,8 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // everything down. func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags - stack := makeFullNode(ctx) - startNode(ctx, stack) + stack, backend := makeFullNode(ctx) + startNode(ctx, stack, backend) defer stack.Close() // Attach to the newly started node and start the JavaScript console diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 73a82f30656a..fbc8627498ce 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -32,12 +32,11 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console/prompt" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/flags" - "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" @@ -350,10 +349,10 @@ func geth(ctx *cli.Context) error { } prepare(ctx) - stack := makeFullNode(ctx) + stack, backend := makeFullNode(ctx) defer stack.Close() - startNode(ctx, stack) + startNode(ctx, stack, backend) stack.Wait() return nil } @@ -361,7 +360,7 @@ func geth(ctx *cli.Context) error { // startNode boots up the system node and all registered protocols, after which // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // miner. -func startNode(ctx *cli.Context, stack *node.Node) { +func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { debug.Memsize.Add("node", stack) // Start up the node itself @@ -384,20 +383,12 @@ func startNode(ctx *cli.Context, stack *node.Node) { // 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 ethBackend *eth.Ethereum - if err := stack.ServiceContext.Lifecycle(ðBackend); err != nil { - utils.Fatalf("Failed to retrieve ethereum service: %v", err) - } - ethBackend.SetContractBackend(ethClient) + backend.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 lesBackend *les.LightEthereum - if err := stack.ServiceContext.Lifecycle(&lesBackend); err != nil { - utils.Fatalf("Failed to retrieve light ethereum service: %v", err) - } - lesBackend.SetContractBackend(ethClient) + backend.SetContractBackend(ethClient) } go func() { @@ -463,24 +454,23 @@ func startNode(ctx *cli.Context, stack *node.Node) { if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } - // Check if node's backend is eth - var ethBackend *eth.Ethereum - if err := stack.ServiceContext.Lifecycle(ðBackend); err != nil { - utils.Fatalf("Ethereum service not running: %v", err) - } // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) { gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name) } - ethBackend.TxPool().SetGasPrice(gasprice) + txpool := backend.TxPool() + if txpool == nil { + utils.Fatalf("Ethereum service not running: %v", err) + } + txpool.SetGasPrice(gasprice) threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.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") } - if err := ethBackend.StartMining(threads); err != nil { + if err := backend.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 87be1d10fc59..d97c01fdb5ac 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -48,6 +48,7 @@ import ( "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/graphql" "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -61,7 +62,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" pcsclite "github.com/gballet/go-libpcsclite" cli "gopkg.in/urfave/cli.v1" @@ -1654,12 +1654,13 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { } // 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 { if cfg.SyncMode == downloader.LightSync { - _, err := les.New(stack, cfg) + backend, err := les.New(stack, cfg) if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } + return backend.ApiBackend } else { backend, err := eth.New(stack, cfg) if err != nil { @@ -1669,28 +1670,28 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { ls, _ := les.NewLesServer(backend, cfg) backend.AddLesServer(ls) } + return backend.APIBackend } } // RegisterShhService configures Whisper and adds it to the given node. func RegisterShhService(stack *node.Node, cfg *whisper.Config) { - if err := whisper.New(stack, cfg); err != nil { + if _, err := whisper.New(stack, cfg); err != nil { Fatalf("Failed to register the Whisper service: %v", err) } } // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // the given node. -func RegisterEthStatsService(stack *node.Node, url string) { - if err := ethstats.New(stack, url); err != nil { +func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) { + if err := ethstats.New(stack, backend, backend.Engine(), url); err != nil { 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. -func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - // create new graphQL service - if err := graphql.New(stack, endpoint, cors, vhosts, timeouts); err != nil { +func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.Config) { + if err := graphql.New(stack, backend, cfg.GraphQLEndpoint(), cfg.GraphQLCors, cfg.GraphQLVirtualHosts, cfg.HTTPTimeouts); err != nil { Fatalf("Failed to register the GraphQL service: %v", err) } } diff --git a/eth/api_backend.go b/eth/api_backend.go index a7122da2cba8..46f41e7cd678 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" @@ -31,8 +32,10 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -257,6 +260,10 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, 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 { return b.eth.TxPool().SubscribeNewTxsEvent(ch) } @@ -307,3 +314,27 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +func (b *EthAPIBackend) Engine() consensus.Engine { + return b.eth.engine +} + +func (b *EthAPIBackend) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { + return b.eth.blockchain.GetBlockByNumber(number), nil +} + +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) +} + +func (b *EthAPIBackend) SetContractBackend(client *ethclient.Client) { + b.eth.SetContractBackend(client) +} diff --git a/eth/backend.go b/eth/backend.go index 174d6b79872d..c06bbe640114 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -138,7 +138,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) // Assemble the Ethereum object - chainDb, err := stack.ServiceContext.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 { return nil, err } @@ -151,9 +151,9 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { eth := &Ethereum{ config: config, chainDb: chainDb, - eventMux: stack.ServiceContext.EventMux, - accountManager: stack.ServiceContext.AccountManager, - engine: CreateConsensusEngine(stack.ServiceContext, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), + eventMux: stack.EventMux(), + accountManager: stack.AccountManager(), + engine: CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, @@ -206,7 +206,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { eth.bloomIndexer.Start(eth.blockchain) if config.TxPool.Journal != "" { - config.TxPool.Journal = stack.ServiceContext.ResolvePath(config.TxPool.Journal) + config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) } eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) @@ -222,14 +222,14 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{stack.ServiceContext.ExtRPCEnabled(), eth, nil} + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), eth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandidates, err = eth.setupDiscovery(&stack.ServiceContext.Config.P2P) + eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P) if err != nil { return nil, err } @@ -259,7 +259,7 @@ func makeExtraData(extra []byte) []byte { } // 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 chainConfig.Clique != nil { return clique.New(chainConfig.Clique, db) @@ -277,7 +277,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo return ethash.NewShared() default: engine := ethash.New(ethash.Config{ - CacheDir: ctx.ResolvePath(config.CacheDir), + CacheDir: stack.ResolvePath(config.CacheDir), CachesInMem: config.CachesInMem, CachesOnDisk: config.CachesOnDisk, CachesLockMmap: config.CachesLockMmap, @@ -538,15 +538,6 @@ func (s *Ethereum) Protocols() []p2p.Protocol { return protos } -// P2PServer registers the node's running p2p server with the Backend. -func (s *Ethereum) P2PServer(server *p2p.Server) error { - if server == nil { - return node.ErrNodeStopped // TODO is this error okay to return? - } - s.p2pServer = server - return nil -} - // Start implements node.Lifecycle, starting all internal goroutines needed by the // Ethereum protocol implementation. func (s *Ethereum) Start() error { diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index bc0305fc229d..e869f492e575 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -397,6 +397,14 @@ func (ec *Client) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuer return ec.c.EthSubscribe(ctx, ch, "logs", arg) } +// ToFilterArg is for testing purposes only. +func ToFilterArg(q ethereum.FilterQuery, test bool) (interface{}, error) { + if test { + return toFilterArg(q) + } + return nil, fmt.Errorf("functionality reserved for testing") // TODO is this okay? +} + func toFilterArg(q ethereum.FilterQuery) (interface{}, error) { arg := map[string]interface{}{ "address": q.Addresses, diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index b49abe917732..09cbdefa7e72 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package ethclient +package ethclient_test import ( "context" @@ -33,23 +33,24 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" ) // Verify that Client implements the ethereum interfaces. var ( - _ = ethereum.ChainReader(&Client{}) - _ = ethereum.TransactionReader(&Client{}) - _ = ethereum.ChainStateReader(&Client{}) - _ = ethereum.ChainSyncReader(&Client{}) - _ = ethereum.ContractCaller(&Client{}) - _ = ethereum.GasEstimator(&Client{}) - _ = ethereum.GasPricer(&Client{}) - _ = ethereum.LogFilterer(&Client{}) - _ = ethereum.PendingStateReader(&Client{}) + _ = ethereum.ChainReader(ðclient.Client{}) + _ = ethereum.TransactionReader(ðclient.Client{}) + _ = ethereum.ChainStateReader(ðclient.Client{}) + _ = ethereum.ChainSyncReader(ðclient.Client{}) + _ = ethereum.ContractCaller(ðclient.Client{}) + _ = ethereum.GasEstimator(ðclient.Client{}) + _ = ethereum.GasPricer(ðclient.Client{}) + _ = ethereum.LogFilterer(ðclient.Client{}) + _ = ethereum.PendingStateReader(ðclient.Client{}) // _ = ethereum.PendingStateEventer(&Client{}) - _ = ethereum.PendingContractCaller(&Client{}) + _ = ethereum.PendingContractCaller(ðclient.Client{}) ) func TestToFilterArg(t *testing.T) { @@ -163,7 +164,7 @@ func TestToFilterArg(t *testing.T) { }, } { t.Run(testCase.name, func(t *testing.T) { - output, err := toFilterArg(testCase.input) + output, err := ethclient.ToFilterArg(testCase.input, true) if (testCase.err == nil) != (err == nil) { t.Fatalf("expected error %v but got %v", testCase.err, err) } @@ -255,7 +256,7 @@ func TestHeader(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - ec := NewClient(client) + ec := ethclient.NewClient(client) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -304,7 +305,7 @@ func TestBalanceAt(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - ec := NewClient(client) + ec := ethclient.NewClient(client) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -325,7 +326,7 @@ func TestTransactionInBlockInterrupted(t *testing.T) { defer backend.Stop() defer client.Close() - ec := NewClient(client) + ec := ethclient.NewClient(client) ctx, cancel := context.WithCancel(context.Background()) cancel() tx, err := ec.TransactionInBlock(ctx, common.Hash{1}, 1) @@ -342,7 +343,7 @@ func TestChainID(t *testing.T) { client, _ := backend.Attach() defer backend.Stop() defer client.Close() - ec := NewClient(client) + ec := ethclient.NewClient(client) id, err := ec.ChainID(context.Background()) if err != nil { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 0989a9862b12..2f8766ef8cca 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -36,7 +36,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -56,23 +56,15 @@ const ( chainHeadChanSize = 10 ) -type txPool interface { - // SubscribeNewTxsEvent should return an event subscription of - // NewTxsEvent and send events to the given channel. - SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription -} - -type blockChain interface { - SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription -} +// Backend encompasses the backend behaviors needed for the ethstats service. +type Backend ethapi.Backend // Service implements an Ethereum netstats reporting daemon that pushes local // chain statistics up to a monitoring server. type Service struct { - server *p2p.Server // Peer-to-peer server to retrieve networking infos - eth *eth.Ethereum // Full Ethereum service if monitoring a full node - les *les.LightEthereum // Light Ethereum service if monitoring a light node - engine consensus.Engine // Consensus engine to retrieve variadic block fields + server *p2p.Server // Peer-to-peer server to retrieve networking infos + backend Backend + engine consensus.Engine // Consensus engine to retrieve variadic block fields node string // Name of the node to display on the monitoring page pass string // Password to authorize access to the monitoring page @@ -83,7 +75,7 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(node *node.Node, url string) error { +func New(node *node.Node, backend Backend, engine consensus.Engine, url string) error { // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) @@ -91,27 +83,14 @@ func New(node *node.Node, url string) error { return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) } ethstats := &Service{ - server: node.Server(), - node: parts[1], - pass: parts[3], - host: parts[4], - pongCh: make(chan struct{}), - histCh: make(chan []uint64, 1), - } - - // fetch backend - var ethServ *eth.Ethereum - if err := node.ServiceContext.Lifecycle(ðServ); err == nil { - ethstats.eth = ethServ - ethstats.engine = ethServ.Engine() - } - var lesServ *les.LightEthereum - if err := node.ServiceContext.Lifecycle(&lesServ); err == nil { - ethstats.les = lesServ - ethstats.engine = lesServ.Engine() - } - if ethstats.engine == nil { // TODO check to make sure at least one backend is not nil? - return fmt.Errorf("Ethereum service not found") + backend: backend, + engine: engine, + server: node.Server(), + node: parts[1], + pass: parts[3], + host: parts[4], + pongCh: make(chan struct{}), + histCh: make(chan []uint64, 1), } node.RegisterLifecycle(ethstats) @@ -136,22 +115,12 @@ func (s *Service) Stop() error { // until termination. func (s *Service) loop() { // Subscribe to chain events to execute updates on - var blockchain blockChain - var txpool txPool - if s.eth != nil { - blockchain = s.eth.BlockChain() - txpool = s.eth.TxPool() - } else { - blockchain = s.les.BlockChain() - txpool = s.les.TxPool() - } - chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize) - headSub := blockchain.SubscribeChainHeadEvent(chainHeadCh) + headSub := s.backend.SubscribeChainHeadEvent(chainHeadCh) defer headSub.Unsubscribe() txEventCh := make(chan core.NewTxsEvent, txChanSize) - txSub := txpool.SubscribeNewTxsEvent(txEventCh) + txSub := s.backend.SubscribeNewTxsEvent(txEventCh) defer txSub.Unsubscribe() // Start a goroutine that exhausts the subscriptions to avoid events piling up @@ -560,29 +529,20 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { txs []txStats uncles []*types.Header ) - if s.eth != nil { - // Full nodes have all needed information available - if block == nil { - block = s.eth.BlockChain().CurrentBlock() - } - header = block.Header() - td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) - txs = make([]txStats, len(block.Transactions())) - for i, tx := range block.Transactions() { - txs[i].Hash = tx.Hash() - } - uncles = block.Uncles() - } else { - // Light nodes would need on-demand lookups for transactions/uncles, skip - if block != nil { - header = block.Header() - } else { - header = s.les.BlockChain().CurrentHeader() - } - td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) - txs = []txStats{} + // Full nodes have all needed information available + if block == nil { + block = s.backend.CurrentBlock() + } + header = block.Header() + td = s.backend.GetTd(header.Hash()) // TODO is it okay to just call `GetTD` with just the hash and not number? + + txs = make([]txStats, len(block.Transactions())) + for i, tx := range block.Transactions() { + txs[i].Hash = tx.Hash() } + uncles = block.Uncles() + // Assemble and return the block stats author, _ := s.engine.Author(header) @@ -613,12 +573,7 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { indexes = append(indexes, list...) } else { // No indexes requested, send back the top ones - var head int64 - if s.eth != nil { - head = s.eth.BlockChain().CurrentHeader().Number.Int64() - } else { - head = s.les.BlockChain().CurrentHeader().Number.Int64() - } + head := s.backend.CurrentHeader().Number.Int64() start := head - historyUpdateRange + 1 if start < 0 { start = 0 @@ -631,14 +586,11 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { history := make([]*blockStats, len(indexes)) for i, number := range indexes { // Retrieve the next block if it's known to us - var block *types.Block - if s.eth != nil { - block = s.eth.BlockChain().GetBlockByNumber(number) - } else { - if header := s.les.BlockChain().GetHeaderByNumber(number); header != nil { - block = types.NewBlockWithHeader(header) - } + block, err := s.backend.GetBlockByNumber(context.Background(), number) + if err != nil { + return err } + // If we do have the block, add to the history and continue if block != nil { history[len(history)-1-i] = s.assembleBlockStats(block) @@ -673,12 +625,7 @@ type pendStats struct { // it to the stats server. func (s *Service) reportPending(conn *websocket.Conn) error { // Retrieve the pending count from the local blockchain - var pending int - if s.eth != nil { - pending, _ = s.eth.TxPool().Stats() - } else { - pending = s.les.TxPool().Stats() - } + pending, _ := s.backend.Stats() // Assemble the transaction stats and send it to the server log.Trace("Sending pending transactions to ethstats", "count", pending) @@ -712,22 +659,20 @@ func (s *Service) reportStats(conn *websocket.Conn) error { var ( mining bool hashrate int - syncing bool - gasprice int ) - if s.eth != nil { - mining = s.eth.Miner().Mining() - hashrate = int(s.eth.Miner().HashRate()) - sync := s.eth.Downloader().Progress() - syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock - - price, _ := s.eth.APIBackend.SuggestPrice(context.Background()) - gasprice = int(price.Uint64()) - } else { - sync := s.les.Downloader().Progress() - syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock + mine := s.backend.Miner() + if mine != nil { + mining = mine.Mining() + hashrate = int(s.backend.Miner().HashRate()) } + + sync := s.backend.Downloader().Progress() + syncing := s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock + + price, _ := s.backend.SuggestPrice(context.Background()) + gasprice := int(price.Uint64()) + // Assemble the node stats and send it to the server log.Trace("Sending node details to ethstats") diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 1c2a56ab0877..3b18cb5e670a 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -181,13 +181,13 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { func createGQLService(t *testing.T, stack *node.Node, endpoint string) { // create backend - _, err := eth.New(stack, ð.DefaultConfig) + ethBackend, err := eth.New(stack, ð.DefaultConfig) if err != nil { t.Fatalf("could not create eth backend: %v", err) } // create gql service - err = New(stack, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) + err = New(stack, ethBackend.APIBackend, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) if err != nil { t.Fatalf("could not create graphql service: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index 8bec768ef6ef..f93f12a545e3 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,12 +17,9 @@ package graphql import ( - "errors" "net/http" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/graph-gophers/graphql-go" @@ -30,19 +27,9 @@ import ( ) // New constructs a new GraphQL service instance. -func New(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { - // fetch backend - var backend ethapi.Backend - var ethServ *eth.Ethereum - if err := stack.ServiceContext.Lifecycle(ðServ); err == nil { - backend = ethServ.APIBackend - } - var lesServ *les.LightEthereum - if err := stack.ServiceContext.Lifecycle(&lesServ); err == nil { - backend = lesServ.ApiBackend - } +func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { if backend == nil { - return errors.New("No backend found") // TODO should this be a fatal error? + stack.Fatalf("missing backend") } // check if http server with given endpoint exists and enable graphQL on it server := stack.ExistingHTTPServer(endpoint) diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 074cd794a65a..6d8374eb53c9 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -23,14 +23,17 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -53,9 +56,12 @@ type Backend interface { HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) HeaderByHash(ctx context.Context, hash common.Hash) (*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) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) + GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) @@ -73,8 +79,13 @@ type Backend interface { GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) Stats() (pending int, queued int) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) + TxPool() *core.TxPool SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription + // Mining API + Miner() *miner.Miner + StartMining(threads int) error + // Filter API BloomStatus() (uint64, uint64) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) @@ -84,7 +95,9 @@ type Backend interface { SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription ChainConfig() *params.ChainConfig - CurrentBlock() *types.Block + Engine() consensus.Engine + + SetContractBackend(client *ethclient.Client) } func GetAPIs(apiBackend Backend) []rpc.API { diff --git a/les/api_backend.go b/les/api_backend.go index 448260a19802..d905081077a6 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -19,10 +19,12 @@ package les import ( "context" "errors" + "fmt" "math/big" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" @@ -31,9 +33,11 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -282,3 +286,32 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +func (b *LesApiBackend) Engine() consensus.Engine { + return b.eth.engine +} + +func (b *LesApiBackend) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { + header := b.eth.blockchain.GetHeaderByNumber(number) + return types.NewBlockWithHeader(header), nil // TODO is this okay? +} + +func (b *LesApiBackend) CurrentHeader() *types.Header { + return b.eth.blockchain.CurrentHeader() +} + +func (b *LesApiBackend) Miner() *miner.Miner { + return nil +} + +func (b *LesApiBackend) StartMining(threads int) error { + return fmt.Errorf("Light clients do not support mining") // TODO is this okay? +} + +func (b *LesApiBackend) SetContractBackend(client *ethclient.Client) { + b.eth.SetContractBackend(client) +} + +func (b *LesApiBackend) TxPool() *core.TxPool { + return nil // TODO is this okay? +} diff --git a/les/client.go b/les/client.go index dc6a86a0fc9f..b8bd3ccabc40 100644 --- a/les/client.go +++ b/les/client.go @@ -78,11 +78,11 @@ type LightEthereum struct { } func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { - chainDb, err := stack.ServiceContext.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") + chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") if err != nil { return nil, err } - lespayDb, err := stack.ServiceContext.OpenDatabase("lespay", 0, 0, "eth/db/lespay") + lespayDb, err := stack.OpenDatabase("lespay", 0, 0, "eth/db/lespay") if err != nil { return nil, err } @@ -103,10 +103,10 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { closeCh: make(chan struct{}), }, peers: peers, - eventMux: stack.ServiceContext.EventMux, + eventMux: stack.EventMux(), reqDist: newRequestDistributor(peers, &mclock.System{}), - accountManager: stack.ServiceContext.AccountManager, - engine: eth.CreateConsensusEngine(stack.ServiceContext, chainConfig, &config.Ethash, nil, false, chainDb), + accountManager: stack.AccountManager(), + engine: eth.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), 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)), @@ -114,7 +114,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { } peers.subscribe((*vtSubscription)(leth.valueTracker)) - dnsdisc, err := leth.setupDiscovery(&stack.ServiceContext.Config.P2P) + dnsdisc, err := leth.setupDiscovery(&stack.Config().P2P) if err != nil { return nil, err } @@ -164,7 +164,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } - leth.ApiBackend = &LesApiBackend{stack.ServiceContext.ExtRPCEnabled(), leth, nil} + leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), leth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice diff --git a/miner/stress_clique.go b/miner/stress_clique.go index 6f65ddddfca2..143ce3f5eb2e 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -22,6 +22,7 @@ package main import ( "bytes" "crypto/ecdsa" + "github.com/ethereum/go-ethereum/internal/ethapi" "io/ioutil" "math/big" "math/rand" @@ -59,12 +60,16 @@ func main() { genesis := makeGenesis(faucets, sealers) var ( - nodes []*node.Node + nodes []struct{ + node *node.Node + backend ethapi.Backend + } enodes []*enode.Node ) + for _, sealer := range sealers { // Start the node and wait until it's up - node, err := makeSealer(genesis) + node, ethBackend, err := makeSealer(genesis) if err != nil { panic(err) } @@ -78,7 +83,13 @@ func main() { node.Server().AddPeer(n) } // Start tracking the node and it's enode - nodes = append(nodes, node) + nodes = append(nodes, struct{ + node *node.Node + backend ethapi.Backend + }{ + node, + ethBackend, + }) enodes = append(enodes, node.Server().Self()) // Inject the signer key and start sealing with it @@ -95,11 +106,7 @@ func main() { time.Sleep(3 * time.Second) for _, node := range nodes { - var ethereum *eth.Ethereum - if err := node.ServiceContext.Lifecycle(ðereum); err != nil { - panic(err) - } - if err := ethereum.StartMining(1); err != nil { + if err := node.backend.StartMining(1); err != nil { panic(err) } } @@ -111,22 +118,24 @@ func main() { index := rand.Intn(len(faucets)) // Fetch the accessor for the relevant signer - var ethereum *eth.Ethereum - if err := nodes[index%len(nodes)].Service(ðereum); err != nil { - panic(err) - } + backend := nodes[index%len(nodes)].backend + // 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]) if err != nil { panic(err) } - if err := ethereum.TxPool().AddLocal(tx); err != nil { + txpool := backend.TxPool() + if txpool == nil { + panic(fmt.Errorf("Ethereum service not running")) + } + if err := txpool.AddLocal(tx); err != nil { panic(err) } nonces[index]++ // Wait if we're too saturated - if pend, _ := ethereum.TxPool().Stats(); pend > 2048 { + if pend, _ := txpool.Stats(); pend > 2048 { time.Sleep(100 * time.Millisecond) } } @@ -169,7 +178,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core return genesis } -func makeSealer(genesis *core.Genesis) (*node.Node, error) { +func makeSealer(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") @@ -213,5 +222,5 @@ func makeSealer(genesis *core.Genesis) (*node.Node, error) { stack.RegisterLifecycle(ethBackend) // Start the node and return if successful - return stack, stack.Start() + return stack, ethBackend, stack.Start() } diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 6893dff6e273..4e0a75174d3f 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -61,12 +61,15 @@ func main() { genesis := makeGenesis(faucets) var ( - nodes []*node.Node + nodes []struct{ + node *node.Node + backend ethapi.Backend + } enodes []*enode.Node ) for i := 0; i < 4; i++ { // Start the node and wait until it's up - node, err := makeMiner(genesis) + node, ethBackend, err := makeMiner(genesis) if err != nil { panic(err) } @@ -80,7 +83,13 @@ func main() { node.Server().AddPeer(n) } // Start tracking the node and it's enode - nodes = append(nodes, node) + nodes = append(nodes, struct{ + node *node.Node + backend ethapi.Backend + }{ + node, + ethBackend, + }) enodes = append(enodes, node.Server().Self()) // Inject the signer key and start sealing with it @@ -93,11 +102,7 @@ func main() { time.Sleep(3 * time.Second) for _, node := range nodes { - var ethereum *eth.Ethereum - if err := node.ServiceContext.Lifecycle(ðereum); err != nil { - panic(err) - } - if err := ethereum.StartMining(1); err != nil { + if err := node.backend.StartMining(1); err != nil { panic(err) } } @@ -107,24 +112,24 @@ func main() { nonces := make([]uint64, len(faucets)) for { index := rand.Intn(len(faucets)) - // Fetch the accessor for the relevant signer - var ethereum *eth.Ethereum - if err := nodes[index%len(nodes)].Service(ðereum); err != nil { - panic(err) - } + backend := nodes[index%len(nodes)].backend // 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]) if err != nil { panic(err) } - if err := ethereum.TxPool().AddLocal(tx); err != nil { + txpool := backend.TxPool() + if txpool == nil { + panic(fmt.Errorf("Ethereum service not running")) + } + if err := txpool.AddLocal(tx); err != nil { panic(err) } nonces[index]++ // Wait if we're too saturated - if pend, _ := ethereum.TxPool().Stats(); pend > 2048 { + if pend, _ := txpool.Stats(); pend > 2048 { time.Sleep(100 * time.Millisecond) } } @@ -149,7 +154,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { return genesis } -func makeMiner(genesis *core.Genesis) (*node.Node, error) { +func makeMiner(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") @@ -194,5 +199,5 @@ func makeMiner(genesis *core.Genesis) (*node.Node, error) { stack.RegisterLifecycle(ethBackend) // Start the node and return if successful - return stack, stack.Start() + return stack, ethBackend, stack.Start() } diff --git a/mobile/geth.go b/mobile/geth.go index 053c304f6738..22567a5d107b 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -175,20 +175,20 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ethConf.SyncMode = downloader.LightSync ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.DatabaseCache = config.EthereumDatabaseCache - _, err = les.New(rawStack, ðConf) + lesBackend, err := les.New(rawStack, ðConf) if err != nil { return nil, fmt.Errorf("ethereum init: %v", err) } // If netstats reporting is requested, do it if config.EthereumNetStats != "" { - if err := ethstats.New(rawStack, config.EthereumNetStats); err != nil { + if err := ethstats.New(rawStack, lesBackend.ApiBackend, lesBackend.Engine(), config.EthereumNetStats); err != nil { return nil, fmt.Errorf("netstats init: %v", err) } } } // Register the Whisper protocol if requested if config.WhisperEnabled { - if err := whisper.New(rawStack, &whisper.DefaultConfig); err != nil { + if _, err := whisper.New(rawStack, &whisper.DefaultConfig); err != nil { return nil, fmt.Errorf("whisper init: %v", err) } } diff --git a/node/api.go b/node/api.go index ec9b836d7203..d6f1901907aa 100644 --- a/node/api.go +++ b/node/api.go @@ -159,7 +159,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } endpoint := fmt.Sprintf("%s:%d", *host, *port) // check if HTTP server already exists - if server, exists := api.node.HTTPServers.servers[endpoint]; exists { + if server, exists := api.node.httpServers[endpoint]; exists { if server.RPCAllowed { return false, fmt.Errorf("HTTP RPC already running on %v", server.Listener.Addr()) } @@ -220,7 +220,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - for _, httpServer := range api.node.HTTPServers.servers { + for _, httpServer := range api.node.httpServers { if httpServer.RPCAllowed { api.node.stopServer(httpServer) return true, nil @@ -235,7 +235,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str api.node.lock.Lock() defer api.node.lock.Unlock() // check if an existing WS server already exists - for _, server := range api.node.HTTPServers.servers { + for _, server := range api.node.httpServers { if server.WSAllowed { return false, fmt.Errorf("WebSocket RPC already running on %v", server.Listener.Addr()) } @@ -253,7 +253,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } endpoint := fmt.Sprintf("%s:%d", *host, *port) // check if there is an existing server on the specified port, and if there is, enable ws on it - if server, exists := api.node.HTTPServers.servers[endpoint]; exists { + if server, exists := api.node.httpServers[endpoint]; exists { // else configure ws on the existing server server.WSAllowed = true // configure origins @@ -322,7 +322,7 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - for _, httpServer := range api.node.HTTPServers.servers { + for _, httpServer := range api.node.httpServers { if httpServer.WSAllowed { httpServer.WSAllowed = false // if RPC is not enabled on the WS http server, shut it down diff --git a/node/lifecycle.go b/node/lifecycle.go new file mode 100644 index 000000000000..0d5f9a0680a0 --- /dev/null +++ b/node/lifecycle.go @@ -0,0 +1,31 @@ +// 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 . + +package node + +// Lifecycle encompasses the behavior of services that can be started and stopped +// on the node. Lifecycle management is delegated to the node, but it is the +// 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 + + // Stop terminates all goroutines belonging to the service, blocking until they + // are all terminated. + Stop() error +} diff --git a/node/node.go b/node/node.go index f4c35254ac67..d347c7fbaa69 100644 --- a/node/node.go +++ b/node/node.go @@ -52,11 +52,9 @@ type Node struct { server *p2p.Server // Currently running P2P networking layer - ServiceContext *ServiceContext // TODO rename to LifecycleContext or just NodeContext? - lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - HTTPServers *HTTPServers // HTTPServers stores information about the node's rpc, ws, and graphQL http servers. + httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests @@ -109,13 +107,7 @@ func New(conf *Config) (*Node, error) { ephemeralKeystore: ephemeralKeystore, config: conf, lifecycles: make(map[reflect.Type]Lifecycle), - ServiceContext: &ServiceContext{ - Config: *conf, - Lifecycles: make(map[reflect.Type]Lifecycle), - }, - HTTPServers: &HTTPServers{ - servers: make(map[string]*HTTPServer), - }, + httpServers: make(serverMap), ipc: &HTTPServer{ endpoint: conf.IPCEndpoint(), }, @@ -138,9 +130,7 @@ func New(conf *Config) (*Node, error) { if node.server.Config.NodeDatabase == "" { node.server.Config.NodeDatabase = node.config.NodeDB() } - // Configure service context - node.ServiceContext.EventMux = node.eventmux - node.ServiceContext.AccountManager = node.accman + // Configure HTTP server(s) if conf.HTTPHost != "" { httpServ := &HTTPServer{ @@ -159,13 +149,14 @@ func New(conf *Config) (*Node, error) { httpServ.WSAllowed = true httpServ.WsOrigins = conf.WSOrigins httpServ.Whitelist = append(httpServ.Whitelist, conf.WSModules...) - node.HTTPServers.servers[conf.HTTPEndpoint()] = httpServ + + node.httpServers[conf.HTTPEndpoint()] = httpServ return node, nil } - node.HTTPServers.servers[conf.HTTPEndpoint()] = httpServ + node.httpServers[conf.HTTPEndpoint()] = httpServ } if conf.WSHost != "" { - node.HTTPServers.servers[conf.WSEndpoint()] = &HTTPServer{ + node.httpServers[conf.WSEndpoint()] = &HTTPServer{ WsOrigins: conf.WSOrigins, Whitelist: conf.WSModules, Srv: rpc.NewServer(), @@ -206,11 +197,10 @@ func (n *Node) Close() error { func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { kind := reflect.TypeOf(lifecycle) if _, exists := n.lifecycles[kind]; exists { - Fatalf("Lifecycle cannot be registered more than once", kind) + n.Fatalf("Lifecycle cannot be registered more than once", kind) } n.lifecycles[kind] = lifecycle - n.ServiceContext.Lifecycles[kind] = lifecycle } // RegisterProtocols adds backend's protocols to the node's p2p server @@ -225,12 +215,12 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { // RegisterHTTPServer registers the given HTTP server on the node func (n *Node) RegisterHTTPServer(endpoint string, server *HTTPServer) { - n.HTTPServers.servers[endpoint] = server + n.httpServers[endpoint] = server } // ExistingHTTPServer checks if an HTTP server is already configured on the given endpoint func (n *Node) ExistingHTTPServer(endpoint string) *HTTPServer { - if server, exists := n.HTTPServers.servers[endpoint]; exists { + if server, exists := n.httpServers[endpoint]; exists { return server } return nil @@ -258,8 +248,7 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { httpSrv.WriteTimeout = h.Timeouts.WriteTimeout httpSrv.IdleTimeout = h.Timeouts.IdleTimeout } - - // complete the HTTPServers + // add listener and http.Server to HTTPServer h.Listener = listener h.Server = httpSrv @@ -294,7 +283,7 @@ func (n *Node) Start() error { // Configure the RPC interfaces if err := n.configureRPC(); err != nil { - n.HTTPServers.Stop() + n.httpServers.Stop() n.server.Stop() return err } @@ -310,10 +299,6 @@ func (n *Node) Start() error { started = append(started, lifecycle) } - // Finish initializing the service context - n.ServiceContext.AccountManager = n.accman - n.ServiceContext.EventMux = n.eventmux - // Finish initializing the startup n.stop = make(chan struct{}) return nil @@ -365,7 +350,7 @@ func (n *Node) configureRPC() error { return err } - for _, server := range n.HTTPServers.servers { + for _, server := range n.httpServers { // configure the handlers if server.RPCAllowed { server.handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) @@ -396,8 +381,8 @@ func (n *Node) configureRPC() error { } } // only register http server as a lifecycle if it has not already been registered - if _, exists := n.lifecycles[reflect.TypeOf(n.HTTPServers)]; !exists { - n.RegisterLifecycle(n.HTTPServers) + if _, exists := n.lifecycles[reflect.TypeOf(n.httpServers)]; !exists { + n.RegisterLifecycle(n.httpServers) } // All API endpoints started successfully return nil @@ -467,7 +452,7 @@ func (n *Node) stopServer(server *HTTPServer) { server.Srv = nil } // remove stopped http server from node's http servers // TODO is this preferable? - delete(n.HTTPServers.servers, server.endpoint) + delete(n.httpServers, server.endpoint) } // Stop terminates a running node along with all it's services. In the node was @@ -594,7 +579,7 @@ func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - for _, httpServer := range n.HTTPServers.servers { + for _, httpServer := range n.httpServers { if httpServer.WSAllowed { if httpServer.Listener != nil { return httpServer.Listener.Addr().String() @@ -612,24 +597,6 @@ func (n *Node) EventMux() *event.TypeMux { return n.eventmux } -// Lifecycle retrieves a currently running Lifecycle registered of a specific type. -func (n *Node) Lifecycle(lifecycle interface{}) error { - n.lock.RLock() - defer n.lock.RUnlock() - - // Short circuit if the node's not running - if !n.running() { - return ErrNodeStopped - } - // Otherwise try to find the service to return - element := reflect.ValueOf(lifecycle).Elem() - if running, ok := n.lifecycles[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} - // OpenDatabase opens an existing database with the given name (or creates one if no // previous can be found) from within the node's instance directory. If the node is // ephemeral, a memory database is returned. @@ -712,11 +679,10 @@ func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server return nil } -// TODO change this when you figure out how else to do a nice fatal err // Fatalf formats a message to standard error and exits the program. // The message is also printed to standard output if standard error // is redirected to a different file. -func Fatalf(format string, args ...interface{}) { +func (n *Node) Fatalf(format string, args ...interface{}) { w := io.MultiWriter(os.Stdout, os.Stderr) if runtime.GOOS == "windows" { // The SameFile check below doesn't work on Windows. diff --git a/node/node_test.go b/node/node_test.go index e00c686058e4..d87a5612a5ce 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -364,47 +364,6 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { stack.server.PrivateKey = testNodeKey } -// TestLifecycleRetrieval tests that individual services can be retrieved. -func TestLifecycleRetrieval(t *testing.T) { - // Create a simple stack and register two service types - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - noop := NewNoop() - stack.RegisterLifecycle(noop) - - is, err := NewInstrumentedService() - if err != nil { - t.Fatalf("instrumented service creation failed: %v", err) - } - stack.RegisterLifecycle(is) - - // Make sure none of the services can be retrieved until started - var noopServ *Noop - if err := stack.Lifecycle(&noopServ); err != ErrNodeStopped { - t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) - } - var instServ *InstrumentedService - if err := stack.Lifecycle(&instServ); err != ErrNodeStopped { - t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) - } - // Start the stack and ensure everything is retrievable now - if err := stack.Start(); err != nil { - t.Fatalf("failed to start stack: %v", err) - } - defer stack.Stop() - - if err := stack.Lifecycle(&noopServ); err != nil { - t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil) - } - if err := stack.Lifecycle(&instServ); err != nil { - t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil) - } -} - // Tests whether a given HTTPServer can be registered on the node func TestRegisterHTTPServer(t *testing.T) { stack, err := New(testNodeConfig()) @@ -449,15 +408,21 @@ func TestRegisterHTTPServer(t *testing.T) { func TestHTTPServerCreateAndStop(t *testing.T) { // test on same ports node1 := startHTTP(t, 7453, 7453) - if len(node1.HTTPServers.servers) != 1 { + if len(node1.httpServers) != 1 { t.Fatalf("node has more than 1 http server") } // check to make sure http servers are registered - var httpSrv1 *HTTPServers - if err := node1.Lifecycle(&httpSrv1); err != nil { - t.Fatalf("HTTP servers not registered as lifecycles on the node: %v", err) + var exists bool + for _, lifecycle := range node1.lifecycles { + if reflect.DeepEqual(node1.httpServers, lifecycle) { + exists = true + } + } + if !exists { + t.Fatal("HTTP servers not registered as lifecycles on the node") } - for _, server := range node1.HTTPServers.servers { + // check to make sure http servers are configured properly + for _, server := range node1.httpServers { if !(server.WSAllowed && server.RPCAllowed) { t.Fatalf("node's http server is not configured to handle both rpc and ws") } @@ -466,21 +431,25 @@ func TestHTTPServerCreateAndStop(t *testing.T) { t.Fatalf("failed to remove server %v from node after stopping it", server) } } - node1.Close() // test on separate ports node2 := startHTTP(t, 7453, 9393) - if len(node2.HTTPServers.servers) != 2 { + if len(node2.httpServers) != 2 { t.Fatalf("amount of http servers on the node is not equal to 2") } // check to make sure http servers are registered - var httpSrv2 *HTTPServers - if err := node2.Lifecycle(&httpSrv2); err != nil { - t.Fatalf("HTTP servers not registered as lifecycles on the node: %v", err) + exists = false + for _, lifecycle := range node2.lifecycles { + if reflect.DeepEqual(node2.httpServers, lifecycle) { + exists = true + } + } + if !exists { + t.Fatal("HTTP servers not registered as lifecycles on the node") } // check that neither http server has both ws and rpc enabled - for _, server := range node2.HTTPServers.servers { + for _, server := range node2.httpServers { if server.WSAllowed && server.RPCAllowed { t.Fatalf("both rpc and ws allowed on a single http server") } diff --git a/node/rpcstack.go b/node/rpcstack.go index 3ceab681e687..d12089274b8d 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -32,9 +32,7 @@ import ( "github.com/rs/cors" ) -type HTTPServers struct { - servers map[string]*HTTPServer // Stores information about all http servers (if any) by their port, including http, ws, and graphql -} +type serverMap map[string]*HTTPServer // Stores information about all http servers (if any) by their endpoint, including http, ws, and graphql type HTTPServer struct { handler http.Handler @@ -55,23 +53,23 @@ type HTTPServer struct { Timeouts rpc.HTTPTimeouts RPCAllowed bool - WSAllowed bool + WSAllowed bool // TODO discuss this later bc possible race condition GQLAllowed bool GQLHandler http.Handler } -func (h *HTTPServers) Start() error { - for _, server := range h.servers { +func (sm serverMap) Start() error { + for _, server := range sm { if err := server.Start(); err != nil { - return h.Stop() + return sm.Stop() } } return nil } -func (h *HTTPServers) Stop() error { - for _, server := range h.servers { +func (sm serverMap) Stop() error { + for _, server := range sm { if err := server.Stop(); err != nil { return err } @@ -79,14 +77,14 @@ func (h *HTTPServers) Stop() error { return nil } -// Start starts the HTTPServers's HTTP server. // TODO I don't like the way this is written +// Start starts the serverMap's HTTP server. // TODO I don't like the way this is written func (h *HTTPServer) Start() error { go h.Server.Serve(h.Listener) log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", h.Listener.Addr())) return nil } -// Stop shuts down the HTTPServers's HTTP server. // TODO I don't like the way this is written +// Stop shuts down the serverMap's HTTP server. // TODO I don't like the way this is written func (h *HTTPServer) Stop() error { if h.Server != nil { url := fmt.Sprintf("http://%v/", h.Listener.Addr()) @@ -102,12 +100,12 @@ func (h *HTTPServer) Stop() error { return nil } -// SetHandler assigns the given handler to the HTTPServers. +// SetHandler assigns the given handler to the serverMap. func (h *HTTPServer) SetHandler(handler http.Handler) { h.handler = handler } -// SetEndpoints assigns the given endpoint to the HTTPServers. +// SetEndpoints assigns the given endpoint to the serverMap. func (h *HTTPServer) SetEndpoint(endpoint string) { h.endpoint = endpoint } @@ -224,6 +222,7 @@ func newGzipHandler(next http.Handler) http.Handler { // NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade // request to the websocket protocol. If not, serves the the request with the http handler. func (hs *HTTPServer) NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { + // TODO make sure you protect the pointer return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if hs.WSAllowed && isWebsocket(r) { ws.ServeHTTP(w, r) diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index dc75afa93438..799b138a6917 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -40,7 +40,7 @@ func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { assert.Equal(t, "websocket", response.Header.Get("Upgrade")) } -// Tests that a ws handler can be added to and enabled on an existing HTTPServers +// Tests that a ws handler can be added to and enabled on an existing HTTPServer func TestWSAllowed(t *testing.T) { stack, err := New(&Config{ HTTPHost: DefaultHTTPHost, diff --git a/node/service.go b/node/service.go deleted file mode 100644 index e7746e92262c..000000000000 --- a/node/service.go +++ /dev/null @@ -1,104 +0,0 @@ -// 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 . - -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" -) - -// 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 { - Config Config - Lifecycles map[reflect.Type]Lifecycle // TODO should this be in the service context or should it be on the node itself .. ? - 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) -} - -// Lifecycle retrieves a currently running Lifecycle registered of a specific type. -func (ctx *ServiceContext) Lifecycle(lifecycle interface{}) error { - element := reflect.ValueOf(lifecycle).Elem() - if running, ok := ctx.Lifecycles[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() -} - -// Lifecycle encompasses the behavior of services that can be started and stopped -// on the node. Lifecycle management is delegated to the node, but it is the -// 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 - - // Stop terminates all goroutines belonging to the service, blocking until they - // are all terminated. - Stop() error -} diff --git a/node/service_test.go b/node/service_test.go deleted file mode 100644 index 80f40aeeb68b..000000000000 --- a/node/service_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// 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 . - -package node - -import ( - "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 Lifecycles can be retrieved by later ones. -func TestContextLifecycles(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 - - noop := NewNoop() - stack.RegisterLifecycle(noop) - - is, err := NewInstrumentedService() - if err != nil { - t.Fatalf("could not create instrumented service %v", err) - - } - is.startHook = func() { - if err := stack.ServiceContext.Lifecycle(&noop); err != nil { - t.Errorf("former service not found: %v", err) - } - } - stack.RegisterLifecycle(is) - - // 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() -} diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 2a6d3aeaaa7e..1f7a21470db0 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -443,9 +443,8 @@ func startExecNodeStack() (*node.Node, error) { return nil, fmt.Errorf("unknown node service %q", err) } ctx := &ServiceContext{ - RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, - NodeContext: stack.ServiceContext, - Config: conf.Node, + RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, + Config: conf.Node, } if conf.Snapshots != nil { ctx.Snapshot = conf.Snapshots[name] diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 48c5bd9753f2..c786b9f9ddc1 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -250,9 +250,8 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { sn.registerOnce.Do(func() { for _, name := range sn.config.Lifecycles { ctx := &ServiceContext{ - RPCDialer: sn.adapter, - NodeContext: sn.node.ServiceContext, - Config: sn.config, + RPCDialer: sn.adapter, + Config: sn.config, } if snapshots != nil { ctx.Snapshot = snapshots[name] diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 1ca60f4b9b58..716cde6a6c7d 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -233,9 +233,8 @@ func assignTCPPort() (uint16, error) { type ServiceContext struct { RPCDialer - NodeContext *node.ServiceContext - Config *NodeConfig - Snapshot []byte + Config *NodeConfig + Snapshot []byte } // RPCDialer is used when initialising services which need to connect to diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index 959a1e32f629..069ec97d09b3 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -91,10 +91,10 @@ func TestMailServer(t *testing.T) { var server WMailServer - stack := newNode(t) + stack, w := newNode(t) defer stack.Close() + shh = w - shh = getWhisperFromNode(stack, t) shh.RegisterServer(&server) err = server.Init(shh, dir, password, powRequirement) @@ -218,12 +218,12 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope { // newNode creates a new node using a default config and // creates and registers a new Whisper service on it. -func newNode(t *testing.T) *node.Node { +func newNode(t *testing.T) (*node.Node, *whisper.Whisper) { stack, err := node.New(&node.DefaultConfig) if err != nil { t.Fatalf("could not create new node: %v", err) } - err = whisper.New(stack, &whisper.DefaultConfig) + w, err := whisper.New(stack, &whisper.DefaultConfig) if err != nil { t.Fatalf("could not create new whisper service: %v", err) } @@ -231,15 +231,5 @@ func newNode(t *testing.T) *node.Node { if err != nil { t.Fatalf("could not start node: %v", err) } - return stack -} - -// getWhisperFromNode retrieves the Whisper service from the running node. -func getWhisperFromNode(stack *node.Node, t *testing.T) *whisper.Whisper { - var w *whisper.Whisper - err := stack.Lifecycle(&w) - if err != nil { - t.Fatalf("could not get whisper service from node: %v", err) - } - return w + return stack, w } diff --git a/whisper/whisperv6/api_test.go b/whisper/whisperv6/api_test.go index 9aceaf456d6b..759ef221ed8d 100644 --- a/whisper/whisperv6/api_test.go +++ b/whisper/whisperv6/api_test.go @@ -23,11 +23,9 @@ import ( ) func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - keyID, err := w.GenerateSymKey() if err != nil { t.Fatalf("Error generating symmetric key: %v", err) diff --git a/whisper/whisperv6/filter_test.go b/whisper/whisperv6/filter_test.go index 49c2b7688361..c95e506972dd 100644 --- a/whisper/whisperv6/filter_test.go +++ b/whisper/whisperv6/filter_test.go @@ -93,11 +93,9 @@ func TestInstallFilters(t *testing.T) { const SizeTestFilters = 256 - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) tst := generateTestCases(t, SizeTestFilters) @@ -135,11 +133,9 @@ func TestInstallFilters(t *testing.T) { func TestInstallSymKeyGeneratesHash(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) filter, _ := generateFilter(t, true) @@ -166,11 +162,9 @@ func TestInstallSymKeyGeneratesHash(t *testing.T) { func TestInstallIdenticalFilters(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) filter1, _ := generateFilter(t, true) @@ -240,11 +234,9 @@ func TestInstallIdenticalFilters(t *testing.T) { func TestInstallFilterWithSymAndAsymKeys(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) filter1, _ := generateFilter(t, true) @@ -658,11 +650,9 @@ func TestWatchers(t *testing.T) { var x, firstID string var err error - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) tst := generateTestCases(t, NumFilters) for i = 0; i < NumFilters; i++ { diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index 5799919db967..ac610367050c 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -94,7 +94,7 @@ type Whisper struct { } // New creates a Whisper client ready to communicate through the Ethereum P2P network. -func New(stack *node.Node, cfg *Config) error { +func New(stack *node.Node, cfg *Config) (*Whisper, error) { if cfg == nil { cfg = &DefaultConfig } @@ -136,7 +136,7 @@ func New(stack *node.Node, cfg *Config) error { stack.RegisterAPIs(whisper.APIs()) stack.RegisterProtocols(whisper.Protocols()) stack.RegisterLifecycle(whisper) - return nil + return whisper, nil } // MinPow returns the PoW value required by this node. diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go index a8056d6776e1..7fb8f7c1cd85 100644 --- a/whisper/whisperv6/whisper_test.go +++ b/whisper/whisperv6/whisper_test.go @@ -30,10 +30,9 @@ import ( ) func TestWhisperBasic(t *testing.T) { - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - // get whisper service from node - w := getWhisperFromNode(stack, t) + shh := w.Protocols()[0] if shh.Name != ProtocolName { t.Fatalf("failed Protocol Name: %v.", shh.Name) @@ -114,11 +113,10 @@ func TestWhisperBasic(t *testing.T) { } func TestWhisperAsymmetricKeyImport(t *testing.T) { - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() var privateKeys []*ecdsa.PrivateKey - w := getWhisperFromNode(stack, t) for i := 0; i < 50; i++ { id, err := w.NewKeyPair() if err != nil { @@ -145,10 +143,9 @@ func TestWhisperAsymmetricKeyImport(t *testing.T) { } func TestWhisperIdentityManagement(t *testing.T) { - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) id1, err := w.NewKeyPair() if err != nil { t.Fatalf("failed to generate new key pair: %s.", err) @@ -272,11 +269,9 @@ func TestWhisperSymKeyManagement(t *testing.T) { id2 = string("arbitrary-string-2") ) - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - id1, err := w.GenerateSymKey() if err != nil { t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err) @@ -461,11 +456,9 @@ func TestWhisperSymKeyManagement(t *testing.T) { func TestExpiry(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - w.SetMinimumPowTest(0.0000001) defer w.SetMinimumPowTest(DefaultMinimumPoW) w.Start() @@ -530,11 +523,9 @@ func TestExpiry(t *testing.T) { func TestCustomization(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() @@ -626,11 +617,9 @@ func TestCustomization(t *testing.T) { func TestSymmetricSendCycle(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() @@ -720,11 +709,9 @@ func TestSymmetricSendCycle(t *testing.T) { func TestSymmetricSendWithoutAKey(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() @@ -793,11 +780,9 @@ func TestSymmetricSendWithoutAKey(t *testing.T) { func TestSymmetricSendKeyMismatch(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() @@ -907,11 +892,9 @@ func TestBloom(t *testing.T) { t.Fatal("bloomFilterMatch false negative") } - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - f := w.BloomFilter() if f != nil { t.Fatal("wrong bloom on creation") @@ -926,14 +909,14 @@ func TestBloom(t *testing.T) { } } -// newNode creates a new node using a default config and +// newNodeWithWhisper creates a new node using a default config and // creates and registers a new Whisper service on it. -func newNode(t *testing.T) *node.Node { +func newNodeWithWhisper(t *testing.T) (*node.Node, *Whisper) { stack, err := node.New(&node.DefaultConfig) if err != nil { t.Fatalf("could not create new node: %v", err) } - err = New(stack, &DefaultConfig) + w, err := New(stack, &DefaultConfig) if err != nil { t.Fatalf("could not create new whisper service: %v", err) } @@ -941,15 +924,5 @@ func newNode(t *testing.T) *node.Node { if err != nil { t.Fatalf("could not start node: %v", err) } - return stack -} - -// getWhisperFromNode retrieves the Whisper service from the running node. -func getWhisperFromNode(stack *node.Node, t *testing.T) *Whisper { - var w *Whisper - err := stack.Lifecycle(&w) - if err != nil { - t.Fatalf("could not get whisper service from node: %v", err) - } - return w + return stack, w }