diff --git a/XDCx/XDCx.go b/XDCx/XDCx.go
index 1c09249874..6fe39d4390 100644
--- a/XDCx/XDCx.go
+++ b/XDCx/XDCx.go
@@ -16,6 +16,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
+ "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc"
"golang.org/x/sync/syncmap"
@@ -67,7 +68,7 @@ func (XDCx *XDCX) Protocols() []p2p.Protocol {
return []p2p.Protocol{}
}
-func (XDCx *XDCX) Start(server *p2p.Server) error {
+func (XDCx *XDCX) Start() error {
return nil
}
@@ -91,7 +92,7 @@ func NewMongoDBEngine(cfg *Config) *XDCxDAO.MongoDatabase {
return mongoDB
}
-func New(cfg *Config) *XDCX {
+func New(stack *node.Node, cfg *Config) *XDCX {
XDCX := &XDCX{
orderNonce: make(map[common.Address]*big.Int),
Triegc: prque.New[int64, common.Hash](nil),
@@ -111,6 +112,9 @@ func New(cfg *Config) *XDCX {
XDCX.StateCache = tradingstate.NewDatabase(XDCX.db)
XDCX.settings.Store(overflowIdx, false)
+ stack.RegisterAPIs(XDCX.APIs())
+ stack.RegisterProtocols(XDCX.Protocols())
+ stack.RegisterLifecycle(XDCX)
return XDCX
}
diff --git a/XDCx/order_processor_test.go b/XDCx/order_processor_test.go
index 626c72cf26..214ff77496 100644
--- a/XDCx/order_processor_test.go
+++ b/XDCx/order_processor_test.go
@@ -9,6 +9,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types"
+ "github.com/XinFinOrg/XDPoSChain/node"
)
func Test_getCancelFeeV1(t *testing.T) {
@@ -90,7 +91,12 @@ func Test_getCancelFeeV1(t *testing.T) {
}
func Test_getCancelFee(t *testing.T) {
- XDCx := New(&DefaultConfig)
+ stack, err := node.New(&node.DefaultConfig)
+ if err != nil {
+ t.Fatalf("could not create new node: %v", err)
+ }
+ XDCx := New(stack, &DefaultConfig)
+ defer stack.Close()
db := rawdb.NewMemoryDatabase()
stateCache := tradingstate.NewDatabase(db)
tradingStateDb, _ := tradingstate.New(types.EmptyRootHash, stateCache)
diff --git a/XDCxlending/XDCxlending.go b/XDCxlending/XDCxlending.go
index 34a0009554..7b6e4e5b65 100644
--- a/XDCxlending/XDCxlending.go
+++ b/XDCxlending/XDCxlending.go
@@ -19,6 +19,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
+ "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
@@ -50,7 +51,7 @@ func (l *Lending) Protocols() []p2p.Protocol {
return []p2p.Protocol{}
}
-func (l *Lending) Start(server *p2p.Server) error {
+func (l *Lending) Start() error {
return nil
}
@@ -58,7 +59,7 @@ func (l *Lending) Stop() error {
return nil
}
-func New(XDCx *XDCx.XDCX) *Lending {
+func New(stack *node.Node, XDCx *XDCx.XDCX) *Lending {
lending := &Lending{
orderNonce: make(map[common.Address]*big.Int),
Triegc: prque.New[int64, common.Hash](nil),
@@ -67,6 +68,12 @@ func New(XDCx *XDCx.XDCX) *Lending {
}
lending.StateCache = lendingstate.NewDatabase(XDCx.GetLevelDB())
lending.XDCx = XDCx
+
+ // Register the backend on the node
+ stack.RegisterAPIs(lending.APIs())
+ stack.RegisterProtocols(lending.Protocols())
+ stack.RegisterLifecycle(lending)
+
return lending
}
diff --git a/XDCxlending/order_processor_test.go b/XDCxlending/order_processor_test.go
index 2cd16b65db..c5aecb0d8c 100644
--- a/XDCxlending/order_processor_test.go
+++ b/XDCxlending/order_processor_test.go
@@ -11,6 +11,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types"
+ "github.com/XinFinOrg/XDPoSChain/node"
)
func Test_getCancelFeeV1(t *testing.T) {
@@ -95,7 +96,12 @@ func Test_getCancelFeeV1(t *testing.T) {
}
func Test_getCancelFee(t *testing.T) {
- XDCx := XDCx.New(&XDCx.DefaultConfig)
+ stack, err := node.New(&node.DefaultConfig)
+ if err != nil {
+ t.Fatalf("could not create new node: %v", err)
+ }
+ XDCx := XDCx.New(stack, &XDCx.DefaultConfig)
+ defer stack.Close()
db := rawdb.NewMemoryDatabase()
stateCache := tradingstate.NewDatabase(db)
tradingStateDb, _ := tradingstate.New(types.EmptyRootHash, stateCache)
@@ -113,7 +119,7 @@ func Test_getCancelFee(t *testing.T) {
// set tokenBPrice = 1 XDC
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenB, common.XDCNativeAddressBinary), common.BasePrice)
- l := New(XDCx)
+ l := New(stack, XDCx)
type CancelFeeArg struct {
borrowFeeRate *big.Int
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 3f73d01cf2..1b1ccfd46f 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -30,6 +30,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/XDCx"
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
"github.com/XinFinOrg/XDPoSChain/accounts"
+ "github.com/XinFinOrg/XDPoSChain/node"
+
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
@@ -49,6 +51,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/eth/filters"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event"
+ "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
@@ -117,8 +120,16 @@ func NewXDCSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64, chainConf
var DefaultConfig = XDCx.Config{
DataDir: "",
}
- XDCXServ := XDCx.New(&DefaultConfig)
- lendingServ := XDCxlending.New(XDCXServ)
+ stack, err := node.New(&node.Config{DataDir: ""})
+ if err != nil {
+ log.Error("Could not create new node: %v", err)
+ return nil
+ }
+ defer stack.Close()
+
+ XDCXServ := XDCx.New(stack, &DefaultConfig)
+
+ lendingServ := XDCxlending.New(stack, XDCXServ)
consensus.GetXDCXService = func() utils.TradingService {
return XDCXServ
diff --git a/cmd/XDC/chaincmd.go b/cmd/XDC/chaincmd.go
index 9f49aac759..0566f0faed 100644
--- a/cmd/XDC/chaincmd.go
+++ b/cmd/XDC/chaincmd.go
@@ -174,7 +174,7 @@ func initGenesis(ctx *cli.Context) error {
}
// Open an initialise both full and light databases
- stack, _ := makeFullNode(ctx)
+ stack, _ := makeConfigNode(ctx)
defer stack.Close()
name := "chaindata"
@@ -195,7 +195,7 @@ func importChain(ctx *cli.Context) error {
if ctx.Args().Len() < 1 {
utils.Fatalf("This command requires an argument.")
}
- stack, cfg := makeFullNode(ctx)
+ stack, _, cfg := makeFullNode(ctx)
defer stack.Close()
// Start metrics export if enabled
@@ -273,7 +273,7 @@ func exportChain(ctx *cli.Context) error {
utils.Fatalf("This command requires an argument.")
}
- stack, _ := makeFullNode(ctx)
+ stack, _, _ := makeFullNode(ctx)
defer stack.Close()
chain, db := utils.MakeChain(ctx, stack, true)
@@ -310,7 +310,7 @@ func importPreimages(ctx *cli.Context) error {
utils.Fatalf("This command requires an argument.")
}
- stack, _ := makeFullNode(ctx)
+ stack, _, _ := makeFullNode(ctx)
defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, false)
@@ -329,7 +329,7 @@ func exportPreimages(ctx *cli.Context) error {
if ctx.Args().Len() < 1 {
utils.Fatalf("This command requires an argument.")
}
- stack, _ := makeFullNode(ctx)
+ stack, _, _ := makeFullNode(ctx)
defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, true)
@@ -344,7 +344,7 @@ func exportPreimages(ctx *cli.Context) error {
}
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/XDC/config.go b/cmd/XDC/config.go
index 307ecc1592..8d812165bd 100644
--- a/cmd/XDC/config.go
+++ b/cmd/XDC/config.go
@@ -24,6 +24,7 @@ import (
"math/big"
"os"
"reflect"
+ "runtime"
"slices"
"strings"
"unicode"
@@ -35,6 +36,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/eth/ethconfig"
+ "github.com/XinFinOrg/XDPoSChain/internal/ethapi"
"github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/metrics"
@@ -216,7 +218,7 @@ func applyValues(values []string, params *[]string) {
}
-func makeFullNode(ctx *cli.Context) (*node.Node, XDCConfig) {
+func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend, XDCConfig) {
stack, cfg := makeConfigNode(ctx)
// Start metrics export if enabled
@@ -224,15 +226,29 @@ func makeFullNode(ctx *cli.Context) (*node.Node, XDCConfig) {
// Register XDCX's OrderBook service if requested.
// enable in default
- utils.RegisterXDCXService(stack, &cfg.XDCX)
- utils.RegisterEthService(stack, &cfg.Eth, cfg.Node.Version)
+ XDCXServ, lendingServ := utils.RegisterXDCXService(stack, &cfg.XDCX)
+ backend, eth := utils.RegisterEthService(stack, &cfg.Eth, XDCXServ, lendingServ)
+
+ // Create gauge with geth system and build information
+ if eth != nil { // The 'eth' backend may be nil in light mode
+ var protos []string
+ for _, p := range eth.Protocols() {
+ protos = append(protos, fmt.Sprintf("%v/%d", p.Name, p.Version))
+ }
+ metrics.NewRegisteredGaugeInfo("xdc/info", nil).Update(metrics.GaugeInfoValue{
+ "arch": runtime.GOARCH,
+ "os": runtime.GOOS,
+ "version": cfg.Node.Version,
+ "eth_protocols": strings.Join(protos, ","),
+ })
+ }
// 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, cfg
+ return stack, backend, cfg
}
// dumpConfig is the dumpconfig command.
diff --git a/cmd/XDC/consolecmd.go b/cmd/XDC/consolecmd.go
index fb5b7de77d..30f9295b58 100644
--- a/cmd/XDC/consolecmd.go
+++ b/cmd/XDC/consolecmd.go
@@ -75,12 +75,12 @@ JavaScript API. See https://github.com/XinFinOrg/XDPoSChain/wiki/JavaScript-Cons
// same time.
func localConsole(ctx *cli.Context) error {
// Create and start the node based on the CLI flags
- node, cfg := makeFullNode(ctx)
- startNode(ctx, node, cfg)
- defer node.Close()
+ stack, backend, cfg := makeFullNode(ctx)
+ startNode(ctx, stack, backend, cfg)
+ defer stack.Close()
// Attach to the newly started node and start the JavaScript console
- client, err := node.Attach()
+ client, err := stack.Attach()
if err != nil {
utils.Fatalf("Failed to attach to the inproc XDC: %v", err)
}
@@ -180,12 +180,12 @@ 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
- node, cfg := makeFullNode(ctx)
- startNode(ctx, node, cfg)
- defer node.Close()
+ stack, backend, cfg := makeFullNode(ctx)
+ startNode(ctx, stack, backend, cfg)
+ defer stack.Close()
// Attach to the newly started node and start the JavaScript console
- client, err := node.Attach()
+ client, err := stack.Attach()
if err != nil {
utils.Fatalf("Failed to attach to the inproc XDC: %v", err)
}
diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go
index 082deaac7d..1f63a9d007 100644
--- a/cmd/XDC/main.go
+++ b/cmd/XDC/main.go
@@ -34,6 +34,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/eth"
"github.com/XinFinOrg/XDPoSChain/ethclient"
"github.com/XinFinOrg/XDPoSChain/internal/debug"
+ "github.com/XinFinOrg/XDPoSChain/internal/ethapi"
"github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/metrics"
@@ -249,17 +250,17 @@ func main() {
// It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down.
func XDC(ctx *cli.Context) error {
- node, cfg := makeFullNode(ctx)
- defer node.Close()
- startNode(ctx, node, cfg)
- node.Wait()
+ stack, backend, cfg := makeFullNode(ctx)
+ defer stack.Close()
+ startNode(ctx, stack, backend, cfg)
+ stack.Wait()
return nil
}
// 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, cfg XDCConfig) {
+func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, cfg XDCConfig) {
// Start up the node itself
utils.StartNode(stack)
@@ -324,17 +325,17 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
}()
// Start auxiliary services if enabled
- var ethereum *eth.Ethereum
- if err := stack.Service(ðereum); err != nil {
- utils.Fatalf("Ethereum service not running: %v", err)
+ ethBackend, ok := backend.(*eth.EthAPIBackend)
+ if !ok {
+ utils.Fatalf("Ethereum service not running")
}
- if engine, ok := ethereum.Engine().(*XDPoS.XDPoS); ok {
+ if engine, ok := ethBackend.Engine().(*XDPoS.XDPoS); ok {
go func() {
started := false
ok := false
slaveMode := ctx.IsSet(utils.XDCSlaveModeFlag.Name)
var err error
- ok, err = ethereum.ValidateMasternode()
+ ok, err = ethBackend.ValidateMasternode()
if err != nil {
utils.Fatalf("Can't verify masternode permission: %v", err)
}
@@ -349,13 +350,13 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
type threaded interface {
SetThreads(threads int)
}
- if th, ok := ethereum.Engine().(threaded); ok {
+ if th, ok := ethBackend.Engine().(threaded); ok {
th.SetThreads(threads)
}
}
// Set the gas price to the limits from the CLI and start mining
- ethereum.TxPool().SetGasPrice(cfg.Eth.GasPrice)
- if err := ethereum.StartStaking(true); err != nil {
+ ethBackend.TxPool().SetGasPrice(cfg.Eth.GasPrice)
+ if err := ethBackend.StartStaking(true); err != nil {
utils.Fatalf("Failed to start staking: %v", err)
}
started = true
@@ -366,17 +367,17 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
for range core.CheckpointCh {
log.Info("Checkpoint!!! It's time to reconcile node's state...")
log.Info("Update consensus parameters")
- chain := ethereum.BlockChain()
+ chain := ethBackend.BlockChain()
engine.UpdateParams(chain.CurrentHeader())
- ok, err = ethereum.ValidateMasternode()
+ ok, err = ethBackend.ValidateMasternode()
if err != nil {
utils.Fatalf("Can't verify masternode permission: %v", err)
}
if !ok {
if started {
log.Info("Only masternode can propose and verify blocks. Cancelling staking on this node...")
- ethereum.StopStaking()
+ ethBackend.StopStaking()
started = false
log.Info("Cancelled mining mode!!!")
}
@@ -391,13 +392,13 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
type threaded interface {
SetThreads(threads int)
}
- if th, ok := ethereum.Engine().(threaded); ok {
+ if th, ok := ethBackend.Engine().(threaded); ok {
th.SetThreads(threads)
}
}
// Set the gas price to the limits from the CLI and start mining
- ethereum.TxPool().SetGasPrice(cfg.Eth.GasPrice)
- if err := ethereum.StartStaking(true); err != nil {
+ ethBackend.TxPool().SetGasPrice(cfg.Eth.GasPrice)
+ if err := ethBackend.StartStaking(true); err != nil {
utils.Fatalf("Failed to start staking: %v", err)
}
started = true
diff --git a/cmd/p2psim/main.go b/cmd/p2psim/main.go
index 6536d6657d..406f747434 100644
--- a/cmd/p2psim/main.go
+++ b/cmd/p2psim/main.go
@@ -309,7 +309,7 @@ func createNode(ctx *cli.Context) error {
config.PrivateKey = privKey
}
if services := ctx.String("services"); services != "" {
- config.Services = strings.Split(services, ",")
+ config.Lifecycles = strings.Split(services, ",")
}
node, err := client.CreateNode(config)
if err != nil {
diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go
index dabe53a05c..108a4baffc 100644
--- a/cmd/utils/cmd.go
+++ b/cmd/utils/cmd.go
@@ -74,7 +74,7 @@ func StartNode(stack *node.Node) {
defer signal.Stop(sigc)
<-sigc
log.Info("Got interrupt, shutting down...")
- go stack.Stop()
+ go stack.Close()
for i := 10; i > 0; i-- {
<-sigc
if i > 1 {
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 353b39583b..04385353bb 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -32,6 +32,7 @@ import (
"time"
"github.com/XinFinOrg/XDPoSChain/XDCx"
+ "github.com/XinFinOrg/XDPoSChain/XDCxlending"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
"github.com/XinFinOrg/XDPoSChain/common"
@@ -43,10 +44,13 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
+ "github.com/XinFinOrg/XDPoSChain/eth"
+ "github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/ethconfig"
"github.com/XinFinOrg/XDPoSChain/eth/filters"
"github.com/XinFinOrg/XDPoSChain/eth/gasprice"
"github.com/XinFinOrg/XDPoSChain/ethdb"
+ "github.com/XinFinOrg/XDPoSChain/ethstats"
"github.com/XinFinOrg/XDPoSChain/internal/ethapi"
"github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/XinFinOrg/XDPoSChain/log"
@@ -1508,6 +1512,36 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
}
}
+// RegisterEthService adds an Ethereum client to the stack.
+func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, XDCXServ *XDCx.XDCX, lendingServ *XDCxlending.Lending) (ethapi.Backend, *eth.Ethereum) {
+ if cfg.SyncMode == downloader.LightSync {
+ Fatalf("can't register eth service in light sync mode, light mode has been deprecated")
+ return nil, nil
+ } else {
+ backend, err := eth.New(stack, cfg, XDCXServ, lendingServ)
+ if err != nil {
+ Fatalf("Failed to register the Ethereum service: %v", err)
+ }
+
+ return backend.ApiBackend, backend
+ }
+}
+
+// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to the node.
+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)
+ }
+}
+
+func RegisterXDCXService(stack *node.Node, cfg *XDCx.Config) (*XDCx.XDCX, *XDCxlending.Lending) {
+ // register XDCx service
+ XDCX := XDCx.New(stack, cfg)
+ // register XDCxlending service
+ lendingServ := XDCxlending.New(stack, XDCX)
+ return XDCX, lendingServ
+}
+
// SetupNetwork configures the system for either the main net or some test network.
func SetupNetwork(ctx *cli.Context) {
// TODO(fjl): move target gas limit into config
diff --git a/cmd/utils/utils.go b/cmd/utils/utils.go
deleted file mode 100644
index 72086552d7..0000000000
--- a/cmd/utils/utils.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package utils
-
-import (
- "errors"
- "fmt"
- "runtime"
- "strings"
-
- "github.com/XinFinOrg/XDPoSChain/XDCx"
- "github.com/XinFinOrg/XDPoSChain/XDCxlending"
- "github.com/XinFinOrg/XDPoSChain/eth"
- "github.com/XinFinOrg/XDPoSChain/eth/downloader"
- "github.com/XinFinOrg/XDPoSChain/eth/ethconfig"
- "github.com/XinFinOrg/XDPoSChain/ethstats"
- "github.com/XinFinOrg/XDPoSChain/metrics"
- "github.com/XinFinOrg/XDPoSChain/node"
-)
-
-// RegisterEthService adds an Ethereum client to the stack.
-func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, version string) {
- var err error
- if cfg.SyncMode == downloader.LightSync {
- err = errors.New("can't register eth service in light sync mode, light mode has been deprecated")
- } else {
- err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
- var XDCXServ *XDCx.XDCX
- ctx.Service(&XDCXServ)
- var lendingServ *XDCxlending.Lending
- ctx.Service(&lendingServ)
- fullNode, err := eth.New(ctx, cfg, XDCXServ, lendingServ)
- if err != nil {
- return nil, err
- }
-
- // TODO: move the following code to function makeFullNode
- // Ref: #21105, #22641, #23761, #24877
- // Create gauge with geth system and build information
- var protos []string
- for _, p := range fullNode.Protocols() {
- protos = append(protos, fmt.Sprintf("%v/%d", p.Name, p.Version))
- }
- metrics.NewRegisteredGaugeInfo("xdc/info", nil).Update(metrics.GaugeInfoValue{
- "arch": runtime.GOARCH,
- "os": runtime.GOOS,
- "version": version, // cfg.Node.Version
- "eth_protocols": strings.Join(protos, ","),
- })
-
- return fullNode, err
- })
- }
-
- if err != nil {
- Fatalf("Failed to register the Ethereum service: %v", err)
- }
-}
-
-// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to the node.
-func RegisterEthStatsService(stack *node.Node, url string) {
- if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
- var ethServ *eth.Ethereum
- ctx.Service(ðServ)
- return ethstats.New(url, ethServ)
- }); err != nil {
- Fatalf("Failed to register the Ethereum Stats service: %v", err)
- }
-}
-
-func RegisterXDCXService(stack *node.Node, cfg *XDCx.Config) {
- XDCX := XDCx.New(cfg)
- if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) {
- return XDCX, nil
- }); err != nil {
- Fatalf("Failed to register the XDCX service: %v", err)
- }
-
- // register XDCxlending service
- if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) {
- return XDCxlending.New(XDCX), nil
- }); err != nil {
- Fatalf("Failed to register the XDCXLending service: %v", err)
- }
-}
diff --git a/console/console_test.go b/console/console_test.go
index 2f5cfd5c54..acc710d222 100644
--- a/console/console_test.go
+++ b/console/console_test.go
@@ -104,9 +104,8 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
if confOverride != nil {
confOverride(ethConf)
}
- if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
- return eth.New(ctx, ethConf, &XDCx.XDCX{}, &XDCxlending.Lending{})
- }); err != nil {
+ ethBackend, err := eth.New(stack, ethConf, &XDCx.XDCX{}, &XDCxlending.Lending{})
+ if err != nil {
t.Fatalf("failed to register Ethereum protocol: %v", err)
}
// Start the node and assemble the JavaScript console around it
@@ -132,13 +131,11 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
t.Fatalf("failed to create JavaScript console: %v", err)
}
// Create the final tester and return
- var ethereum *eth.Ethereum
- stack.Service(ðereum)
return &tester{
workspace: workspace,
stack: stack,
- ethereum: ethereum,
+ ethereum: ethBackend,
console: console,
input: prompter,
output: printer,
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 03d3a66ba2..f549ddafd6 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -38,6 +38,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/bloombits"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state"
+ "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
@@ -46,31 +47,32 @@ import (
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log"
+ "github.com/XinFinOrg/XDPoSChain/miner"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
-// EthApiBackend implements ethapi.Backend for full nodes
-type EthApiBackend struct {
+// EthAPIBackend implements ethapi.Backend for full nodes
+type EthAPIBackend struct {
eth *Ethereum
gpo *gasprice.Oracle
XDPoS *XDPoS.XDPoS
}
-func (b *EthApiBackend) ChainConfig() *params.ChainConfig {
+func (b *EthAPIBackend) ChainConfig() *params.ChainConfig {
return b.eth.chainConfig
}
-func (b *EthApiBackend) CurrentBlock() *types.Block {
+func (b *EthAPIBackend) CurrentBlock() *types.Block {
return b.eth.blockchain.CurrentBlock()
}
-func (b *EthApiBackend) SetHead(number uint64) {
+func (b *EthAPIBackend) SetHead(number uint64) {
b.eth.protocolManager.downloader.Cancel()
b.eth.blockchain.SetHead(number)
}
-func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) {
+func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) {
// Pending block is only known by the miner
if blockNr == rpc.PendingBlockNumber {
blockNr = rpc.LatestBlockNumber
@@ -103,7 +105,7 @@ func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNum
return header, nil
}
-func (b *EthApiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
+func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.HeaderByNumber(ctx, blockNr)
}
@@ -120,11 +122,11 @@ func (b *EthApiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
-func (b *EthApiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
+func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
return b.eth.blockchain.GetHeaderByHash(hash), nil
}
-func (b *EthApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) {
+func (b *EthAPIBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) {
// Pending block is only known by the miner
if blockNr == rpc.PendingBlockNumber {
blockNr = rpc.LatestBlockNumber
@@ -153,12 +155,12 @@ func (b *EthApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumb
return b.eth.blockchain.GetBlockByNumber(uint64(blockNr)), nil
}
-func (b *EthApiBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
+func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
return b.eth.blockchain.GetBlockByHash(hash), nil
}
// GetBody returns body of a block. It does not resolve special block numbers.
-func (b *EthApiBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
+func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
if number < 0 || hash == (common.Hash{}) {
return nil, errors.New("invalid arguments; expect hash and no special block numbers")
}
@@ -168,7 +170,7 @@ func (b *EthApiBackend) GetBody(ctx context.Context, hash common.Hash, number rp
return nil, errors.New("block body not found")
}
-func (b *EthApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
+func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.BlockByNumber(ctx, blockNr)
}
@@ -189,11 +191,11 @@ func (b *EthApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
-func (b *EthApiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
+func (b *EthAPIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
return b.eth.miner.PendingBlockAndReceipts()
}
-func (b *EthApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
+func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
// Pending state is only known by the miner
if blockNr == rpc.PendingBlockNumber {
blockNr = rpc.LatestBlockNumber
@@ -210,7 +212,7 @@ func (b *EthApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.
return stateDb, header, err
}
-func (b *EthApiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
+func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.StateAndHeaderByNumber(ctx, blockNr)
}
@@ -234,23 +236,23 @@ func (b *EthApiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
}
-func (b *EthApiBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) {
+func (b *EthAPIBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) {
return b.eth.blockchain.GetBlockByHash(blockHash), nil
}
-func (b *EthApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
+func (b *EthAPIBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) {
return b.eth.blockchain.GetReceiptsByHash(blockHash), nil
}
-func (b *EthApiBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
+func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil
}
-func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int {
- return b.eth.blockchain.GetTdByHash(blockHash)
+func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
+ return b.eth.blockchain.GetTdByHash(hash)
}
-func (b *EthApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
+func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
vmError := func() error { return nil }
if vmConfig == nil {
vmConfig = b.eth.blockchain.GetVMConfig()
@@ -261,45 +263,45 @@ func (b *EthApiBackend) GetEVM(ctx context.Context, msg core.Message, state *sta
return vm.NewEVM(context, txContext, state, XDCxState, b.eth.chainConfig, *vmConfig), vmError, nil
}
-func (b *EthApiBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
+func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch)
}
-func (b *EthApiBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
+func (b *EthAPIBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
return b.eth.miner.SubscribePendingLogs(ch)
}
-func (b *EthApiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
+func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
return b.eth.BlockChain().SubscribeChainEvent(ch)
}
-func (b *EthApiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
+func (b *EthAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
return b.eth.BlockChain().SubscribeChainHeadEvent(ch)
}
-func (b *EthApiBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
+func (b *EthAPIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
return b.eth.BlockChain().SubscribeChainSideEvent(ch)
}
-func (b *EthApiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
+func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
return b.eth.BlockChain().SubscribeLogsEvent(ch)
}
-func (b *EthApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
+func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
return b.eth.txPool.AddLocal(signedTx)
}
// SendOrderTx send order via backend
-func (b *EthApiBackend) SendOrderTx(ctx context.Context, signedTx *types.OrderTransaction) error {
+func (b *EthAPIBackend) SendOrderTx(ctx context.Context, signedTx *types.OrderTransaction) error {
return b.eth.orderPool.AddLocal(signedTx)
}
// SendLendingTx send order via backend
-func (b *EthApiBackend) SendLendingTx(ctx context.Context, signedTx *types.LendingTransaction) error {
+func (b *EthAPIBackend) SendLendingTx(ctx context.Context, signedTx *types.LendingTransaction) error {
return b.eth.lendingPool.AddLocal(signedTx)
}
-func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) {
+func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
pending := b.eth.txPool.Pending(false)
var txs types.Transactions
for _, batch := range pending {
@@ -308,89 +310,97 @@ func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) {
return txs, nil
}
-func (b *EthApiBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
+func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
return b.eth.txPool.Get(hash)
}
-func (b *EthApiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
+func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
return b.eth.txPool.Nonce(addr), nil
}
-func (b *EthApiBackend) Stats() (pending int, queued int) {
+func (b *EthAPIBackend) Stats() (pending int, queued int) {
return b.eth.txPool.Stats()
}
-func (b *EthApiBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
+func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
return b.eth.TxPool().Content()
}
-func (b *EthApiBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
+func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) {
return b.eth.TxPool().ContentFrom(addr)
}
-func (b *EthApiBackend) OrderTxPoolContent() (map[common.Address]types.OrderTransactions, map[common.Address]types.OrderTransactions) {
+func (b *EthAPIBackend) OrderTxPoolContent() (map[common.Address]types.OrderTransactions, map[common.Address]types.OrderTransactions) {
return b.eth.OrderPool().Content()
}
-func (b *EthApiBackend) OrderStats() (pending int, queued int) {
+func (b *EthAPIBackend) OrderStats() (pending int, queued int) {
return b.eth.txPool.Stats()
}
-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)
}
-func (b *EthApiBackend) Downloader() *downloader.Downloader {
+func (b *EthAPIBackend) Downloader() *downloader.Downloader {
return b.eth.Downloader()
}
-func (b *EthApiBackend) ProtocolVersion() int {
+func (b *EthAPIBackend) ProtocolVersion() int {
return b.eth.EthVersion()
}
-func (b *EthApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
+func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestTipCap(ctx)
}
-func (b *EthApiBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
+func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
}
-func (b *EthApiBackend) BlobBaseFee(ctx context.Context) *big.Int {
+func (b *EthAPIBackend) BlobBaseFee(ctx context.Context) *big.Int {
return new(big.Int)
}
-func (b *EthApiBackend) ChainDb() ethdb.Database {
+func (b *EthAPIBackend) ChainDb() ethdb.Database {
return b.eth.ChainDb()
}
-func (b *EthApiBackend) EventMux() *event.TypeMux {
+func (b *EthAPIBackend) EventMux() *event.TypeMux {
return b.eth.EventMux()
}
-func (b *EthApiBackend) RPCGasCap() uint64 {
+func (b *EthAPIBackend) RPCGasCap() uint64 {
return b.eth.config.RPCGasCap
}
-func (b *EthApiBackend) AccountManager() *accounts.Manager {
+func (b *EthAPIBackend) AccountManager() *accounts.Manager {
return b.eth.AccountManager()
}
-func (b *EthApiBackend) RPCTxFeeCap() float64 {
+func (b *EthAPIBackend) RPCTxFeeCap() float64 {
return b.eth.config.RPCTxFeeCap
}
-func (b *EthApiBackend) BloomStatus() (uint64, uint64) {
+func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
sections, _, _ := b.eth.bloomIndexer.Sections()
return params.BloomBitsBlocks, sections
}
-func (b *EthApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
+func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
for i := 0; i < bloomFilterThreads; i++ {
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
}
}
-func (b *EthApiBackend) GetIPCClient() (bind.ContractBackend, error) {
+func (b *EthAPIBackend) Engine() consensus.Engine {
+ return b.eth.engine
+}
+
+func (b *EthAPIBackend) Miner() *miner.Miner {
+ return b.eth.Miner()
+}
+
+func (b *EthAPIBackend) GetIPCClient() (bind.ContractBackend, error) {
// func (b *EthApiBackend) GetIPCClient() (*ethclient.Client, error) {
client, err := b.eth.blockchain.GetClient()
if err != nil {
@@ -400,19 +410,15 @@ func (b *EthApiBackend) GetIPCClient() (bind.ContractBackend, error) {
return client, nil
}
-func (b *EthApiBackend) GetEngine() consensus.Engine {
- return b.eth.engine
-}
-
-func (b *EthApiBackend) CurrentHeader() *types.Header {
+func (b *EthAPIBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader()
}
-func (b *EthApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
+func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
return b.eth.stateAtBlock(block, reexec, base, checkLive)
}
-func (s *EthApiBackend) GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int {
+func (s *EthAPIBackend) GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int {
header := s.eth.blockchain.GetHeaderByHash(hash)
if header != nil {
data, err := os.ReadFile(filepath.Join(common.StoreRewardFolder, header.Number.String()+"."+header.Hash().Hex()))
@@ -444,11 +450,11 @@ func (s *EthApiBackend) GetRewardByHash(hash common.Hash) map[string]map[string]
// 2. Get list signers + reward at that checkpoint
// 3. Find out the list signers_reward for input masternode's reward
// 4. Calculate voters's rewards for input masternode
-func (b *EthApiBackend) GetVotersRewards(masternodeAddr common.Address) map[common.Address]*big.Int {
+func (b *EthAPIBackend) GetVotersRewards(masternodeAddr common.Address) map[common.Address]*big.Int {
chain := b.eth.blockchain
block := chain.CurrentBlock()
number := block.Number().Uint64()
- engine := b.GetEngine().(*XDPoS.XDPoS)
+ engine := b.Engine().(*XDPoS.XDPoS)
foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr
// calculate for 2 epochs ago
@@ -524,7 +530,7 @@ func (b *EthApiBackend) GetVotersRewards(masternodeAddr common.Address) map[comm
}
// GetVotersCap return all voters's capability at a checkpoint
-func (b *EthApiBackend) GetVotersCap(checkpoint *big.Int, masterAddr common.Address, voters []common.Address) map[common.Address]*big.Int {
+func (b *EthAPIBackend) GetVotersCap(checkpoint *big.Int, masterAddr common.Address, voters []common.Address) map[common.Address]*big.Int {
chain := b.eth.blockchain
checkpointBlock := chain.GetBlockByNumber(checkpoint.Uint64())
statedb, err := chain.StateAt(checkpointBlock.Root())
@@ -548,7 +554,7 @@ func (b *EthApiBackend) GetVotersCap(checkpoint *big.Int, masterAddr common.Addr
// GetEpochDuration return latest generating velocity epoch by minute
// ie 30min for each epoch
-func (b *EthApiBackend) GetEpochDuration() *big.Int {
+func (b *EthAPIBackend) GetEpochDuration() *big.Int {
chain := b.eth.blockchain
block := chain.CurrentBlock()
number := block.Number().Uint64()
@@ -561,7 +567,7 @@ func (b *EthApiBackend) GetEpochDuration() *big.Int {
}
// GetMasternodesCap return a cap of all masternode at a checkpoint
-func (b *EthApiBackend) GetMasternodesCap(checkpoint uint64) map[common.Address]*big.Int {
+func (b *EthAPIBackend) GetMasternodesCap(checkpoint uint64) map[common.Address]*big.Int {
checkpointBlock := b.eth.blockchain.GetBlockByNumber(checkpoint)
statedb, err := b.eth.blockchain.StateAt(checkpointBlock.Root())
if err != nil {
@@ -583,19 +589,19 @@ func (b *EthApiBackend) GetMasternodesCap(checkpoint uint64) map[common.Address]
return masternodesCap
}
-func (b *EthApiBackend) GetBlocksHashCache(blockNr uint64) []common.Hash {
+func (b *EthAPIBackend) GetBlocksHashCache(blockNr uint64) []common.Hash {
return b.eth.blockchain.GetBlocksHashCache(blockNr)
}
-func (b *EthApiBackend) AreTwoBlockSamePath(bh1 common.Hash, bh2 common.Hash) bool {
+func (b *EthAPIBackend) AreTwoBlockSamePath(bh1 common.Hash, bh2 common.Hash) bool {
return b.eth.blockchain.AreTwoBlockSamePath(bh1, bh2)
}
// GetOrderNonce get order nonce
-func (b *EthApiBackend) GetOrderNonce(address common.Hash) (uint64, error) {
+func (b *EthAPIBackend) GetOrderNonce(address common.Hash) (uint64, error) {
XDCxService := b.eth.GetXDCX()
if XDCxService != nil {
- author, err := b.GetEngine().Author(b.CurrentBlock().Header())
+ author, err := b.Engine().Author(b.CurrentBlock().Header())
if err != nil {
return 0, err
}
@@ -608,10 +614,39 @@ func (b *EthApiBackend) GetOrderNonce(address common.Hash) (uint64, error) {
return 0, errors.New("cannot find XDCx service")
}
-func (b *EthApiBackend) XDCxService() *XDCx.XDCX {
+func (b *EthAPIBackend) XDCxService() *XDCx.XDCX {
return b.eth.XDCX
}
-func (b *EthApiBackend) LendingService() *XDCxlending.Lending {
+func (b *EthAPIBackend) LendingService() *XDCxlending.Lending {
return b.eth.Lending
}
+
+func (b *EthAPIBackend) GetPeer() int {
+ return b.eth.protocolManager.peers.Len()
+}
+
+// ValidateMasternode checks if node's address is in set of masternodes
+func (b *EthAPIBackend) ValidateMasternode() (bool, error) {
+ return b.eth.ValidateMasternode()
+}
+
+func (b *EthAPIBackend) StartStaking(local bool) error {
+ return b.eth.StartStaking(local)
+}
+
+func (b *EthAPIBackend) StopStaking() {
+ b.eth.StopStaking()
+}
+
+func (b *EthAPIBackend) IsStaking() bool {
+ return b.eth.IsStaking()
+}
+
+func (b *EthAPIBackend) BlockChain() *core.BlockChain {
+ return b.eth.blockchain
+}
+
+func (b *EthAPIBackend) TxPool() *txpool.TxPool {
+ return b.eth.txPool
+}
diff --git a/eth/backend.go b/eth/backend.go
index c6690e78eb..da8ec18b4b 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -59,13 +59,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/rpc"
)
-type LesServer interface {
- Start(srvr *p2p.Server)
- Stop()
- Protocols() []p2p.Protocol
- SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
-}
-
// Ethereum implements the Ethereum full node service.
type Ethereum struct {
config *ethconfig.Config
@@ -80,7 +73,6 @@ type Ethereum struct {
lendingPool *txpool.LendingPool
blockchain *core.BlockChain
protocolManager *ProtocolManager
- lesServer LesServer
// DB interfaces
chainDb ethdb.Database // Block chain database
@@ -92,7 +84,7 @@ type Ethereum struct {
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
- ApiBackend *EthApiBackend
+ ApiBackend *EthAPIBackend
miner *miner.Miner
gasPrice *big.Int
@@ -101,19 +93,16 @@ type Ethereum struct {
networkId uint64
netRPCService *ethapi.PublicNetAPI
+ p2pServer *p2p.Server
+
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
XDCX *XDCx.XDCX
Lending *XDCxlending.Lending
}
-func (e *Ethereum) AddLesServer(ls LesServer) {
- e.lesServer = ls
- ls.SetBloomBitsIndexer(e.bloomIndexer)
-}
-
// New creates a new Ethereum object (including the
// initialisation of the common Ethereum object)
-func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX, lendingServ *XDCxlending.Lending) (*Ethereum, error) {
+func New(stack *node.Node, config *ethconfig.Config, XDCXServ *XDCx.XDCX, lendingServ *XDCxlending.Lending) (*Ethereum, error) {
if config.SyncMode == downloader.LightSync {
return nil, errors.New("can't run eth.Ethereum in light sync mode, light mode has been deprecated")
}
@@ -122,7 +111,7 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
}
// Assemble the Ethereum object
- chainDb, err := ctx.OpenDatabase("chaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false)
+ chainDb, err := stack.OpenDatabase("chaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false)
if err != nil {
return nil, err
}
@@ -147,15 +136,16 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
config: config,
chainDb: chainDb,
chainConfig: chainConfig,
- eventMux: ctx.EventMux,
- accountManager: ctx.AccountManager,
- engine: CreateConsensusEngine(ctx, &config.Ethash, chainConfig, chainDb),
+ eventMux: stack.EventMux(),
+ accountManager: stack.AccountManager(),
+ engine: CreateConsensusEngine(stack, &config.Ethash, chainConfig, chainDb),
shutdownChan: make(chan bool),
networkId: networkID,
gasPrice: config.GasPrice,
etherbase: config.Etherbase,
bloomRequests: make(chan chan *bloombits.Retrieval),
bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
+ p2pServer: stack.Server(),
}
// Inject XDCX Service into main Eth Service.
if XDCXServ != nil {
@@ -237,7 +227,7 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
eth.bloomIndexer.Start(eth.blockchain)
if config.TxPool.Journal != "" {
- config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
+ config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
}
eth.txPool = txpool.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain)
eth.orderPool = txpool.NewOrderPool(eth.chainConfig, eth.blockchain)
@@ -246,18 +236,18 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
if eth.protocolManager, err = NewProtocolManagerEx(eth.chainConfig, config.SyncMode, networkID, eth.eventMux, eth.txPool, eth.orderPool, eth.lendingPool, eth.engine, eth.blockchain, chainDb); err != nil {
return nil, err
}
- eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine, ctx.GetConfig().AnnounceTxs)
+ eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine, stack.Config().AnnounceTxs)
eth.miner.SetExtra(makeExtraData(config.ExtraData))
if eth.chainConfig.XDPoS != nil {
- eth.ApiBackend = &EthApiBackend{eth, nil, eth.engine.(*XDPoS.XDPoS)}
+ eth.ApiBackend = &EthAPIBackend{eth, nil, eth.engine.(*XDPoS.XDPoS)}
} else {
- eth.ApiBackend = &EthApiBackend{eth, nil, nil}
+ eth.ApiBackend = &EthAPIBackend{eth, nil, nil}
}
eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, config.GPO, config.GasPrice)
// Set global ipc endpoint.
- eth.blockchain.IPCEndpoint = ctx.GetConfig().IPCEndpoint()
+ eth.blockchain.IPCEndpoint = stack.IPCEndpoint()
if eth.chainConfig.XDPoS != nil {
c := eth.engine.(*XDPoS.XDPoS)
@@ -334,6 +324,13 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
}
}
+ // 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
}
@@ -355,7 +352,7 @@ func makeExtraData(extra []byte) []byte {
}
// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service
-func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chainConfig *params.ChainConfig, db ethdb.Database) consensus.Engine {
+func CreateConsensusEngine(stack *node.Node, config *ethash.Config, chainConfig *params.ChainConfig, db ethdb.Database) consensus.Engine {
// If delegated-proof-of-stake is requested, set it up
if chainConfig.XDPoS != nil {
return XDPoS.New(chainConfig, db)
@@ -374,7 +371,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chai
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,
DatasetDir: config.DatasetDir,
@@ -542,50 +539,39 @@ func (e *Ethereum) IsListening() bool { return true } // Always
func (e *Ethereum) EthVersion() int { return int(e.protocolManager.SubProtocols[0].Version) }
func (e *Ethereum) NetVersion() uint64 { return e.networkId }
func (e *Ethereum) Downloader() *downloader.Downloader { return e.protocolManager.downloader }
+func (e *Ethereum) BloomIndexer() *core.ChainIndexer { return e.bloomIndexer }
-// Protocols implements node.Service, returning all the currently configured
-// network protocols to start.
+// Protocols returns all the currently configured
func (e *Ethereum) Protocols() []p2p.Protocol {
- if e.lesServer == nil {
- return e.protocolManager.SubProtocols
- }
- return append(e.protocolManager.SubProtocols, e.lesServer.Protocols()...)
+ return e.protocolManager.SubProtocols
}
-// 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.
-func (e *Ethereum) Start(srvr *p2p.Server) error {
+func (e *Ethereum) Start() error {
// Start the bloom bits servicing goroutines
e.startBloomHandlers(params.BloomBitsBlocks)
- // Start the RPC service
- e.netRPCService = ethapi.NewPublicNetAPI(srvr, e.NetVersion())
-
// Figure out a max peers count based on the server limits
- maxPeers := srvr.MaxPeers
+ maxPeers := e.p2pServer.MaxPeers
if e.config.LightServ > 0 {
- if e.config.LightPeers >= srvr.MaxPeers {
- return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", e.config.LightPeers, srvr.MaxPeers)
+ if e.config.LightPeers >= e.p2pServer.MaxPeers {
+ return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", e.config.LightPeers, e.p2pServer.MaxPeers)
}
maxPeers -= e.config.LightPeers
}
// Start the networking layer and the light server if requested
e.protocolManager.Start(maxPeers)
- if e.lesServer != nil {
- e.lesServer.Start(srvr)
- }
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.
func (e *Ethereum) Stop() error {
e.bloomIndexer.Close()
e.blockchain.Stop()
e.protocolManager.Stop()
- if e.lesServer != nil {
- e.lesServer.Stop()
- }
+
e.txPool.Stop()
e.miner.Stop()
e.eventMux.Stop()
diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go
index 5789b94617..4f3af91e1d 100644
--- a/ethstats/ethstats.go
+++ b/ethstats/ethstats.go
@@ -38,8 +38,11 @@ import (
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth"
+ "github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log"
+ "github.com/XinFinOrg/XDPoSChain/miner"
+ "github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/gorilla/websocket"
@@ -63,22 +66,35 @@ type consensusEngine interface {
SubscribeForensicsEvent(chan<- types.ForensicsEvent) event.Subscription
}
-type txPool interface {
- // SubscribeNewTxsEvent should return an event subscription of
- // NewTxsEvent and send events to the given channel.
- SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
+// backend encompasses the bare-minimum functionality needed for ethstats reporting
+type backend interface {
+ SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
+ SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription
+ CurrentHeader() *types.Header
+ HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
+ GetTd(ctx context.Context, hash common.Hash) *big.Int
+ Stats() (pending int, queued int)
+ Downloader() *downloader.Downloader
+ Engine() consensus.Engine
+ SuggestGasTipCap(ctx context.Context) (*big.Int, error)
}
-type blockChain interface {
- SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
+// fullNodeBackend encompasses the functionality necessary for a full node
+// reporting to ethstats
+type fullNodeBackend interface {
+ backend
+ Miner() *miner.Miner
+ BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
+ CurrentBlock() *types.Block
+ SuggestPrice(ctx context.Context) (*big.Int, error)
}
// 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
- 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
@@ -135,47 +151,37 @@ func (w *connWrapper) Close() error {
}
// New returns a monitoring service ready for stats reporting.
-func New(url string, ethServ *eth.Ethereum) (*Service, 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)
if len(parts) != 5 {
- return nil, fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url)
+ return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url)
}
- // Assemble and return the stats service
- var engine consensus.Engine
- if ethServ != nil {
- engine = ethServ.Engine()
+ ethstats := &Service{
+ 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),
}
- return &Service{
- eth: ethServ,
- engine: engine,
- node: parts[1],
- pass: parts[3],
- host: parts[4],
- pongCh: make(chan struct{}),
- histCh: make(chan []uint64, 1),
- }, nil
+
+ node.RegisterLifecycle(ethstats)
+ return nil
}
-// Protocols implements node.Service, returning the P2P network protocols used
-// by the stats service (nil as it doesn't use the devp2p overlay network).
-func (s *Service) Protocols() []p2p.Protocol { return nil }
-
-// APIs implements node.Service, returning the RPC API endpoints provided by the
-// stats service (nil as it doesn't provide any user callable APIs).
-func (s *Service) APIs() []rpc.API { return nil }
-
-// Start implements node.Service, starting up the monitoring and reporting daemon.
-func (s *Service) Start(server *p2p.Server) error {
- s.server = server
+// Start implements node.Lifecycle, starting up the monitoring and reporting daemon.
+func (s *Service) Start() error {
go s.loop()
log.Info("Stats daemon started")
return nil
}
-// Stop implements node.Service, terminating the monitoring and reporting daemon.
+// Stop implements node.Lifecycle, terminating the monitoring and reporting daemon.
func (s *Service) Stop() error {
log.Info("Stats daemon stopped")
return nil
@@ -185,26 +191,17 @@ 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
- var engine consensusEngine
- if s.eth != nil {
- blockchain = s.eth.BlockChain()
- txpool = s.eth.TxPool()
- engine = s.eth.Engine().(*XDPoS.XDPoS)
- }
-
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()
// Forensics events
forensicsEventCh := make(chan types.ForensicsEvent)
- if engine != nil {
+ if engine, ok := s.engine.(consensusEngine); ok && engine != nil {
forensicsSub := engine.SubscribeForensicsEvent(forensicsEventCh)
defer forensicsSub.Unsubscribe()
}
@@ -640,19 +637,30 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
txs []txStats
uncles []*types.Header
)
- if s.eth != nil {
- // Full nodes have all needed information available
+
+ // check if backend is a full node
+ fullBackend, ok := s.backend.(fullNodeBackend)
+ if ok {
if block == nil {
- block = s.eth.BlockChain().CurrentBlock()
+ block = fullBackend.CurrentBlock()
}
header = block.Header()
- td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
+ td = fullBackend.GetTd(context.Background(), header.Hash())
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.backend.CurrentHeader()
+ }
+ td = s.backend.GetTd(context.Background(), header.Hash())
+ txs = []txStats{}
}
// Assemble and return the block stats
author, err := s.engine.Author(header)
@@ -687,10 +695,7 @@ func (s *Service) reportHistory(conn *connWrapper, 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()
- }
+ head := s.backend.CurrentHeader().Number.Int64()
start := head - historyUpdateRange + 1
if start < 0 {
start = 0
@@ -702,10 +707,15 @@ func (s *Service) reportHistory(conn *connWrapper, list []uint64) error {
// Gather the batch of blocks to report
history := make([]*blockStats, len(indexes))
for i, number := range indexes {
+ fullBackend, ok := s.backend.(fullNodeBackend)
// Retrieve the next block if it's known to us
var block *types.Block
- if s.eth != nil {
- block = s.eth.BlockChain().GetBlockByNumber(number)
+ if ok {
+ block, _ = fullBackend.BlockByNumber(context.Background(), rpc.BlockNumber(number)) // TODO ignore error here ?
+ } else {
+ if header, _ := s.backend.HeaderByNumber(context.Background(), rpc.BlockNumber(number)); header != nil {
+ block = types.NewBlockWithHeader(header)
+ }
}
// If we do have the block, add to the history and continue
if block != nil {
@@ -741,10 +751,7 @@ type pendStats struct {
// it to the stats server.
func (s *Service) reportPending(conn *connWrapper) error {
// Retrieve the pending count from the local blockchain
- var pending int
- if s.eth != nil {
- pending, _ = s.eth.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)
@@ -771,7 +778,7 @@ type nodeStats struct {
Uptime int `json:"uptime"`
}
-// reportPending retrieves various stats about the node at the networking and
+// reportStats retrieves various stats about the node at the networking and
// mining layer and reports it to the stats server.
func (s *Service) reportStats(conn *connWrapper) error {
// Gather the syncing and mining infos from the local miner instance
@@ -781,17 +788,22 @@ func (s *Service) reportStats(conn *connWrapper) error {
syncing bool
gasprice int
)
- if s.eth != nil {
- mining = s.eth.Miner().Mining()
- hashrate = int(s.eth.Miner().HashRate())
+ // check if backend is a full node
+ fullBackend, ok := s.backend.(fullNodeBackend)
+ if ok {
+ mining = fullBackend.Miner().Mining()
+ hashrate = int(fullBackend.Miner().HashRate())
- sync := s.eth.Downloader().Progress()
- syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock
+ sync := fullBackend.Downloader().Progress()
+ syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
- price, _ := s.eth.ApiBackend.SuggestGasTipCap(context.Background())
+ price, _ := s.backend.SuggestGasTipCap(context.Background())
gasprice = int(price.Uint64())
- if basefee := s.eth.ApiBackend.CurrentHeader().BaseFee; basefee != nil {
+ if basefee := fullBackend.CurrentHeader().BaseFee; basefee != nil {
gasprice += int(basefee.Uint64())
+ } else {
+ sync := s.backend.Downloader().Progress()
+ syncing = s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
}
}
// Assemble the node stats and send it to the server
@@ -803,7 +815,7 @@ func (s *Service) reportStats(conn *connWrapper) error {
Active: true,
Mining: mining,
Hashrate: hashrate,
- Peers: s.eth.GetPeer(),
+ Peers: s.server.PeerCount(),
GasPrice: gasprice,
Syncing: syncing,
Uptime: 100,
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index c3d77c6416..7092271865 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -905,7 +905,7 @@ func (s *PublicBlockChainAPI) GetMasternodes(ctx context.Context, b *types.Block
if prevBlockNumber >= latestBlockNumber || !s.b.ChainConfig().IsTIP2019(b.Number()) {
prevBlockNumber = curBlockNumber
}
- if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
+ if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
// Get block epoc latest.
return engine.GetMasternodesByNumber(s.chainReader, prevBlockNumber), nil
} else {
@@ -980,7 +980,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd
var maxMasternodes int
if header.Number.Cmp(s.b.ChainConfig().XDPoS.V2.SwitchBlock) == 1 {
- if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
+ if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
round, err := engine.EngineV2.GetRoundNumber(header)
if err != nil {
return result, err
@@ -1008,7 +1008,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd
}
// Get masternode list
- if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
+ if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
masternodes = engine.GetMasternodesFromCheckpointHeader(header)
if len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
@@ -1139,7 +1139,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch
}
// Find candidates that have masternode status
- if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
+ if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
masternodes = engine.GetMasternodesFromCheckpointHeader(header)
if len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
@@ -1175,7 +1175,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch
var maxMasternodes int
if header.Number.Cmp(s.b.ChainConfig().XDPoS.V2.SwitchBlock) == 1 {
- if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
+ if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
round, err := engine.EngineV2.GetRoundNumber(header)
if err != nil {
return result, err
@@ -1251,7 +1251,7 @@ func (s *PublicBlockChainAPI) GetCheckpointFromEpoch(ctx context.Context, epochN
if epochNum == rpc.LatestEpochNumber {
blockNumer := s.b.CurrentBlock().Number()
- if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
+ if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
var err error
var currentEpoch uint64
checkpointNumber, currentEpoch, err = engine.GetCurrentEpochSwitchBlock(s.chainReader, blockNumer)
@@ -1340,7 +1340,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
if block == nil {
return nil, fmt.Errorf("nil block in DoCall: number=%d, hash=%s", header.Number.Uint64(), header.Hash().Hex())
}
- author, err := b.GetEngine().Author(block.Header())
+ author, err := b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -1672,7 +1672,7 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx bool, ctx context.Context) (map[string]interface{}, error) {
fields := RPCMarshalHeader(b.Header())
fields["size"] = hexutil.Uint64(b.Size())
- fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(b.Hash()))
+ fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(context.Background(), b.Hash()))
if inclTx {
formatTx := func(tx *types.Transaction) (interface{}, error) {
@@ -1720,7 +1720,7 @@ func (s *PublicBlockChainAPI) findNearestSignedBlock(ctx context.Context, b *typ
}
// Get block epoc latest
- checkpointNumber, _, err := s.b.GetEngine().(*XDPoS.XDPoS).GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(signedBlockNumber)))
+ checkpointNumber, _, err := s.b.Engine().(*XDPoS.XDPoS).GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(signedBlockNumber)))
if err != nil {
log.Error("[findNearestSignedBlock] Error while trying to get current Epoch switch block", "Number", signedBlockNumber)
}
@@ -1740,7 +1740,7 @@ findFinalityOfBlock return finality of a block
Use blocksHashCache for to keep track - refer core/blockchain.go for more detail
*/
func (s *PublicBlockChainAPI) findFinalityOfBlock(ctx context.Context, b *types.Block, masternodes []common.Address) (uint, error) {
- engine, _ := s.b.GetEngine().(*XDPoS.XDPoS)
+ engine, _ := s.b.Engine().(*XDPoS.XDPoS)
signedBlock := s.findNearestSignedBlock(ctx, b)
if signedBlock == nil {
@@ -1839,7 +1839,7 @@ func (s *PublicBlockChainAPI) rpcOutputBlockSigners(b *types.Block, ctx context.
return []common.Address{}, err
}
- engine, ok := s.b.GetEngine().(*XDPoS.XDPoS)
+ engine, ok := s.b.Engine().(*XDPoS.XDPoS)
if !ok {
log.Error("Undefined XDPoS consensus engine")
return []common.Address{}, nil
@@ -2018,7 +2018,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
if block == nil {
return nil, 0, nil, fmt.Errorf("nil block in AccessList: number=%d, hash=%s", header.Number.Uint64(), header.Hash().Hex())
}
- author, err := b.GetEngine().Author(block.Header())
+ author, err := b.Engine().Author(block.Header())
if err != nil {
return nil, 0, nil, err
}
@@ -2619,7 +2619,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBestBid(ctx context.Context, baseToken
if XDCxService == nil {
return result, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return result, err
}
@@ -2644,7 +2644,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBestAsk(ctx context.Context, baseToken
if XDCxService == nil {
return result, errors.New("not found XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return result, err
}
@@ -2668,7 +2668,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBidTree(ctx context.Context, baseToken
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2692,7 +2692,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetPrice(ctx context.Context, baseToken,
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2716,7 +2716,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLastEpochPrice(ctx context.Context, ba
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2740,7 +2740,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetCurrentEpochPrice(ctx context.Context,
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2764,7 +2764,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetAskTree(ctx context.Context, baseToken
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2788,7 +2788,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetOrderById(ctx context.Context, baseTok
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2813,7 +2813,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetTradingOrderBookInfo(ctx context.Conte
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2837,7 +2837,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLiquidationPriceTree(ctx context.Conte
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2861,7 +2861,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetInvestingTree(ctx context.Context, len
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2885,7 +2885,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBorrowingTree(ctx context.Context, len
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2909,7 +2909,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderBookInfo(tx context.Contex
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2933,7 +2933,7 @@ func (s *PublicXDCXTransactionPoolAPI) getLendingOrderTree(ctx context.Context,
if lendingService == nil {
return nil, errors.New("not find XDCX Lending service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2957,7 +2957,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingTradeTree(ctx context.Context,
if lendingService == nil {
return nil, errors.New("not find XDCX lending service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -2981,7 +2981,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLiquidationTimeTree(ctx context.Contex
if lendingService == nil {
return nil, errors.New("not find XDCX Lending service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -3005,7 +3005,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderCount(ctx context.Context,
if lendingService == nil {
return nil, errors.New("not find XDCX Lending service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -3027,7 +3027,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBestInvesting(ctx context.Context, len
if lendingService == nil {
return result, errors.New("not find XDCX Lending service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return result, err
}
@@ -3049,7 +3049,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBestBorrowing(ctx context.Context, len
if lendingService == nil {
return result, errors.New("not find XDCX Lending service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return result, err
}
@@ -3070,7 +3070,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBids(ctx context.Context, baseToken, q
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -3094,7 +3094,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetAsks(ctx context.Context, baseToken, q
if XDCxService == nil {
return nil, errors.New("not find XDCX service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -3118,7 +3118,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetInvests(ctx context.Context, lendingTo
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -3142,7 +3142,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBorrows(ctx context.Context, lendingTo
if lendingService == nil {
return nil, errors.New("XDCX Lending service not found")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return nil, err
}
@@ -3200,7 +3200,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderById(ctx context.Context,
if lendingService == nil {
return lendingItem, errors.New("not find XDCX lending service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return lendingItem, err
}
@@ -3227,7 +3227,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingTradeById(ctx context.Context,
if lendingService == nil {
return lendingItem, errors.New("not find XDCX Lending service")
}
- author, err := s.b.GetEngine().Author(block.Header())
+ author, err := s.b.Engine().Author(block.Header())
if err != nil {
return lendingItem, err
}
@@ -3535,7 +3535,7 @@ func GetSignersFromBlocks(b Backend, blockNumber uint64, blockHash common.Hash,
mapMN[node] = true
}
signer := types.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(blockNumber))
- if engine, ok := b.GetEngine().(*XDPoS.XDPoS); ok {
+ if engine, ok := b.Engine().(*XDPoS.XDPoS); ok {
limitNumber := blockNumber + common.LimitTimeFinality
currentNumber := b.CurrentBlock().NumberU64()
if limitNumber > currentNumber {
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index 32a2c545f7..e4cdf283da 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -70,7 +70,7 @@ type Backend interface {
GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error)
PendingBlockAndReceipts() (*types.Block, types.Receipts)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
- GetTd(blockHash common.Hash) *big.Int
+ GetTd(ctx context.Context, blockHash common.Hash) *big.Int
GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error)
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
@@ -93,9 +93,9 @@ type Backend interface {
SendLendingTx(ctx context.Context, signedTx *types.LendingTransaction) error
ChainConfig() *params.ChainConfig
+ Engine() consensus.Engine
CurrentBlock() *types.Block
GetIPCClient() (bind.ContractBackend, error)
- GetEngine() consensus.Engine
GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int
GetVotersRewards(common.Address) map[common.Address]*big.Int
@@ -116,6 +116,7 @@ type Backend interface {
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
BloomStatus() (uint64, uint64)
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
+ GetPeer() int
}
func GetAPIs(apiBackend Backend, chainReader consensus.ChainReader) []rpc.API {
diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go
index 411b4fde4e..960b81d076 100644
--- a/internal/ethapi/transaction_args_test.go
+++ b/internal/ethapi/transaction_args_test.go
@@ -304,6 +304,7 @@ func (b *backendMock) RPCEVMTimeout() time.Duration { return time.Second }
func (b *backendMock) RPCTxFeeCap() float64 { return 0 }
func (b *backendMock) UnprotectedAllowed() bool { return false }
func (b *backendMock) SetHead(number uint64) {}
+func (b *backendMock) GetPeer() int { return 0 }
func (b *backendMock) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
return nil, nil
@@ -345,7 +346,7 @@ func (b *backendMock) GetReceipts(ctx context.Context, hash common.Hash) (types.
return nil, nil
}
-func (b *backendMock) GetTd(common.Hash) *big.Int {
+func (b *backendMock) GetTd(ctx context.Context, hash common.Hash) *big.Int {
return nil
}
@@ -412,10 +413,6 @@ func (b *backendMock) GetBlocksHashCache(blockNr uint64) []common.Hash {
return []common.Hash{}
}
-func (b *backendMock) GetEngine() consensus.Engine {
- return nil
-}
-
func (b *backendMock) GetEpochDuration() *big.Int {
return nil
}
diff --git a/node/api.go b/node/api.go
index 362880ceaa..cfe904f06c 100644
--- a/node/api.go
+++ b/node/api.go
@@ -18,32 +18,52 @@ package node
import (
"context"
- "errors"
"fmt"
"strings"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/crypto"
+ "github.com/XinFinOrg/XDPoSChain/internal/debug"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
-// PrivateAdminAPI is the collection of administrative API methods exposed only
-// over a secure RPC channel.
-type PrivateAdminAPI struct {
- node *Node // Node interfaced by this API
+// apis returns the collection of built-in RPC APIs.
+func (n *Node) apis() []rpc.API {
+ return []rpc.API{
+ {
+ Namespace: "admin",
+ Version: "1.0",
+ Service: &privateAdminAPI{n},
+ }, {
+ Namespace: "admin",
+ Version: "1.0",
+ Service: &publicAdminAPI{n},
+ Public: true,
+ }, {
+ Namespace: "debug",
+ Version: "1.0",
+ Service: debug.Handler,
+ }, {
+ Namespace: "web3",
+ Version: "1.0",
+ Service: &publicWeb3API{n},
+ Public: true,
+ },
+ }
+
}
-// NewPrivateAdminAPI creates a new API definition for the private admin methods
-// of the node itself.
-func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI {
- return &PrivateAdminAPI{node: node}
+// privateAdminAPI is the collection of administrative API methods exposed only
+// over a secure RPC channel.
+type privateAdminAPI struct {
+ node *Node // Node interfaced by this API
}
// AddPeer requests connecting to a remote node, and also maintaining the new
// connection at all times, even reconnecting if it is lost.
-func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
+func (api *privateAdminAPI) AddPeer(url string) (bool, error) {
// Make sure the server is running, fail otherwise
server := api.node.Server()
if server == nil {
@@ -59,7 +79,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
}
// RemovePeer disconnects from a remote node if the connection exists
-func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
+func (api *privateAdminAPI) RemovePeer(url string) (bool, error) {
// Make sure the server is running, fail otherwise
server := api.node.Server()
if server == nil {
@@ -75,7 +95,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
}
// AddTrustedPeer allows a remote node to always connect, even if slots are full
-func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) {
+func (api *privateAdminAPI) AddTrustedPeer(url string) (bool, error) {
// Make sure the server is running, fail otherwise
server := api.node.Server()
if server == nil {
@@ -91,7 +111,7 @@ func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) {
// RemoveTrustedPeer removes a remote node from the trusted peer set, but it
// does not disconnect it automatically.
-func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
+func (api *privateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
// Make sure the server is running, fail otherwise
server := api.node.Server()
if server == nil {
@@ -107,7 +127,7 @@ func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
// PeerEvents creates an RPC subscription which receives peer events from the
// node's p2p.Server
-func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {
+func (api *privateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {
// Make sure the server is running, fail otherwise
server := api.node.Server()
if server == nil {
@@ -144,14 +164,11 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription,
}
// StartRPC starts the HTTP RPC API server.
-func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
+func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
api.node.lock.Lock()
defer api.node.lock.Unlock()
- if api.node.httpHandler != nil {
- return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint)
- }
-
+ // Determine host and port.
if host == nil {
h := DefaultHTTPHost
if api.node.config.HTTPHost != "" {
@@ -163,57 +180,55 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
port = &api.node.config.HTTPPort
}
- allowedOrigins := api.node.config.HTTPCors
+ // Determine config.
+ config := httpConfig{
+ CorsAllowedOrigins: api.node.config.HTTPCors,
+ Vhosts: api.node.config.HTTPVirtualHosts,
+ Modules: api.node.config.HTTPModules,
+ }
if cors != nil {
- allowedOrigins = nil
+ config.CorsAllowedOrigins = nil
for _, origin := range strings.Split(*cors, ",") {
- allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin))
+ config.CorsAllowedOrigins = append(config.CorsAllowedOrigins, strings.TrimSpace(origin))
}
}
-
- allowedVHosts := api.node.config.HTTPVirtualHosts
if vhosts != nil {
- allowedVHosts = nil
+ config.Vhosts = nil
for _, vhost := range strings.Split(*host, ",") {
- allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost))
+ config.Vhosts = append(config.Vhosts, strings.TrimSpace(vhost))
}
}
-
- modules := api.node.httpWhitelist
if apis != nil {
- modules = nil
+ config.Modules = nil
for _, m := range strings.Split(*apis, ",") {
- modules = append(modules, strings.TrimSpace(m))
+ config.Modules = append(config.Modules, strings.TrimSpace(m))
}
}
- if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts, api.node.config.WSOrigins); err != nil {
+ if err := api.node.http.setListenAddr(*host, *port); err != nil {
+ return false, err
+ }
+ if err := api.node.http.enableRPC(api.node.rpcAPIs, config); err != nil {
+ return false, err
+ }
+ if err := api.node.http.start(); err != nil {
return false, err
}
return true, nil
}
-// StopRPC terminates an already running HTTP RPC API endpoint.
-func (api *PrivateAdminAPI) StopRPC() (bool, error) {
- api.node.lock.Lock()
- defer api.node.lock.Unlock()
-
- if api.node.httpHandler == nil {
- return false, errors.New("HTTP RPC not running")
- }
- api.node.stopHTTP()
+// StopRPC shuts down the HTTP server.
+func (api *privateAdminAPI) StopRPC() (bool, error) {
+ api.node.http.stop()
return true, nil
}
// StartWS starts the websocket RPC API server.
-func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) {
+func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) {
api.node.lock.Lock()
defer api.node.lock.Unlock()
- if api.node.wsHandler != nil {
- return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint)
- }
-
+ // Determine host and port.
if host == nil {
h := DefaultWSHost
if api.node.config.WSHost != "" {
@@ -225,55 +240,56 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str
port = &api.node.config.WSPort
}
- origins := api.node.config.WSOrigins
- if allowedOrigins != nil {
- origins = nil
- for _, origin := range strings.Split(*allowedOrigins, ",") {
- origins = append(origins, strings.TrimSpace(origin))
- }
+ // Determine config.
+ config := wsConfig{
+ Modules: api.node.config.WSModules,
+ Origins: api.node.config.WSOrigins,
}
- modules := api.node.config.WSModules
if apis != nil {
- modules = nil
+ config.Modules = nil
for _, m := range strings.Split(*apis, ",") {
- modules = append(modules, strings.TrimSpace(m))
+ config.Modules = append(config.Modules, strings.TrimSpace(m))
+ }
+ }
+ if allowedOrigins != nil {
+ config.Origins = nil
+ for _, origin := range strings.Split(*allowedOrigins, ",") {
+ config.Origins = append(config.Origins, strings.TrimSpace(origin))
}
}
- if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, origins, api.node.config.WSExposeAll); err != nil {
+ // Enable WebSocket on the server.
+ server := api.node.wsServerForPort(*port)
+ if err := server.setListenAddr(*host, *port); err != nil {
return false, err
}
- return true, nil
-}
-
-// StopWS terminates an already running websocket RPC API endpoint.
-func (api *PrivateAdminAPI) StopWS() (bool, error) {
- api.node.lock.Lock()
- defer api.node.lock.Unlock()
-
- if api.node.wsHandler == nil {
- return false, errors.New("WebSocket RPC not running")
+ if err := server.enableWS(api.node.rpcAPIs, config); err != nil {
+ return false, err
}
- api.node.stopWS()
+ if err := server.start(); err != nil {
+ return false, err
+ }
+ api.node.http.log.Info("WebSocket endpoint opened", "url", api.node.WSEndpoint())
return true, nil
}
-// PublicAdminAPI is the collection of administrative API methods exposed over
-// both secure and unsecure RPC channels.
-type PublicAdminAPI struct {
- node *Node // Node interfaced by this API
+// StopWS terminates all WebSocket servers.
+func (api *privateAdminAPI) StopWS() (bool, error) {
+ api.node.http.stopWS()
+ api.node.ws.stop()
+ return true, nil
}
-// NewPublicAdminAPI creates a new API definition for the public admin methods
-// of the node itself.
-func NewPublicAdminAPI(node *Node) *PublicAdminAPI {
- return &PublicAdminAPI{node: node}
+// publicAdminAPI is the collection of administrative API methods exposed over
+// both secure and unsecure RPC channels.
+type publicAdminAPI struct {
+ node *Node // Node interfaced by this API
}
// Peers retrieves all the information we know about each individual peer at the
// protocol granularity.
-func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) {
+func (api *publicAdminAPI) Peers() ([]*p2p.PeerInfo, error) {
server := api.node.Server()
if server == nil {
return nil, ErrNodeStopped
@@ -283,7 +299,7 @@ func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) {
// NodeInfo retrieves all the information we know about the host node at the
// protocol granularity.
-func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
+func (api *publicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
server := api.node.Server()
if server == nil {
return nil, ErrNodeStopped
@@ -292,27 +308,22 @@ func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
}
// Datadir retrieves the current data directory the node is using.
-func (api *PublicAdminAPI) Datadir() string {
+func (api *publicAdminAPI) Datadir() string {
return api.node.DataDir()
}
-// PublicWeb3API offers helper utils
-type PublicWeb3API struct {
+// publicWeb3API offers helper utils
+type publicWeb3API struct {
stack *Node
}
-// NewPublicWeb3API creates a new Web3Service instance
-func NewPublicWeb3API(stack *Node) *PublicWeb3API {
- return &PublicWeb3API{stack}
-}
-
// ClientVersion returns the node name
-func (s *PublicWeb3API) ClientVersion() string {
+func (s *publicWeb3API) ClientVersion() string {
return s.stack.Server().Name
}
// Sha3 applies the ethereum sha3 implementation on the input.
// It assumes the input is hex encoded.
-func (s *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
+func (s *publicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
return crypto.Keccak256(input)
}
diff --git a/node/api_test.go b/node/api_test.go
new file mode 100644
index 0000000000..388caff8b1
--- /dev/null
+++ b/node/api_test.go
@@ -0,0 +1,350 @@
+// 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
+
+import (
+ "bytes"
+ "io"
+ "net"
+ "net/http"
+ "net/url"
+ "strings"
+ "testing"
+
+ "github.com/XinFinOrg/XDPoSChain/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 "
+}
diff --git a/node/doc.go b/node/doc.go
index e3cc58e5f4..9110a97362 100644
--- a/node/doc.go
+++ b/node/doc.go
@@ -21,8 +21,37 @@ In the model exposed by this package, a node is a collection of services which u
resources to provide RPC APIs. Services can also offer devp2p protocols, which are wired
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.
-Resources Managed By Node
+ ●───────┐
+ 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
All file-system resources used by a node instance are located in a directory called the
data directory. The location of each resource can be overridden through additional node
@@ -46,8 +75,7 @@ without a data directory, databases are opened in memory instead.
Node also creates the shared store of encrypted Ethereum account keys. Services can access
the account manager through the service context.
-
-Sharing Data Directory Among Instances
+# Sharing Data Directory Among Instances
Multiple node instances can share a single data directory if they have distinct instance
names (set through the Name config option). Sharing behaviour depends on the type of
@@ -65,26 +93,25 @@ create one database for each instance.
The account key store is shared among all node instances using the same data directory
unless its location is changed through the KeyStoreDir configuration option.
-
-Data Directory Sharing Example
+# Data Directory Sharing Example
In this example, two node instances named A and B are started with the same data
directory. Node instance A opens the database "db", node instance B opens the databases
"db" and "db-2". The following files will be created in the data directory:
- data-directory/
- A/
- nodekey -- devp2p node key of instance A
- nodes/ -- devp2p discovery knowledge database of instance A
- db/ -- LevelDB content for "db"
- A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A
- B/
- nodekey -- devp2p node key of node B
- nodes/ -- devp2p discovery knowledge database of instance B
- static-nodes.json -- devp2p static node list of instance B
- db/ -- LevelDB content for "db"
- db-2/ -- LevelDB content for "db-2"
- B.ipc -- JSON-RPC UNIX domain socket endpoint of instance B
- keystore/ -- account key store, used by both instances
+ data-directory/
+ A/
+ nodekey -- devp2p node key of instance A
+ nodes/ -- devp2p discovery knowledge database of instance A
+ db/ -- LevelDB content for "db"
+ A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A
+ B/
+ nodekey -- devp2p node key of node B
+ nodes/ -- devp2p discovery knowledge database of instance B
+ static-nodes.json -- devp2p static node list of instance B
+ db/ -- LevelDB content for "db"
+ db-2/ -- LevelDB content for "db-2"
+ B.ipc -- JSON-RPC UNIX domain socket endpoint of instance B
+ keystore/ -- account key store, used by both instances
*/
package node
diff --git a/node/endpoints.go b/node/endpoints.go
index 234b49e73d..312d925567 100644
--- a/node/endpoints.go
+++ b/node/endpoints.go
@@ -17,51 +17,12 @@
package node
import (
- "net"
- "net/http"
"time"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
-// StartHTTPEndpoint starts the HTTP RPC endpoint.
-func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, 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
- }
- // Bundle and start the HTTP server
- httpSrv := &http.Server{
- Handler: handler,
- ReadTimeout: timeouts.ReadTimeout,
- WriteTimeout: timeouts.WriteTimeout,
- IdleTimeout: timeouts.IdleTimeout,
- }
- log.Info("StartHTTPEndpoint", "ReadTimeout", timeouts.ReadTimeout, "WriteTimeout", timeouts.WriteTimeout, "IdleTimeout", timeouts.IdleTimeout)
- go httpSrv.Serve(listener)
- 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
// 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.
@@ -87,7 +48,7 @@ func CheckTimeouts(timeouts *rpc.HTTPTimeouts) {
log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadTimeout)
timeouts.ReadTimeout = rpc.DefaultHTTPTimeouts.ReadTimeout
}
- if timeouts.WriteTimeout < time.Second {
+ if timeouts.WriteTimeout < 2*time.Second {
log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", rpc.DefaultHTTPTimeouts.WriteTimeout)
timeouts.WriteTimeout = rpc.DefaultHTTPTimeouts.WriteTimeout
}
diff --git a/node/errors.go b/node/errors.go
index 2e0dadc4d6..67547bf691 100644
--- a/node/errors.go
+++ b/node/errors.go
@@ -39,17 +39,6 @@ func convertFileLockError(err error) error {
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
// services or itself.
type StopError struct {
diff --git a/node/lifecycle.go b/node/lifecycle.go
new file mode 100644
index 0000000000..0d5f9a0680
--- /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 0a0443f111..4fbff9690c 100644
--- a/node/node.go
+++ b/node/node.go
@@ -17,10 +17,8 @@
package node
import (
- "context"
"errors"
"fmt"
- "net"
"net/http"
"os"
"path/filepath"
@@ -32,7 +30,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event"
- "github.com/XinFinOrg/XDPoSChain/internal/debug"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc"
@@ -48,40 +45,29 @@ type Node struct {
keyDir string // key store directory
keyDirTemp bool // If true, key directory will be removed by Stop
- ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop
- instanceDirLock flock.Releaser // prevents concurrent use of instance directory
+ ephemKeystore string // if non-empty, the key directory that will be removed by Stop
+ dirLock flock.Releaser // prevents concurrent use of instance directory
+ stop chan struct{} // Channel to wait for termination notifications
- serverConfig p2p.Config
- server *p2p.Server // Currently running P2P networking layer
- state int // Tracks state of node lifecycle
-
- serviceFuncs []ServiceConstructor // Service constructors (in dependency order)
- services map[reflect.Type]Service // Currently running services
+ server *p2p.Server // Currently running P2P networking layer
+ startStopLock sync.Mutex // Start/Stop are protected by an additional lock
+ state int // Tracks state of node lifecycle
+ lock sync.Mutex
+ lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle
rpcAPIs []rpc.API // List of APIs currently provided by the node
+ http *httpServer //
+ ws *httpServer //
+ ipc *ipcServer // Stores information about the ipc http server
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
- ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled)
- ipcListener net.Listener // IPC RPC listener socket to serve API requests
- ipcHandler *rpc.Server // IPC RPC request handler to process the API requests
-
- httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
- httpWhitelist []string // HTTP RPC modules to allow through this endpoint
- httpListenerAddr net.Addr // Address of HTTP RPC listener socket serving API requests
- httpServer *http.Server // HTTP RPC HTTP server
- httpHandler *rpc.Server // HTTP RPC request handler to process the API requests
-
- wsEndpoint string // WebSocket endpoint (interface + port) to listen at (empty = WebSocket disabled)
- wsListenerAddr net.Addr // Address of WebSocket RPC listener socket serving API requests
- wsHTTPServer *http.Server // WebSocket RPC HTTP server
- wsHandler *rpc.Server // WebSocket RPC request handler to process the API requests
-
- stop chan struct{} // Channel to wait for termination notifications
- lock sync.RWMutex
+ databases map[*closeTrackingDB]struct{} // All open databases
}
const (
initializingState = iota
+ runningState
+ closedState
)
// New creates a new P2P node, ready for protocol registration.
@@ -114,13 +100,21 @@ func New(conf *Config) (*Node, error) {
}
node := &Node{
- config: conf,
- eventmux: new(event.TypeMux),
- log: conf.Logger,
- serviceFuncs: []ServiceConstructor{},
- ipcEndpoint: conf.IPCEndpoint(),
- httpEndpoint: conf.HTTPEndpoint(),
- wsEndpoint: conf.WSEndpoint(),
+ config: conf,
+ inprocHandler: rpc.NewServer(),
+ eventmux: new(event.TypeMux),
+ log: conf.Logger,
+ stop: make(chan struct{}),
+ server: &p2p.Server{Config: conf.P2P},
+ databases: make(map[*closeTrackingDB]struct{}),
+ }
+
+ // Register built-in APIs.
+ node.rpcAPIs = append(node.rpcAPIs, node.apis()...)
+
+ // Acquire the instance directory lock.
+ if err := node.openDataDir(); err != nil {
+ return nil, err
}
keyDir, isEphem, err := getKeyStoreDir(conf)
@@ -133,18 +127,108 @@ func New(conf *Config) (*Node, error) {
// are required to add the backends later on.
node.accman = accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed})
+ // Initialize the p2p server. This creates the node key and discovery databases.
+ node.server.Config.PrivateKey = node.config.NodeKey()
+ node.server.Config.Name = node.config.NodeName()
+ node.server.Config.Logger = node.log
+ if node.server.Config.StaticNodes == nil {
+ node.server.Config.StaticNodes = node.config.StaticNodes()
+ }
+ if node.server.Config.TrustedNodes == nil {
+ node.server.Config.TrustedNodes = node.config.TrustedNodes()
+ }
+ if node.server.Config.NodeDatabase == "" {
+ node.server.Config.NodeDatabase = node.config.NodeDB()
+ }
+
+ //make sure timeout values are meaningful
+ CheckTimeouts(&conf.HTTPTimeouts)
+ // Configure RPC servers.
+ node.http = newHTTPServer(node.log, conf.HTTPTimeouts)
+ node.ws = newHTTPServer(node.log, conf.HTTPTimeouts)
+ node.ipc = newIPCServer(node.log, conf.IPCEndpoint())
+
return node, nil
}
+// Start starts all registered lifecycles, RPC services and p2p networking.
+// Node can only be started once.
+func (n *Node) Start() error {
+ n.startStopLock.Lock()
+ defer n.startStopLock.Unlock()
+
+ n.lock.Lock()
+ switch n.state {
+ case runningState:
+ n.lock.Unlock()
+ return ErrNodeRunning
+ case closedState:
+ n.lock.Unlock()
+ return ErrNodeStopped
+ }
+ n.state = runningState
+ err := n.startNetworking()
+ lifecycles := make([]Lifecycle, len(n.lifecycles))
+ copy(lifecycles, n.lifecycles)
+ n.lock.Unlock()
+
+ // Check if networking startup failed.
+ if err != nil {
+ n.doClose(nil)
+ return err
+ }
+ // Start all registered lifecycles.
+ var started []Lifecycle
+ for _, lifecycle := range lifecycles {
+ if err = lifecycle.Start(); err != nil {
+ break
+ }
+ started = append(started, lifecycle)
+ }
+ // Check if any lifecycle failed to start.
+ if err != nil {
+ n.stopServices(started)
+ n.doClose(nil)
+ }
+ return err
+}
+
// Close stops the Node and releases resources acquired in
// Node constructor New.
func (n *Node) Close() error {
- var errs []error
+ n.startStopLock.Lock()
+ defer n.startStopLock.Unlock()
- // Terminate all subsystems and collect any errors
- if err := n.Stop(); err != nil && err != ErrNodeStopped {
- errs = append(errs, err)
+ n.lock.Lock()
+ state := n.state
+ n.lock.Unlock()
+ switch state {
+ case initializingState:
+ // The node was never started.
+ return n.doClose(nil)
+ case runningState:
+ // The node was started, release resources acquired by Start().
+ var errs []error
+ if err := n.stopServices(n.lifecycles); err != nil {
+ errs = append(errs, err)
+ }
+ return n.doClose(errs)
+ case closedState:
+ return ErrNodeStopped
+ default:
+ panic(fmt.Sprintf("node is in unknown state %d", state))
}
+}
+
+// doClose releases resources acquired by New(), collecting errors.
+func (n *Node) doClose(errs []error) error {
+ // Close databases. This needs the lock because it needs to
+ // synchronize with OpenDatabase*.
+ n.lock.Lock()
+ n.state = closedState
+ errs = append(errs, n.closeDatabases()...)
+ n.lock.Unlock()
+
if err := n.accman.Close(); err != nil {
errs = append(errs, err)
}
@@ -154,7 +238,19 @@ func (n *Node) Close() error {
}
}
- // Report any errors that might have occurred
+ if n.ephemKeystore != "" {
+ if err := os.RemoveAll(n.ephemKeystore); err != nil {
+ errs = append(errs, err)
+ }
+ }
+
+ // Release instance directory lock.
+ n.closeDataDir()
+
+ // Unblock n.Wait.
+ close(n.stop)
+
+ // Report any errors that might have occurred.
switch len(errs) {
case 0:
return nil
@@ -165,109 +261,49 @@ func (n *Node) Close() error {
}
}
-// Register injects a new service into the node's stack. The service created by
-// the passed constructor must be unique in its type with regard to sibling ones.
-func (n *Node) Register(constructor ServiceConstructor) error {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- if n.server != nil {
- return ErrNodeRunning
- }
- n.serviceFuncs = append(n.serviceFuncs, constructor)
- return nil
-}
-
-// Start creates a live P2P node and starts running it.
-func (n *Node) Start() error {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- // Short circuit if the node's already running
- if n.server != nil {
- return ErrNodeRunning
- }
- if err := n.openDataDir(); err != nil {
- return err
- }
-
- // Initialize the p2p server. This creates the node key and
- // discovery databases.
- n.serverConfig = n.config.P2P
- n.serverConfig.PrivateKey = n.config.NodeKey()
- n.serverConfig.Name = n.config.NodeName()
- n.serverConfig.Logger = n.log
- if n.serverConfig.StaticNodes == nil {
- n.serverConfig.StaticNodes = n.config.StaticNodes()
- }
- if n.serverConfig.TrustedNodes == nil {
- n.serverConfig.TrustedNodes = n.config.TrustedNodes()
- }
- if n.serverConfig.NodeDatabase == "" {
- n.serverConfig.NodeDatabase = n.config.NodeDB()
- }
- running := &p2p.Server{Config: n.serverConfig}
- n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name)
-
- // Otherwise copy and specialize the P2P configuration
- services := make(map[reflect.Type]Service)
- for _, constructor := range n.serviceFuncs {
- // Create a new context for the particular service
- ctx := &ServiceContext{
- config: n.config,
- services: make(map[reflect.Type]Service),
- EventMux: n.eventmux,
- AccountManager: n.accman,
- }
- for kind, s := range services { // copy needed for threaded access
- ctx.services[kind] = s
- }
- // Construct and save the service
- service, err := constructor(ctx)
- if err != nil {
- return err
- }
- kind := reflect.TypeOf(service)
- if _, exists := services[kind]; exists {
- return &DuplicateServiceError{Kind: kind}
- }
- services[kind] = service
- }
- // Gather the protocols and start the freshly assembled P2P server
- for _, service := range services {
- running.Protocols = append(running.Protocols, service.Protocols()...)
- }
- if err := running.Start(); err != nil {
+// startNetworking starts all network endpoints.
+func (n *Node) startNetworking() error {
+ n.log.Info("Starting peer-to-peer node", "instance", n.server.Name)
+ if err := n.server.Start(); err != nil {
return convertFileLockError(err)
}
- // Start each of the services
- var started []reflect.Type
- for kind, service := range services {
- // Start the next service, stopping all previous upon failure
- if err := service.Start(running); err != nil {
- for _, kind := range started {
- services[kind].Stop()
- }
- running.Stop()
-
- return err
- }
- // Mark the service started for potential cleanup
- started = append(started, kind)
+ err := n.startRPC()
+ if err != nil {
+ n.stopRPC()
+ n.server.Stop()
}
- // Lastly, start the configured RPC interfaces
- if err := n.startRPC(services); err != nil {
- for _, service := range services {
- service.Stop()
- }
- running.Stop()
- return err
- }
- // Finish initializing the startup
- n.services = services
- n.server = running
- n.stop = make(chan struct{})
+ return err
+}
+// containsLifecycle checks if 'lfs' contains 'l'.
+func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool {
+ for _, obj := range lfs {
+ if obj == l {
+ return true
+ }
+ }
+ return false
+}
+
+// stopServices terminates running services, RPC and p2p networking.
+// It is the inverse of Start.
+func (n *Node) stopServices(running []Lifecycle) error {
+ n.stopRPC()
+
+ // Stop running lifecycles in reverse order.
+ failure := &StopError{Services: make(map[reflect.Type]error)}
+ for i := len(running) - 1; i >= 0; i-- {
+ if err := running[i].Stop(); err != nil {
+ failure.Services[reflect.TypeOf(running[i])] = err
+ }
+ }
+
+ // Stop p2p networking.
+ n.server.Stop()
+
+ if len(failure.Services) > 0 {
+ return failure
+ }
return nil
}
@@ -286,67 +322,128 @@ func (n *Node) openDataDir() error {
if err != nil {
return convertFileLockError(err)
}
- n.instanceDirLock = release
+ n.dirLock = release
return nil
}
-// startRPC is a helper method to start all the various RPC endpoints during node
+func (n *Node) closeDataDir() {
+ // Release instance directory lock.
+ if n.dirLock != nil {
+ if err := n.dirLock.Release(); err != nil {
+ n.log.Error("Can't release datadir lock", "err", err)
+ }
+ n.dirLock = nil
+ }
+}
+
+// configureRPC is a helper method to configure all the various RPC endpoints during node
// startup. It's not meant to be called at any time afterwards as it makes certain
// assumptions about the state of the node.
-func (n *Node) startRPC(services map[reflect.Type]Service) error {
- // Gather all the possible APIs to surface
- apis := n.apis()
- for _, service := range services {
- apis = append(apis, service.APIs()...)
- }
- // Start the various API endpoints, terminating all in case of errors
- if err := n.startInProc(apis); err != nil {
+func (n *Node) startRPC() error {
+ if err := n.startInProc(); err != nil {
return err
}
- if err := n.startIPC(apis); err != nil {
- n.stopInProc()
- return err
- }
- if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil {
- n.stopIPC()
- n.stopInProc()
- return err
- }
- // if endpoints are not the same, start separate servers
- if n.httpEndpoint != n.wsEndpoint {
- if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil {
- n.stopHTTP()
- n.stopIPC()
- n.stopInProc()
+
+ // Configure IPC.
+ if n.ipc.endpoint != "" {
+ if err := n.ipc.start(n.rpcAPIs); err != nil {
return err
}
}
- // All API endpoints started successfully
- n.rpcAPIs = apis
- return nil
+ // Configure HTTP.
+ if n.config.HTTPHost != "" {
+ config := httpConfig{
+ CorsAllowedOrigins: n.config.HTTPCors,
+ Vhosts: n.config.HTTPVirtualHosts,
+ Modules: n.config.HTTPModules,
+ }
+ if err := n.http.setListenAddr(n.config.HTTPHost, n.config.HTTPPort); err != nil {
+ return err
+ }
+ if err := n.http.enableRPC(n.rpcAPIs, config); err != nil {
+ return err
+ }
+ }
+
+ // Configure WebSocket.
+ if n.config.WSHost != "" {
+ server := n.wsServerForPort(n.config.WSPort)
+ config := wsConfig{
+ Modules: n.config.WSModules,
+ Origins: n.config.WSOrigins,
+ }
+ if err := server.setListenAddr(n.config.WSHost, n.config.WSPort); err != nil {
+ return err
+ }
+ if err := server.enableWS(n.rpcAPIs, config); err != nil {
+ return err
+ }
+ }
+
+ if err := n.http.start(); err != nil {
+ return err
+ }
+ return n.ws.start()
}
-// startInProc initializes an in-process RPC endpoint.
-func (n *Node) startInProc(apis []rpc.API) error {
- // Register all the APIs exposed by the services
- handler := rpc.NewServer()
- for _, api := range apis {
- if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
+func (n *Node) wsServerForPort(port int) *httpServer {
+ if n.config.HTTPHost == "" || n.http.port == port {
+ return n.http
+ }
+ return n.ws
+}
+
+func (n *Node) stopRPC() {
+ n.http.stop()
+ n.ws.stop()
+ n.ipc.stop()
+ n.stopInProc()
+}
+
+// startInProc registers all RPC APIs on the inproc server.
+func (n *Node) startInProc() error {
+ for _, api := range n.rpcAPIs {
+ if err := n.inprocHandler.RegisterName(api.Namespace, api.Service); err != nil {
return err
}
- n.log.Debug("InProc registered", "namespace", api.Namespace)
}
- n.inprocHandler = handler
return nil
}
// stopInProc terminates the in-process RPC endpoint.
func (n *Node) stopInProc() {
- if n.inprocHandler != nil {
- n.inprocHandler.Stop()
- n.inprocHandler = nil
+ n.inprocHandler.Stop()
+}
+
+// Wait blocks until the node is closed.
+func (n *Node) Wait() {
+ <-n.stop
+}
+
+// RegisterLifecycle registers the given Lifecycle on the node.
+func (n *Node) RegisterLifecycle(lifecycle Lifecycle) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+
+ if n.state != initializingState {
+ panic("can't register lifecycle on running/stopped node")
}
+ if containsLifecycle(n.lifecycles, lifecycle) {
+ panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle))
+ }
+ n.lifecycles = append(n.lifecycles, lifecycle)
+}
+
+// RegisterProtocols adds backend's protocols to the node's p2p server.
+func (n *Node) RegisterProtocols(protocols []p2p.Protocol) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+
+ if n.state != initializingState {
+ panic("can't register protocols on running/stopped node")
+ }
+ n.server.Protocols = append(n.server.Protocols, protocols...)
}
// RegisterAPIs registers the APIs a service provides on the node.
@@ -360,221 +457,32 @@ func (n *Node) RegisterAPIs(apis []rpc.API) {
n.rpcAPIs = append(n.rpcAPIs, apis...)
}
-// startIPC initializes and starts the IPC RPC endpoint.
-func (n *Node) startIPC(apis []rpc.API) error {
- if n.ipcEndpoint == "" {
- return nil // IPC disabled.
- }
- listener, handler, err := rpc.StartIPCEndpoint(n.ipcEndpoint, apis)
- if err != nil {
- return err
- }
- n.ipcListener = listener
- n.ipcHandler = handler
- log.Info("IPC endpoint opened", "url", n.ipcEndpoint)
- return nil
-}
-
-// stopIPC terminates the IPC RPC endpoint.
-func (n *Node) stopIPC() {
- if n.ipcListener != nil {
- n.ipcListener.Close()
- n.ipcListener = nil
-
- n.log.Info("IPC endpoint closed", "url", n.ipcEndpoint)
- }
- if n.ipcHandler != nil {
- n.ipcHandler.Stop()
- n.ipcHandler = nil
- }
-}
-
-// startHTTP initializes and starts the HTTP RPC endpoint.
-func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error {
- // Short circuit if the HTTP endpoint isn't being exposed
- if endpoint == "" {
- return nil
- }
- // register apis and create handler stack
- srv := rpc.NewServer()
- err := RegisterApisFromWhitelist(apis, modules, srv, false)
- if err != nil {
- return err
- }
- handler := NewHTTPHandlerStack(srv, cors, vhosts, &timeouts)
- // wrap handler in WebSocket handler only if WebSocket port is the same as http rpc
- if n.httpEndpoint == n.wsEndpoint {
- handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins))
- }
- httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler)
- if err != nil {
- return err
- }
- n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr),
- "cors", strings.Join(cors, ","),
- "vhosts", strings.Join(vhosts, ","))
- if n.httpEndpoint == n.wsEndpoint {
- n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr))
- }
- // All listeners booted successfully
- n.httpEndpoint = endpoint
- n.httpListenerAddr = addr
- n.httpServer = httpServer
- n.httpHandler = srv
-
- return nil
-}
-
-// stopHTTP terminates the HTTP RPC endpoint.
-func (n *Node) stopHTTP() {
- if n.httpServer != nil {
- // Don't bother imposing a timeout here.
- n.httpServer.Shutdown(context.Background())
- n.log.Info("HTTP endpoint closed", "url", fmt.Sprintf("http://%v/", n.httpListenerAddr))
- }
- if n.httpHandler != nil {
- n.httpHandler.Stop()
- n.httpHandler = nil
- }
-}
-
-// startWS initializes and starts the WebSocket RPC endpoint.
-func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool) error {
- // Short circuit if the WS endpoint isn't being exposed
- if endpoint == "" {
- return nil
- }
-
- srv := rpc.NewServer()
- handler := srv.WebsocketHandler(wsOrigins)
- err := RegisterApisFromWhitelist(apis, modules, srv, exposeAll)
- if err != nil {
- return err
- }
- httpServer, addr, err := startWSEndpoint(endpoint, handler)
- if err != nil {
- return err
- }
- n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr))
- // All listeners booted successfully
- n.wsEndpoint = endpoint
- n.wsListenerAddr = addr
- n.wsHTTPServer = httpServer
- n.wsHandler = srv
-
- return nil
-}
-
-// stopWS terminates the WebSocket RPC endpoint.
-func (n *Node) stopWS() {
- if n.wsHTTPServer != nil {
- // Don't bother imposing a timeout here.
- n.wsHTTPServer.Shutdown(context.Background())
- n.log.Info("WebSocket endpoint closed", "url", fmt.Sprintf("ws://%v", n.wsListenerAddr))
- }
- if n.wsHandler != nil {
- n.wsHandler.Stop()
- n.wsHandler = nil
- }
-}
-
-// Stop terminates a running node along with all it's services. In the node was
-// not started, an error is returned.
-func (n *Node) Stop() error {
+// RegisterHandler mounts a handler on the given path on the canonical HTTP server.
+//
+// The name of the handler is shown in a log message when the HTTP server starts
+// and should be a descriptive term for the service provided by the handler.
+func (n *Node) RegisterHandler(name, path string, handler http.Handler) {
n.lock.Lock()
defer n.lock.Unlock()
- // Short circuit if the node's not running
- if n.server == nil {
- return ErrNodeStopped
+ if n.state != initializingState {
+ panic("can't register HTTP handler on running/stopped node")
}
-
- // Terminate the API, services and the p2p server.
- n.stopWS()
- n.stopHTTP()
- n.stopIPC()
- n.rpcAPIs = nil
- failure := &StopError{
- Services: make(map[reflect.Type]error),
- }
- for kind, service := range n.services {
- if err := service.Stop(); err != nil {
- failure.Services[kind] = err
- }
- }
- n.server.Stop()
- n.services = nil
- n.server = nil
-
- // Release instance directory lock.
- if n.instanceDirLock != nil {
- if err := n.instanceDirLock.Release(); err != nil {
- n.log.Error("Can't release datadir lock", "err", err)
- }
- n.instanceDirLock = nil
- }
-
- // unblock n.Wait
- close(n.stop)
-
- // Remove the keystore if it was created ephemerally.
- var keystoreErr error
- if n.ephemeralKeystore != "" {
- keystoreErr = os.RemoveAll(n.ephemeralKeystore)
- }
-
- if len(failure.Services) > 0 {
- return failure
- }
- if keystoreErr != nil {
- return keystoreErr
- }
- return nil
-}
-
-// Wait blocks the thread until the node is stopped. If the node is not running
-// at the time of invocation, the method immediately returns.
-func (n *Node) Wait() {
- n.lock.RLock()
- if n.server == nil {
- n.lock.RUnlock()
- return
- }
- stop := n.stop
- n.lock.RUnlock()
-
- <-stop
-}
-
-// Restart terminates a running node and boots up a new one in its place. If the
-// node isn't running, an error is returned.
-func (n *Node) Restart() error {
- if err := n.Stop(); err != nil {
- return err
- }
- if err := n.Start(); err != nil {
- return err
- }
- return nil
+ n.http.mux.Handle(path, handler)
+ n.http.handlerNames[path] = name
}
// Attach creates an RPC client attached to an in-process API handler.
func (n *Node) Attach() (*rpc.Client, error) {
- n.lock.RLock()
- defer n.lock.RUnlock()
-
- if n.server == nil {
- return nil, ErrNodeStopped
- }
return rpc.DialInProc(n.inprocHandler), nil
}
// RPCHandler returns the in-process RPC request handler.
func (n *Node) RPCHandler() (*rpc.Server, error) {
- n.lock.RLock()
- defer n.lock.RUnlock()
+ n.lock.Lock()
+ defer n.lock.Unlock()
- if n.inprocHandler == nil {
+ if n.state == closedState {
return nil, ErrNodeStopped
}
return n.inprocHandler, nil
@@ -586,33 +494,15 @@ func (n *Node) Config() *Config {
}
// Server retrieves the currently running P2P network layer. This method is meant
-// only to inspect fields of the currently running server, life cycle management
-// should be left to this Node entity.
+// only to inspect fields of the currently running server. Callers should not
+// start or stop the returned server.
func (n *Node) Server() *p2p.Server {
- n.lock.RLock()
- defer n.lock.RUnlock()
+ n.lock.Lock()
+ defer n.lock.Unlock()
return n.server
}
-// Service retrieves a currently running service registered of a specific type.
-func (n *Node) Service(service interface{}) error {
- n.lock.RLock()
- defer n.lock.RUnlock()
-
- // Short circuit if the node's not running
- if n.server == nil {
- return ErrNodeStopped
- }
- // Otherwise try to find the service to return
- element := reflect.ValueOf(service).Elem()
- if running, ok := n.services[element.Type()]; ok {
- element.Set(reflect.ValueOf(running))
- return nil
- }
- return ErrServiceUnknown
-}
-
// DataDir retrieves the current datadir used by the protocol stack.
// Deprecated: No files should be stored in this directory, use InstanceDir instead.
func (n *Node) DataDir() string {
@@ -636,29 +526,20 @@ func (n *Node) AccountManager() *accounts.Manager {
// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack.
func (n *Node) IPCEndpoint() string {
- return n.ipcEndpoint
+ return n.ipc.endpoint
}
// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack.
func (n *Node) HTTPEndpoint() string {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- if n.httpListenerAddr != nil {
- return n.httpListenerAddr.String()
- }
- return n.httpEndpoint
+ return "http://" + n.http.listenAddr()
}
// WSEndpoint retrieves the current WS endpoint used by the protocol stack.
func (n *Node) WSEndpoint() string {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- if n.wsListenerAddr != nil {
- return n.wsListenerAddr.String()
+ if n.http.wsAllowed() {
+ return "ws://" + n.http.listenAddr()
}
- return n.wsEndpoint
+ return "ws://" + n.ws.listenAddr()
}
// EventMux retrieves the event multiplexer used by all the network services in
@@ -671,10 +552,24 @@ func (n *Node) EventMux() *event.TypeMux {
// previous can be found) from within the node's instance directory. If the node is
// ephemeral, a memory database is returned.
func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) {
- if n.config.DataDir == "" {
- return rawdb.NewMemoryDatabase(), nil
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ if n.state == closedState {
+ return nil, ErrNodeStopped
}
- return rawdb.NewLevelDBDatabase(n.config.ResolvePath(name), cache, handles, namespace, readonly)
+
+ var db ethdb.Database
+ var err error
+ if n.config.DataDir == "" {
+ db = rawdb.NewMemoryDatabase()
+ } else {
+ db, err = rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace, readonly)
+ }
+
+ if err == nil {
+ db = n.wrapDatabase(db)
+ }
+ return db, err
}
// ResolvePath returns the absolute path of a resource in the instance directory.
@@ -682,49 +577,35 @@ func (n *Node) ResolvePath(x string) string {
return n.config.ResolvePath(x)
}
-// apis returns the collection of RPC descriptors this node offers.
-func (n *Node) apis() []rpc.API {
- return []rpc.API{
- {
- Namespace: "admin",
- Version: "1.0",
- Service: NewPrivateAdminAPI(n),
- }, {
- Namespace: "admin",
- Version: "1.0",
- Service: NewPublicAdminAPI(n),
- Public: true,
- }, {
- Namespace: "debug",
- Version: "1.0",
- Service: debug.Handler,
- }, {
- Namespace: "web3",
- Version: "1.0",
- Service: NewPublicWeb3API(n),
- Public: true,
- },
- }
+// closeTrackingDB wraps the Close method of a database. When the database is closed by the
+// service, the wrapper removes it from the node's database map. This ensures that Node
+// won't auto-close the database if it is closed by the service that opened it.
+type closeTrackingDB struct {
+ ethdb.Database
+ n *Node
}
-// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules,
-// and then registers all of the APIs exposed by the services.
-func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error {
- if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 {
- log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available)
- }
- // Generate the whitelist based on the allowed modules
- whitelist := make(map[string]bool)
- for _, module := range modules {
- whitelist[module] = true
- }
- // Register all the APIs exposed by the services
- for _, api := range apis {
- if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
- if err := srv.RegisterName(api.Namespace, api.Service); err != nil {
- return err
- }
+func (db *closeTrackingDB) Close() error {
+ db.n.lock.Lock()
+ delete(db.n.databases, db)
+ db.n.lock.Unlock()
+ return db.Database.Close()
+}
+
+// wrapDatabase ensures the database will be auto-closed when Node is closed.
+func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database {
+ wrapper := &closeTrackingDB{db, n}
+ n.databases[wrapper] = struct{}{}
+ return wrapper
+}
+
+// closeDatabases closes all open databases.
+func (n *Node) closeDatabases() (errors []error) {
+ for db := range n.databases {
+ delete(n.databases, db)
+ if err := db.Database.Close(); err != nil {
+ errors = append(errors, err)
}
}
- return nil
+ return errors
}
diff --git a/node/node_example_test.go b/node/node_example_test.go
index c16aad73ab..c083dad71e 100644
--- a/node/node_example_test.go
+++ b/node/node_example_test.go
@@ -21,26 +21,20 @@ import (
"log"
"github.com/XinFinOrg/XDPoSChain/node"
- "github.com/XinFinOrg/XDPoSChain/p2p"
- "github.com/XinFinOrg/XDPoSChain/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.
//
-// The following methods are needed to implement a node.Service:
-// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on
-// - APIs() []rpc.API - api methods the service wants to expose on rpc channels
+// The following methods are needed to implement a node.Lifecycle:
// - Start() error - method invoked when the node is ready to start the service
// - Stop() error - method invoked when the node terminates the service
-type SampleService struct{}
+type SampleLifecycle struct{}
-func (s *SampleService) Protocols() []p2p.Protocol { return nil }
-func (s *SampleService) APIs() []rpc.API { 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 (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil }
+func (s *SampleLifecycle) Stop() error { fmt.Println("Service stopping..."); return nil }
-func ExampleService() {
+func ExampleLifecycle() {
// Create a network node to run protocols with the default values.
stack, err := node.New(&node.Config{})
if err != nil {
@@ -48,29 +42,19 @@ func ExampleService() {
}
defer stack.Close()
- // Create and register a simple network service. This is done through the definition
- // of a node.ServiceConstructor that will instantiate a node.Service. The reason for
- // the factory method approach is to support service restarts without relying on the
- // individual implementations' support for such operations.
- constructor := func(context *node.ServiceContext) (node.Service, error) {
- return new(SampleService), nil
- }
- if err := stack.Register(constructor); err != nil {
- log.Fatalf("Failed to register service: %v", err)
- }
+ // Create and register a simple network Lifecycle.
+ service := new(SampleLifecycle)
+
+ stack.RegisterLifecycle(service)
+
// Boot up the entire protocol stack, do a restart and terminate
if err := stack.Start(); err != nil {
log.Fatalf("Failed to start the protocol stack: %v", err)
}
- if err := stack.Restart(); err != nil {
- log.Fatalf("Failed to restart the protocol stack: %v", err)
- }
- if err := stack.Stop(); err != nil {
+ if err := stack.Close(); err != nil {
log.Fatalf("Failed to stop the protocol stack: %v", err)
}
// Output:
// Service starting...
// Service stopping...
- // Service starting...
- // Service stopping...
}
diff --git a/node/node_test.go b/node/node_test.go
index 6e6ea3be79..7118f3643b 100644
--- a/node/node_test.go
+++ b/node/node_test.go
@@ -18,12 +18,16 @@ package node
import (
"errors"
+ "fmt"
+ "io"
+ "net"
"net/http"
"reflect"
+ "strings"
"testing"
- "time"
"github.com/XinFinOrg/XDPoSChain/crypto"
+ "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/stretchr/testify/assert"
@@ -40,20 +44,27 @@ func testNodeConfig() *Config {
}
}
-// Tests that an empty protocol stack can be started, restarted and stopped.
-func TestNodeLifeCycle(t *testing.T) {
+// Tests that an empty protocol stack can be closed more than once.
+func TestNodeCloseMultipleTimes(t *testing.T) {
stack, err := New(testNodeConfig())
if err != nil {
t.Fatalf("failed to create protocol stack: %v", err)
}
- defer stack.Close()
+ stack.Close()
// Ensure that a stopped node can be stopped again
for i := 0; i < 3; i++ {
- if err := stack.Stop(); err != ErrNodeStopped {
+ if err := stack.Close(); err != ErrNodeStopped {
t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped)
}
}
+}
+
+func TestNodeStartMultipleTimes(t *testing.T) {
+ stack, err := New(testNodeConfig())
+ if err != nil {
+ t.Fatalf("failed to create protocol stack: %v", err)
+ }
// Ensure that a node can be successfully started, but only once
if err := stack.Start(); err != nil {
t.Fatalf("failed to start node: %v", err)
@@ -61,17 +72,12 @@ func TestNodeLifeCycle(t *testing.T) {
if err := stack.Start(); err != ErrNodeRunning {
t.Fatalf("start failure mismatch: have %v, want %v", err, ErrNodeRunning)
}
- // Ensure that a node can be restarted arbitrarily many times
- for i := 0; i < 3; i++ {
- if err := stack.Restart(); err != nil {
- t.Fatalf("iter %d: failed to restart node: %v", i, err)
- }
- }
+
// Ensure that a node can be stopped, but only once
- if err := stack.Stop(); err != nil {
+ if err := stack.Close(); err != nil {
t.Fatalf("failed to stop node: %v", err)
}
- if err := stack.Stop(); err != ErrNodeStopped {
+ if err := stack.Close(); err != ErrNodeStopped {
t.Fatalf("stop failure mismatch: have %v, want %v", err, ErrNodeStopped)
}
}
@@ -91,88 +97,130 @@ func TestNodeUsedDataDir(t *testing.T) {
if err := original.Start(); err != nil {
t.Fatalf("failed to start original protocol stack: %v", err)
}
- defer original.Stop()
// Create a second node based on the same data directory and ensure failure
- duplicate, err := New(&Config{DataDir: dir})
- if err != nil {
- t.Fatalf("failed to create duplicate protocol stack: %v", err)
- }
- defer duplicate.Close()
-
- if err := duplicate.Start(); err != ErrDatadirUsed {
+ _, err = New(&Config{DataDir: dir})
+ if err != ErrDatadirUsed {
t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed)
}
}
-// Tests whether services can be registered and duplicates caught.
-func TestServiceRegistry(t *testing.T) {
+// Tests whether a Lifecycle can be registered.
+func TestLifecycleRegistry_Successful(t *testing.T) {
stack, err := New(testNodeConfig())
if err != nil {
t.Fatalf("failed to create protocol stack: %v", err)
}
defer stack.Close()
- // Register a batch of unique services and ensure they start successfully
- services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC}
- for i, constructor := range services {
- if err := stack.Register(constructor); err != nil {
- t.Fatalf("service #%d: registration failed: %v", i, err)
+ noop := NewNoop()
+ stack.RegisterLifecycle(noop)
+
+ if !containsLifecycle(stack.lifecycles, noop) {
+ t.Fatalf("lifecycle was not properly registered on the node, %v", err)
+ }
+}
+
+// Tests whether a service's protocols can be registered properly on the node's p2p server.
+func TestRegisterProtocols(t *testing.T) {
+ stack, err := New(testNodeConfig())
+ if err != nil {
+ t.Fatalf("failed to create protocol stack: %v", err)
+ }
+ defer stack.Close()
+
+ fs, err := NewFullService(stack)
+ if err != nil {
+ t.Fatalf("could not create full service: %v", err)
+ }
+
+ for _, protocol := range fs.Protocols() {
+ if !containsProtocol(stack.server.Protocols, protocol) {
+ t.Fatalf("protocol %v was not successfully registered", protocol)
}
}
- if err := stack.Start(); err != nil {
- t.Fatalf("failed to start original service stack: %v", err)
- }
- if err := stack.Stop(); err != nil {
- t.Fatalf("failed to stop original service stack: %v", err)
- }
- // Duplicate one of the services and retry starting the node
- if err := stack.Register(NewNoopServiceB); err != nil {
- t.Fatalf("duplicate registration failed: %v", err)
- }
- if err := stack.Start(); err == nil {
- t.Fatalf("duplicate service started")
- } else {
- if _, ok := err.(*DuplicateServiceError); !ok {
- t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{})
+
+ for _, api := range fs.APIs() {
+ if !containsAPI(stack.rpcAPIs, api) {
+ t.Fatalf("api %v was not successfully registered", api)
}
}
}
-// Tests that registered services get started and stopped correctly.
-func TestServiceLifeCycle(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
+// This test checks that open databases are closed with node.
+func TestNodeCloseClosesDB(t *testing.T) {
+ stack, _ := New(testNodeConfig())
defer stack.Close()
- // Register a batch of life-cycle instrumented services
- services := map[string]InstrumentingWrapper{
- "A": InstrumentedServiceMakerA,
- "B": InstrumentedServiceMakerB,
- "C": InstrumentedServiceMakerC,
+ db, err := stack.OpenDatabase("mydb", 0, 0, "", false)
+ if err != nil {
+ t.Fatal("can't open DB:", err)
}
+ if err = db.Put([]byte{}, []byte{}); err != nil {
+ t.Fatal("can't Put on open DB:", err)
+ }
+
+ stack.Close()
+ if err = db.Put([]byte{}, []byte{}); err == nil {
+ t.Fatal("Put succeeded after node is closed")
+ }
+}
+
+// This test checks that OpenDatabase can be used from within a Lifecycle Start method.
+func TestNodeOpenDatabaseFromLifecycleStart(t *testing.T) {
+ stack, _ := New(testNodeConfig())
+ defer stack.Close()
+
+ var db ethdb.Database
+ var err error
+ stack.RegisterLifecycle(&InstrumentedService{
+ startHook: func() {
+ db, err = stack.OpenDatabase("mydb", 0, 0, "", false)
+ if err != nil {
+ t.Fatal("can't open DB:", err)
+ }
+ },
+ stopHook: func() {
+ db.Close()
+ },
+ })
+
+ stack.Start()
+ stack.Close()
+}
+
+// Tests that registered Lifecycles get started and stopped correctly.
+func TestLifecycleLifeCycle(t *testing.T) {
+ stack, _ := New(testNodeConfig())
+ defer stack.Close()
+
started := make(map[string]bool)
stopped := make(map[string]bool)
- for id, maker := range services {
- id := id // Closure for the constructor
- constructor := func(*ServiceContext) (Service, error) {
- return &InstrumentedService{
- startHook: func(*p2p.Server) { started[id] = true },
- stopHook: func() { stopped[id] = true },
- }, nil
- }
- if err := stack.Register(maker(constructor)); err != nil {
- t.Fatalf("service %s: registration failed: %v", id, err)
- }
+ // Create a batch of instrumented services
+ lifecycles := map[string]Lifecycle{
+ "A": &InstrumentedService{
+ startHook: func() { started["A"] = true },
+ stopHook: func() { stopped["A"] = true },
+ },
+ "B": &InstrumentedService{
+ startHook: func() { started["B"] = true },
+ stopHook: func() { stopped["B"] = true },
+ },
+ "C": &InstrumentedService{
+ startHook: func() { started["C"] = true },
+ stopHook: func() { stopped["C"] = true },
+ },
+ }
+ // register lifecycles on node
+ for _, lifecycle := range lifecycles {
+ stack.RegisterLifecycle(lifecycle)
}
// Start the node and check that all services are running
if err := stack.Start(); err != nil {
t.Fatalf("failed to start protocol stack: %v", err)
}
- for id := range services {
+ for id := range lifecycles {
if !started[id] {
t.Fatalf("service %s: freshly started service not running", id)
}
@@ -181,471 +229,283 @@ func TestServiceLifeCycle(t *testing.T) {
}
}
// Stop the node and check that all services have been stopped
- if err := stack.Stop(); err != nil {
+ if err := stack.Close(); err != nil {
t.Fatalf("failed to stop protocol stack: %v", err)
}
- for id := range services {
+ for id := range lifecycles {
if !stopped[id] {
t.Fatalf("service %s: freshly terminated service still running", id)
}
}
}
-// Tests that services are restarted cleanly as new instances.
-func TestServiceRestarts(t *testing.T) {
+// Tests that if a Lifecycle fails to start, all others started before it will be
+// shut down.
+func TestLifecycleStartupError(t *testing.T) {
stack, err := New(testNodeConfig())
if err != nil {
t.Fatalf("failed to create protocol stack: %v", err)
}
defer stack.Close()
- // Define a service that does not support restarts
- var (
- running bool
- started int
- )
- constructor := func(*ServiceContext) (Service, error) {
- running = false
-
- return &InstrumentedService{
- startHook: func(*p2p.Server) {
- if running {
- panic("already running")
- }
- running = true
- started++
- },
- }, nil
- }
- // Register the service and start the protocol stack
- if err := stack.Register(constructor); err != nil {
- t.Fatalf("failed to register the service: %v", err)
- }
- if err := stack.Start(); err != nil {
- t.Fatalf("failed to start protocol stack: %v", err)
- }
- defer stack.Stop()
-
- if !running || started != 1 {
- t.Fatalf("running/started mismatch: have %v/%d, want true/1", running, started)
- }
- // Restart the stack a few times and check successful service restarts
- for i := 0; i < 3; i++ {
- if err := stack.Restart(); err != nil {
- t.Fatalf("iter %d: failed to restart stack: %v", i, err)
- }
- }
- if !running || started != 4 {
- t.Fatalf("running/started mismatch: have %v/%d, want true/4", running, started)
- }
-}
-
-// Tests that if a service fails to initialize itself, none of the other services
-// will be allowed to even start.
-func TestServiceConstructionAbortion(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
-
- // Define a batch of good services
- services := map[string]InstrumentingWrapper{
- "A": InstrumentedServiceMakerA,
- "B": InstrumentedServiceMakerB,
- "C": InstrumentedServiceMakerC,
- }
started := make(map[string]bool)
- for id, maker := range services {
- id := id // Closure for the constructor
- constructor := func(*ServiceContext) (Service, error) {
- return &InstrumentedService{
- startHook: func(*p2p.Server) { started[id] = true },
- }, nil
- }
- if err := stack.Register(maker(constructor)); err != nil {
- t.Fatalf("service %s: registration failed: %v", id, err)
- }
+ stopped := make(map[string]bool)
+
+ // Create a batch of instrumented services
+ lifecycles := map[string]Lifecycle{
+ "A": &InstrumentedService{
+ startHook: func() { started["A"] = true },
+ stopHook: func() { stopped["A"] = true },
+ },
+ "B": &InstrumentedService{
+ startHook: func() { started["B"] = true },
+ stopHook: func() { stopped["B"] = true },
+ },
+ "C": &InstrumentedService{
+ startHook: func() { started["C"] = true },
+ stopHook: func() { stopped["C"] = true },
+ },
}
+ // register lifecycles on node
+ for _, lifecycle := range lifecycles {
+ stack.RegisterLifecycle(lifecycle)
+ }
+
// Register a service that fails to construct itself
failure := errors.New("fail")
- failer := func(*ServiceContext) (Service, error) {
- return nil, failure
- }
- if err := stack.Register(failer); err != nil {
- t.Fatalf("failer registration failed: %v", err)
- }
- // Start the protocol stack and ensure none of the services get started
- for i := 0; i < 100; i++ {
- if err := stack.Start(); err != failure {
- t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure)
- }
- for id := range services {
- if started[id] {
- t.Fatalf("service %s: started should not have", id)
- }
- delete(started, id)
- }
- }
-}
+ failer := &InstrumentedService{start: failure}
+ stack.RegisterLifecycle(failer)
-// Tests that if a service fails to start, all others started before it will be
-// shut down.
-func TestServiceStartupAbortion(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
-
- // Register a batch of good services
- services := map[string]InstrumentingWrapper{
- "A": InstrumentedServiceMakerA,
- "B": InstrumentedServiceMakerB,
- "C": InstrumentedServiceMakerC,
- }
- started := make(map[string]bool)
- stopped := make(map[string]bool)
-
- for id, maker := range services {
- id := id // Closure for the constructor
- constructor := func(*ServiceContext) (Service, error) {
- return &InstrumentedService{
- startHook: func(*p2p.Server) { started[id] = true },
- stopHook: func() { stopped[id] = true },
- }, nil
- }
- if err := stack.Register(maker(constructor)); err != nil {
- t.Fatalf("service %s: registration failed: %v", id, err)
- }
- }
- // Register a service that fails to start
- failure := errors.New("fail")
- failer := func(*ServiceContext) (Service, error) {
- return &InstrumentedService{
- start: failure,
- }, nil
- }
- if err := stack.Register(failer); err != nil {
- t.Fatalf("failer registration failed: %v", err)
- }
// Start the protocol stack and ensure all started services stop
- for i := 0; i < 100; i++ {
- if err := stack.Start(); err != failure {
- t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure)
- }
- for id := range services {
- if started[id] && !stopped[id] {
- t.Fatalf("service %s: started but not stopped", id)
- }
- delete(started, id)
- delete(stopped, id)
+ if err := stack.Start(); err != failure {
+ t.Fatalf("stack startup failure mismatch: have %v, want %v", err, failure)
+ }
+ for id := range lifecycles {
+ if started[id] && !stopped[id] {
+ t.Fatalf("service %s: started but not stopped", id)
}
+ delete(started, id)
+ delete(stopped, id)
}
}
-// Tests that even if a registered service fails to shut down cleanly, it does
+// Tests that even if a registered Lifecycle fails to shut down cleanly, it does
// not influence the rest of the shutdown invocations.
-func TestServiceTerminationGuarantee(t *testing.T) {
+func TestLifecycleTerminationGuarantee(t *testing.T) {
stack, err := New(testNodeConfig())
if err != nil {
t.Fatalf("failed to create protocol stack: %v", err)
}
defer stack.Close()
- // Register a batch of good services
- services := map[string]InstrumentingWrapper{
- "A": InstrumentedServiceMakerA,
- "B": InstrumentedServiceMakerB,
- "C": InstrumentedServiceMakerC,
- }
started := make(map[string]bool)
stopped := make(map[string]bool)
- for id, maker := range services {
- id := id // Closure for the constructor
- constructor := func(*ServiceContext) (Service, error) {
- return &InstrumentedService{
- startHook: func(*p2p.Server) { started[id] = true },
- stopHook: func() { stopped[id] = true },
- }, nil
- }
- if err := stack.Register(maker(constructor)); err != nil {
- t.Fatalf("service %s: registration failed: %v", id, err)
- }
+ // Create a batch of instrumented services
+ lifecycles := map[string]Lifecycle{
+ "A": &InstrumentedService{
+ startHook: func() { started["A"] = true },
+ stopHook: func() { stopped["A"] = true },
+ },
+ "B": &InstrumentedService{
+ startHook: func() { started["B"] = true },
+ stopHook: func() { stopped["B"] = true },
+ },
+ "C": &InstrumentedService{
+ startHook: func() { started["C"] = true },
+ stopHook: func() { stopped["C"] = true },
+ },
+ }
+ // register lifecycles on node
+ for _, lifecycle := range lifecycles {
+ stack.RegisterLifecycle(lifecycle)
}
// Register a service that fails to shot down cleanly
failure := errors.New("fail")
- failer := func(*ServiceContext) (Service, error) {
- return &InstrumentedService{
- stop: failure,
- }, nil
- }
- if err := stack.Register(failer); err != nil {
- t.Fatalf("failer registration failed: %v", err)
- }
+ failer := &InstrumentedService{stop: failure}
+ stack.RegisterLifecycle(failer)
// Start the protocol stack, and ensure that a failing shut down terminates all
- for i := 0; i < 100; i++ {
- // Start the stack and make sure all is online
- if err := stack.Start(); err != nil {
- t.Fatalf("iter %d: failed to start protocol stack: %v", i, err)
- }
- for id := range services {
- if !started[id] {
- t.Fatalf("iter %d, service %s: service not running", i, id)
- }
- if stopped[id] {
- t.Fatalf("iter %d, service %s: service already stopped", i, id)
- }
- }
- // Stop the stack, verify failure and check all terminations
- err := stack.Stop()
- if err, ok := err.(*StopError); !ok {
- t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err)
- } else {
- failer := reflect.TypeOf(&InstrumentedService{})
- if err.Services[failer] != failure {
- t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services[failer], failure)
- }
- if len(err.Services) != 1 {
- t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1)
- }
- }
- for id := range services {
- if !stopped[id] {
- t.Fatalf("iter %d, service %s: service not terminated", i, id)
- }
- delete(started, id)
- delete(stopped, id)
- }
- }
-}
-
-// TestServiceRetrieval tests that individual services can be retrieved.
-func TestServiceRetrieval(t *testing.T) {
- // Create a simple stack and register two service types
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
-
- if err := stack.Register(NewNoopService); err != nil {
- t.Fatalf("noop service registration failed: %v", err)
- }
- if err := stack.Register(NewInstrumentedService); err != nil {
- t.Fatalf("instrumented service registration failed: %v", err)
- }
- // Make sure none of the services can be retrieved until started
- var noopServ *NoopService
- if err := stack.Service(&noopServ); err != ErrNodeStopped {
- t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped)
- }
- var instServ *InstrumentedService
- if err := stack.Service(&instServ); err != ErrNodeStopped {
- t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped)
- }
- // Start the stack and ensure everything is retrievable now
- if err := stack.Start(); err != nil {
- t.Fatalf("failed to start stack: %v", err)
- }
- defer stack.Stop()
-
- if err := stack.Service(&noopServ); err != nil {
- t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil)
- }
- if err := stack.Service(&instServ); err != nil {
- t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil)
- }
-}
-
-// Tests that all protocols defined by individual services get launched.
-func TestProtocolGather(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
-
- // Register a batch of services with some configured number of protocols
- services := map[string]struct {
- Count int
- Maker InstrumentingWrapper
- }{
- "Zero Protocols": {0, InstrumentedServiceMakerA},
- "Single Protocol": {1, InstrumentedServiceMakerB},
- "Many Protocols": {25, InstrumentedServiceMakerC},
- }
- for id, config := range services {
- protocols := make([]p2p.Protocol, config.Count)
- for i := 0; i < len(protocols); i++ {
- protocols[i].Name = id
- protocols[i].Version = uint(i)
- }
- constructor := func(*ServiceContext) (Service, error) {
- return &InstrumentedService{
- protocols: protocols,
- }, nil
- }
- if err := stack.Register(config.Maker(constructor)); err != nil {
- t.Fatalf("service %s: registration failed: %v", id, err)
- }
- }
- // Start the services and ensure all protocols start successfully
+ // Start the stack and make sure all is online
if err := stack.Start(); err != nil {
t.Fatalf("failed to start protocol stack: %v", err)
}
- defer stack.Stop()
-
- protocols := stack.Server().Protocols
- if len(protocols) != 26 {
- t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26)
- }
- for id, config := range services {
- for ver := 0; ver < config.Count; ver++ {
- launched := false
- for i := 0; i < len(protocols); i++ {
- if protocols[i].Name == id && protocols[i].Version == uint(ver) {
- launched = true
- break
- }
- }
- if !launched {
- t.Errorf("configured protocol not launched: %s v%d", id, ver)
- }
+ for id := range lifecycles {
+ if !started[id] {
+ t.Fatalf("service %s: service not running", id)
+ }
+ if stopped[id] {
+ t.Fatalf("service %s: service already stopped", id)
}
}
+ // Stop the stack, verify failure and check all terminations
+ err = stack.Close()
+ if err, ok := err.(*StopError); !ok {
+ t.Fatalf("termination failure mismatch: have %v, want StopError", err)
+ } else {
+ failer := reflect.TypeOf(&InstrumentedService{})
+ if err.Services[failer] != failure {
+ t.Fatalf("failer termination failure mismatch: have %v, want %v", err.Services[failer], failure)
+ }
+ if len(err.Services) != 1 {
+ t.Fatalf("failure count mismatch: have %d, want %d", len(err.Services), 1)
+ }
+ }
+ for id := range lifecycles {
+ if !stopped[id] {
+ t.Fatalf("service %s: service not terminated", id)
+ }
+ delete(started, id)
+ delete(stopped, id)
+ }
+
+ stack.server = &p2p.Server{}
+ stack.server.PrivateKey = testNodeKey
}
-// Tests that all APIs defined by individual services get exposed.
-func TestAPIGather(t *testing.T) {
- stack, err := New(testNodeConfig())
- if err != nil {
- t.Fatalf("failed to create protocol stack: %v", err)
- }
- defer stack.Close()
+// Tests whether a handler can be successfully mounted on the canonical HTTP server
+// on the given path
+func TestRegisterHandler_Successful(t *testing.T) {
+ node := createNode(t, 7878, 7979)
- // Register a batch of services with some configured APIs
- calls := make(chan string, 1)
- makeAPI := func(result string) *OneMethodAPI {
- return &OneMethodAPI{fun: func() { calls <- result }}
- }
- services := map[string]struct {
- APIs []rpc.API
- Maker InstrumentingWrapper
- }{
- "Zero APIs": {
- []rpc.API{}, InstrumentedServiceMakerA},
- "Single API": {
- []rpc.API{
- {Namespace: "single", Version: "1", Service: makeAPI("single.v1"), Public: true},
- }, InstrumentedServiceMakerB},
- "Many APIs": {
- []rpc.API{
- {Namespace: "multi", Version: "1", Service: makeAPI("multi.v1"), Public: true},
- {Namespace: "multi.v2", Version: "2", Service: makeAPI("multi.v2"), Public: true},
- {Namespace: "multi.v2.nested", Version: "2", Service: makeAPI("multi.v2.nested"), Public: true},
- }, InstrumentedServiceMakerC},
+ // create and mount handler
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("success"))
+ })
+ node.RegisterHandler("test", "/test", handler)
+
+ // start node
+ if err := node.Start(); err != nil {
+ t.Fatalf("could not start node: %v", err)
}
- for id, config := range services {
- config := config
- constructor := func(*ServiceContext) (Service, error) {
- return &InstrumentedService{apis: config.APIs}, nil
- }
- if err := stack.Register(config.Maker(constructor)); err != nil {
- t.Fatalf("service %s: registration failed: %v", id, err)
- }
- }
- // Start the services and ensure all API start successfully
- if err := stack.Start(); err != nil {
- t.Fatalf("failed to start protocol stack: %v", err)
- }
- defer stack.Stop()
-
- // Connect to the RPC server and verify the various registered endpoints
- client, err := stack.Attach()
- if err != nil {
- t.Fatalf("failed to connect to the inproc API server: %v", err)
- }
- defer client.Close()
-
- tests := []struct {
- Method string
- Result string
- }{
- {"single_theOneMethod", "single.v1"},
- {"multi_theOneMethod", "multi.v1"},
- {"multi.v2_theOneMethod", "multi.v2"},
- {"multi.v2.nested_theOneMethod", "multi.v2.nested"},
- }
- for i, test := range tests {
- if err := client.Call(nil, test.Method); err != nil {
- t.Errorf("test %d: API request failed: %v", i, err)
- }
- select {
- case result := <-calls:
- if result != test.Result {
- t.Errorf("test %d: result mismatch: have %s, want %s", i, result, test.Result)
- }
- case <-time.After(time.Second):
- t.Fatalf("test %d: rpc execution timeout", i)
- }
- }
-}
-
-func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) {
- node := startHTTP(t)
- defer node.stopHTTP()
-
- wsReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil)
+ // create HTTP request
+ httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7878/test", nil)
if err != nil {
t.Error("could not issue new http request ", err)
}
- wsReq.Header.Set("Connection", "upgrade")
- wsReq.Header.Set("Upgrade", "websocket")
- wsReq.Header.Set("Sec-WebSocket-Version", "13")
- wsReq.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==")
-
- resp := doHTTPRequest(t, wsReq)
- assert.Equal(t, "websocket", resp.Header.Get("Upgrade"))
-}
-
-func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) {
- node := startHTTP(t)
- defer node.stopHTTP()
-
- httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil)
- if err != nil {
- t.Error("could not issue new http request ", err)
- }
- httpReq.Header.Set("Accept-Encoding", "gzip")
+ // check response
resp := doHTTPRequest(t, httpReq)
- assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding"))
+ buf := make([]byte, 7)
+ _, err = io.ReadFull(resp.Body, buf)
+ if err != nil {
+ t.Fatalf("could not read response: %v", err)
+ }
+ assert.Equal(t, "success", string(buf))
}
-func startHTTP(t *testing.T) *Node {
- conf := &Config{HTTPPort: 7453, WSPort: 7453}
+// Tests that the given handler will not be successfully mounted since no HTTP server
+// is enabled for RPC
+func TestRegisterHandler_Unsuccessful(t *testing.T) {
+ node, err := New(&DefaultConfig)
+ if err != nil {
+ t.Fatalf("could not create new node: %v", err)
+ }
+
+ // create and mount handler
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("success"))
+ })
+ node.RegisterHandler("test", "/test", handler)
+}
+
+// Tests whether websocket requests can be handled on the same port as a regular http server.
+func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) {
+ node := startHTTP(t, 0, 0)
+ defer node.Close()
+
+ ws := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1)
+
+ if node.WSEndpoint() != ws {
+ t.Fatalf("endpoints should be the same")
+ }
+ if !checkRPC(ws) {
+ t.Fatalf("ws request failed")
+ }
+ if !checkRPC(node.HTTPEndpoint()) {
+ t.Fatalf("http request failed")
+ }
+}
+
+func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) {
+ // try and get a free port
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal("can't listen:", err)
+ }
+ port := listener.Addr().(*net.TCPAddr).Port
+ listener.Close()
+
+ node := startHTTP(t, 0, port)
+ defer node.Close()
+
+ wsOnHTTP := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1)
+ ws := fmt.Sprintf("ws://127.0.0.1:%d", port)
+
+ if node.WSEndpoint() == wsOnHTTP {
+ t.Fatalf("endpoints should not be the same")
+ }
+ // ensure ws endpoint matches the expected endpoint
+ if node.WSEndpoint() != ws {
+ t.Fatalf("ws endpoint is incorrect: expected %s, got %s", ws, node.WSEndpoint())
+ }
+
+ if !checkRPC(ws) {
+ t.Fatalf("ws request failed")
+ }
+ if !checkRPC(node.HTTPEndpoint()) {
+ t.Fatalf("http request failed")
+ }
+}
+
+func createNode(t *testing.T, httpPort, wsPort int) *Node {
+ conf := &Config{
+ HTTPHost: "127.0.0.1",
+ HTTPPort: httpPort,
+ WSHost: "127.0.0.1",
+ WSPort: wsPort,
+ }
node, err := New(conf)
if err != nil {
- t.Error("could not create a new node ", err)
+ t.Fatalf("could not create a new node: %v", err)
}
+ return node
+}
- err = node.startHTTP("127.0.0.1:7453", []rpc.API{}, []string{}, []string{}, []string{}, rpc.HTTPTimeouts{}, []string{})
+func startHTTP(t *testing.T, httpPort, wsPort int) *Node {
+ node := createNode(t, httpPort, wsPort)
+ err := node.Start()
if err != nil {
- t.Error("could not start http service on node ", err)
+ t.Fatalf("could not start http service on node: %v", err)
}
return node
}
func doHTTPRequest(t *testing.T, req *http.Request) *http.Response {
- client := &http.Client{}
+ client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
- t.Error("could not issue a GET request to the given endpoint", err)
+ t.Fatalf("could not issue a GET request to the given endpoint: %v", err)
}
t.Cleanup(func() { resp.Body.Close() })
return resp
}
+
+func containsProtocol(stackProtocols []p2p.Protocol, protocol p2p.Protocol) bool {
+ for _, a := range stackProtocols {
+ if reflect.DeepEqual(a, protocol) {
+ return true
+ }
+ }
+ return false
+}
+
+func containsAPI(stackAPIs []rpc.API, api rpc.API) bool {
+ for _, a := range stackAPIs {
+ if reflect.DeepEqual(a, api) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/node/rpcstack.go b/node/rpcstack.go
index f1792cffc6..ae66bffe7d 100644
--- a/node/rpcstack.go
+++ b/node/rpcstack.go
@@ -18,11 +18,15 @@ package node
import (
"compress/gzip"
+ "context"
+ "fmt"
"io"
"net"
"net/http"
+ "sort"
"strings"
"sync"
+ "sync/atomic"
"time"
"github.com/XinFinOrg/XDPoSChain/log"
@@ -30,6 +34,287 @@ import (
"github.com/rs/cors"
)
+// httpConfig is the JSON-RPC/HTTP configuration.
+type httpConfig struct {
+ Modules []string
+ CorsAllowedOrigins []string
+ Vhosts []string
+}
+
+// wsConfig is the JSON-RPC/Websocket configuration
+type wsConfig struct {
+ Origins []string
+ Modules []string
+}
+
+type rpcHandler struct {
+ http.Handler
+ server *rpc.Server
+}
+
+type httpServer struct {
+ log log.Logger
+ timeouts rpc.HTTPTimeouts
+ mux http.ServeMux // registered handlers go here
+
+ mu sync.Mutex
+ server *http.Server
+ listener net.Listener // non-nil when server is running
+
+ // HTTP RPC handler things.
+ httpConfig httpConfig
+ httpHandler atomic.Value // *rpcHandler
+
+ // WebSocket handler things.
+ wsConfig wsConfig
+ wsHandler atomic.Value // *rpcHandler
+
+ // These are set by setListenAddr.
+ endpoint string
+ host string
+ port int
+
+ handlerNames map[string]string
+}
+
+func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer {
+ h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)}
+ h.httpHandler.Store((*rpcHandler)(nil))
+ h.wsHandler.Store((*rpcHandler)(nil))
+ return h
+}
+
+// setListenAddr configures the listening address of the server.
+// The address can only be set while the server isn't running.
+func (h *httpServer) setListenAddr(host string, port int) error {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ if h.listener != nil && (host != h.host || port != h.port) {
+ return fmt.Errorf("HTTP server already running on %s", h.endpoint)
+ }
+
+ h.host, h.port = host, port
+ h.endpoint = fmt.Sprintf("%s:%d", host, port)
+ return nil
+}
+
+// listenAddr returns the listening address of the server.
+func (h *httpServer) listenAddr() string {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ if h.listener != nil {
+ return h.listener.Addr().String()
+ }
+ return h.endpoint
+}
+
+// start starts the HTTP server if it is enabled and not already running.
+func (h *httpServer) start() error {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ if h.endpoint == "" || h.listener != nil {
+ return nil // already running or not configured
+ }
+
+ // Initialize the server.
+ h.server = &http.Server{Handler: h}
+ if h.timeouts != (rpc.HTTPTimeouts{}) {
+ h.server.ReadTimeout = h.timeouts.ReadTimeout
+ h.server.WriteTimeout = h.timeouts.WriteTimeout
+ h.server.IdleTimeout = h.timeouts.IdleTimeout
+ }
+ log.Info("Start http server", "ReadTimeout", h.server.ReadTimeout, "WriteTimeout", h.server.WriteTimeout, "IdleTimeout", h.server.IdleTimeout)
+ // Start the server.
+ listener, err := net.Listen("tcp", h.endpoint)
+ if err != nil {
+ // If the server fails to start, we need to clear out the RPC and WS
+ // configuration so they can be configured another time.
+ h.disableRPC()
+ h.disableWS()
+ return err
+ }
+ h.listener = listener
+ go h.server.Serve(listener)
+
+ // if server is websocket only, return after logging
+ if h.wsAllowed() && !h.rpcAllowed() {
+ h.log.Info("WebSocket enabled", "url", fmt.Sprintf("ws://%v", listener.Addr()))
+ return nil
+ }
+ // Log http endpoint.
+ h.log.Info("HTTP server started",
+ "endpoint", listener.Addr(),
+ "cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","),
+ "vhosts", strings.Join(h.httpConfig.Vhosts, ","),
+ )
+
+ // Log all handlers mounted on server.
+ var paths []string
+ for path := range h.handlerNames {
+ paths = append(paths, path)
+ }
+ sort.Strings(paths)
+ logged := make(map[string]bool, len(paths))
+ for _, path := range paths {
+ name := h.handlerNames[path]
+ if !logged[name] {
+ log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path)
+ logged[name] = true
+ }
+ }
+ return nil
+}
+
+func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ rpc := h.httpHandler.Load().(*rpcHandler)
+ if r.RequestURI == "/" {
+ // Serve JSON-RPC on the root path.
+ ws := h.wsHandler.Load().(*rpcHandler)
+ if ws != nil && isWebsocket(r) {
+ ws.ServeHTTP(w, r)
+ return
+ }
+ if rpc != nil {
+ rpc.ServeHTTP(w, r)
+ return
+ }
+ } else if rpc != nil {
+ // Requests to a path below root are handled by the mux,
+ // which has all the handlers registered via Node.RegisterHandler.
+ // These are made available when RPC is enabled.
+ h.mux.ServeHTTP(w, r)
+ return
+ }
+ w.WriteHeader(404)
+}
+
+// stop shuts down the HTTP server.
+func (h *httpServer) stop() {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ h.doStop()
+}
+
+func (h *httpServer) doStop() {
+ if h.listener == nil {
+ return // not running
+ }
+
+ // Shut down the server.
+ httpHandler := h.httpHandler.Load().(*rpcHandler)
+ wsHandler := h.httpHandler.Load().(*rpcHandler)
+ if httpHandler != nil {
+ h.httpHandler.Store((*rpcHandler)(nil))
+ httpHandler.server.Stop()
+ }
+ if wsHandler != nil {
+ h.wsHandler.Store((*rpcHandler)(nil))
+ wsHandler.server.Stop()
+ }
+ h.server.Shutdown(context.Background())
+ h.listener.Close()
+ h.log.Info("HTTP server stopped", "endpoint", h.listener.Addr())
+
+ // Clear out everything to allow re-configuring it later.
+ h.host, h.port, h.endpoint = "", 0, ""
+ h.server, h.listener = nil, nil
+}
+
+// enableRPC turns on JSON-RPC over HTTP on the server.
+func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ if h.rpcAllowed() {
+ return fmt.Errorf("JSON-RPC over HTTP is already enabled")
+ }
+
+ // Create RPC server and handler.
+ srv := rpc.NewServer()
+ if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil {
+ return err
+ }
+ h.httpConfig = config
+ h.httpHandler.Store(&rpcHandler{
+ Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts, &h.timeouts),
+ server: srv,
+ })
+ return nil
+}
+
+// disableRPC stops the HTTP RPC handler. This is internal, the caller must hold h.mu.
+func (h *httpServer) disableRPC() bool {
+ handler := h.httpHandler.Load().(*rpcHandler)
+ if handler != nil {
+ h.httpHandler.Store((*rpcHandler)(nil))
+ handler.server.Stop()
+ }
+ return handler != nil
+}
+
+// enableWS turns on JSON-RPC over WebSocket on the server.
+func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ if h.wsAllowed() {
+ return fmt.Errorf("JSON-RPC over WebSocket is already enabled")
+ }
+
+ // Create RPC server and handler.
+ srv := rpc.NewServer()
+ if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil {
+ return err
+ }
+ h.wsConfig = config
+ h.wsHandler.Store(&rpcHandler{
+ Handler: srv.WebsocketHandler(config.Origins),
+ server: srv,
+ })
+ return nil
+}
+
+// stopWS disables JSON-RPC over WebSocket and also stops the server if it only serves WebSocket.
+func (h *httpServer) stopWS() {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ if h.disableWS() {
+ if !h.rpcAllowed() {
+ h.doStop()
+ }
+ }
+}
+
+// disableWS disables the WebSocket handler. This is internal, the caller must hold h.mu.
+func (h *httpServer) disableWS() bool {
+ ws := h.wsHandler.Load().(*rpcHandler)
+ if ws != nil {
+ h.wsHandler.Store((*rpcHandler)(nil))
+ ws.server.Stop()
+ }
+ return ws != nil
+}
+
+// rpcAllowed returns true when JSON-RPC over HTTP is enabled.
+func (h *httpServer) rpcAllowed() bool {
+ return h.httpHandler.Load().(*rpcHandler) != nil
+}
+
+// wsAllowed returns true when JSON-RPC over WebSocket is enabled.
+func (h *httpServer) wsAllowed() bool {
+ return h.wsHandler.Load().(*rpcHandler) != nil
+}
+
+// isWebsocket checks the header of an http request for a websocket upgrade request.
+func isWebsocket(r *http.Request) bool {
+ return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" &&
+ strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
+}
+
// NewHTTPHandlerStack returns wrapped http-related handlers
func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string, timeouts *rpc.HTTPTimeouts) http.Handler {
// Wrap the CORS-handler within a host-handler
@@ -37,14 +322,13 @@ func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string, timeo
handler = newVHostHandler(vhosts, handler)
handler = newGzipHandler(handler)
- // make sure timeout values are meaningful
- CheckTimeouts(timeouts)
-
+ // decrease 1 second to let TimeoutHandler trigger first
+ // Note: The minimum value for timeouts.WriteTimeout is 2 seconds, so timeout is at least 1 second
+ timeout := timeouts.WriteTimeout - time.Second
// PR #469: register timeout handler before WebSocket and HTTP
- handler = http.TimeoutHandler(handler, timeouts.WriteTimeout, `{"error":"http server timeout"}`)
+ handler = http.TimeoutHandler(handler, timeout, `{"error":"http server timeout"}`)
+ log.Info("Set http server timeout handler", "WriteTimeout", timeout)
- // add 1 second to let TimeoutHandler works first
- timeouts.WriteTimeout = timeouts.WriteTimeout + time.Second
return handler
}
@@ -149,22 +433,68 @@ 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 request with the http handler.
-func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if isWebsocket(r) {
- ws.ServeHTTP(w, r)
- log.Debug("serving websocket request")
- return
+type ipcServer struct {
+ log log.Logger
+ endpoint string
+
+ mu sync.Mutex
+ listener net.Listener
+ srv *rpc.Server
+}
+
+func newIPCServer(log log.Logger, endpoint string) *ipcServer {
+ return &ipcServer{log: log, endpoint: endpoint}
+}
+
+// Start starts the httpServer's http.Server
+func (is *ipcServer) start(apis []rpc.API) error {
+ is.mu.Lock()
+ defer is.mu.Unlock()
+
+ if is.listener != nil {
+ return nil // already running
+ }
+ listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, apis)
+ if err != nil {
+ return err
+ }
+ is.log.Info("IPC endpoint opened", "url", is.endpoint)
+ is.listener, is.srv = listener, srv
+ return nil
+}
+
+func (is *ipcServer) stop() error {
+ is.mu.Lock()
+ defer is.mu.Unlock()
+
+ if is.listener == nil {
+ return nil // not running
+ }
+ err := is.listener.Close()
+ is.srv.Stop()
+ is.listener, is.srv = nil, nil
+ is.log.Info("IPC endpoint closed", "url", is.endpoint)
+ return err
+}
+
+// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules,
+// and then registers all of the APIs exposed by the services.
+func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error {
+ if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 {
+ log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available)
+ }
+ // Generate the whitelist based on the allowed modules
+ whitelist := make(map[string]bool)
+ for _, module := range modules {
+ whitelist[module] = true
+ }
+ // Register all the APIs exposed by the services
+ for _, api := range apis {
+ if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
+ if err := srv.RegisterName(api.Namespace, api.Service); err != nil {
+ return err
+ }
}
-
- h.ServeHTTP(w, r)
- })
-}
-
-// isWebsocket checks the header of an http request for a websocket upgrade request.
-func isWebsocket(r *http.Request) bool {
- return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" &&
- strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
+ }
+ return nil
}
diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go
index 38f486e52a..47f29995a8 100644
--- a/node/rpcstack_test.go
+++ b/node/rpcstack_test.go
@@ -1,40 +1,96 @@
package node
import (
+ "bytes"
"net/http"
- "net/http/httptest"
"testing"
+ "github.com/XinFinOrg/XDPoSChain/internal/testlog"
+ "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rpc"
+ "github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
)
-func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) {
- srv := rpc.NewServer()
+// TestCorsHandler makes sure CORS are properly handled on the http server.
+func TestCorsHandler(t *testing.T) {
+ srv := createAndStartServer(t, httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, wsConfig{})
+ defer srv.stop()
- handler := NewWebsocketUpgradeHandler(nil, srv.WebsocketHandler([]string{}))
- ts := httptest.NewServer(handler)
- defer ts.Close()
+ resp := testRequest(t, "origin", "test.com", "", srv)
+ assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin"))
- responses := make(chan *http.Response)
- go func(responses chan *http.Response) {
- client := &http.Client{}
+ resp2 := testRequest(t, "origin", "bad", "", srv)
+ assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin"))
+}
- req, _ := http.NewRequest(http.MethodGet, ts.URL, nil)
- req.Header.Set("Connection", "upgrade")
- req.Header.Set("Upgrade", "websocket")
- req.Header.Set("Sec-WebSocket-Version", "13")
- req.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==")
+// 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, err := client.Do(req)
- if err != nil {
- t.Error("could not issue a GET request to the test http server", err)
- }
- responses <- resp
- }(responses)
+ resp := testRequest(t, "", "", "test", srv)
+ assert.Equal(t, resp.StatusCode, http.StatusOK)
- response := <-responses
- assert.Equal(t, "websocket", response.Header.Get("Upgrade"))
+ resp2 := testRequest(t, "", "", "bad", srv)
+ assert.Equal(t, resp2.StatusCode, http.StatusForbidden)
+}
+
+// TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server.
+func TestWebsocketOrigins(t *testing.T) {
+ srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: []string{"test"}})
+ defer srv.stop()
+
+ dialer := websocket.DefaultDialer
+ _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{
+ "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
+}
+
+func testRequest(t *testing.T, key, value, host string, srv *httpServer) *http.Response {
+ t.Helper()
+
+ body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,method":"rpc_modules"}`))
+ req, _ := http.NewRequest("POST", "http://"+srv.listenAddr(), body)
+ req.Header.Set("content-type", "application/json")
+ if key != "" && value != "" {
+ req.Header.Set(key, value)
+ }
+ if host != "" {
+ req.Host = host
+ }
+
+ client := http.DefaultClient
+ resp, err := client.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return resp
}
// TestIsWebsocket tests if an incoming websocket upgrade request is handled properly.
diff --git a/node/service.go b/node/service.go
deleted file mode 100644
index 0bf8974222..0000000000
--- 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 (
- "reflect"
-
- "github.com/XinFinOrg/XDPoSChain/accounts"
- "github.com/XinFinOrg/XDPoSChain/core/rawdb"
- "github.com/XinFinOrg/XDPoSChain/ethdb"
- "github.com/XinFinOrg/XDPoSChain/event"
- "github.com/XinFinOrg/XDPoSChain/p2p"
- "github.com/XinFinOrg/XDPoSChain/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 {
- config *Config
- services map[reflect.Type]Service // Index of the already constructed services
- 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, readonly bool) (ethdb.Database, error) {
- if ctx.config.DataDir == "" {
- return rawdb.NewMemoryDatabase(), nil
- }
- db, err := rawdb.NewLevelDBDatabase(ctx.config.ResolvePath(name), cache, handles, namespace, readonly)
- if err != nil {
- return nil, err
- }
- return db, nil
-}
-
-// 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
-}
-
-// Get current node config.
-func (ctx *ServiceContext) GetConfig() *Config {
- return ctx.config
-}
-
-// 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
-}
diff --git a/node/service_test.go b/node/service_test.go
deleted file mode 100644
index 560666af0c..0000000000
--- a/node/service_test.go
+++ /dev/null
@@ -1,94 +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 (
- "errors"
- "fmt"
- "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 := t.TempDir()
-
- 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, "", false)
- 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, "", false)
- 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, errors.New("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()
-}
diff --git a/node/utils_test.go b/node/utils_test.go
index 4f6034f47e..227c05a199 100644
--- a/node/utils_test.go
+++ b/node/utils_test.go
@@ -20,61 +20,40 @@
package node
import (
- "reflect"
-
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
-// NoopService is a trivial implementation of the Service interface.
-type NoopService struct{}
+// NoopLifecycle is a trivial implementation of the Service interface.
+type NoopLifecycle struct{}
-func (s *NoopService) Protocols() []p2p.Protocol { return nil }
-func (s *NoopService) APIs() []rpc.API { return nil }
-func (s *NoopService) Start(*p2p.Server) error { return nil }
-func (s *NoopService) Stop() error { return nil }
+func (s *NoopLifecycle) Start() error { return nil }
+func (s *NoopLifecycle) Stop() error { return nil }
-func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil }
+func NewNoop() *Noop {
+ noop := new(Noop)
+ return noop
+}
-// Set of services all wrapping the base NoopService resulting in the same method
+// Set of services all wrapping the base NoopLifecycle resulting in the same method
// signatures but different outer types.
-type NoopServiceA struct{ NoopService }
-type NoopServiceB struct{ NoopService }
-type NoopServiceC struct{ NoopService }
+type Noop struct{ NoopLifecycle }
-func NewNoopServiceA(*ServiceContext) (Service, error) { return new(NoopServiceA), nil }
-func NewNoopServiceB(*ServiceContext) (Service, error) { return new(NoopServiceB), nil }
-func NewNoopServiceC(*ServiceContext) (Service, error) { return new(NoopServiceC), nil }
-
-// InstrumentedService is an implementation of Service for which all interface
+// InstrumentedService is an implementation of Lifecycle for which all interface
// methods can be instrumented both return value as well as event hook wise.
type InstrumentedService struct {
+ start error
+ stop error
+
+ startHook func()
+ stopHook func()
+
protocols []p2p.Protocol
- apis []rpc.API
- start error
- stop error
-
- protocolsHook func()
- startHook func(*p2p.Server)
- stopHook func()
}
-func NewInstrumentedService(*ServiceContext) (Service, error) { return new(InstrumentedService), nil }
-
-func (s *InstrumentedService) Protocols() []p2p.Protocol {
- if s.protocolsHook != nil {
- s.protocolsHook()
- }
- return s.protocols
-}
-
-func (s *InstrumentedService) APIs() []rpc.API {
- return s.apis
-}
-
-func (s *InstrumentedService) Start(server *p2p.Server) error {
+func (s *InstrumentedService) Start() error {
if s.startHook != nil {
- s.startHook(server)
+ s.startHook()
}
return s.start
}
@@ -86,48 +65,49 @@ func (s *InstrumentedService) Stop() error {
return s.stop
}
-// InstrumentingWrapper is a method to specialize a service constructor returning
-// a generic InstrumentedService into one returning a wrapping specific one.
-type InstrumentingWrapper func(base ServiceConstructor) ServiceConstructor
+type FullService struct{}
-func InstrumentingWrapperMaker(base ServiceConstructor, kind reflect.Type) ServiceConstructor {
- return func(ctx *ServiceContext) (Service, error) {
- obj, err := base(ctx)
- if err != nil {
- return nil, err
- }
- wrapper := reflect.New(kind)
- wrapper.Elem().Field(0).Set(reflect.ValueOf(obj).Elem())
+func NewFullService(stack *Node) (*FullService, error) {
+ fs := new(FullService)
- return wrapper.Interface().(Service), nil
+ stack.RegisterProtocols(fs.Protocols())
+ stack.RegisterAPIs(fs.APIs())
+ stack.RegisterLifecycle(fs)
+ return fs, nil
+}
+
+func (f *FullService) Start() error { return nil }
+
+func (f *FullService) Stop() error { return nil }
+
+func (f *FullService) Protocols() []p2p.Protocol {
+ return []p2p.Protocol{
+ p2p.Protocol{
+ Name: "test1",
+ Version: uint(1),
+ },
+ p2p.Protocol{
+ Name: "test2",
+ Version: uint(2),
+ },
}
}
-// Set of services all wrapping the base InstrumentedService resulting in the
-// same method signatures but different outer types.
-type InstrumentedServiceA struct{ InstrumentedService }
-type InstrumentedServiceB struct{ InstrumentedService }
-type InstrumentedServiceC struct{ InstrumentedService }
-
-func InstrumentedServiceMakerA(base ServiceConstructor) ServiceConstructor {
- return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceA{}))
-}
-
-func InstrumentedServiceMakerB(base ServiceConstructor) ServiceConstructor {
- return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceB{}))
-}
-
-func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor {
- return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{}))
-}
-
-// OneMethodAPI is a single-method API handler to be returned by test services.
-type OneMethodAPI struct {
- fun func()
-}
-
-func (api *OneMethodAPI) TheOneMethod() {
- if api.fun != nil {
- api.fun()
+func (f *FullService) APIs() []rpc.API {
+ return []rpc.API{
+ {
+ Namespace: "admin",
+ Version: "1.0",
+ },
+ {
+ Namespace: "debug",
+ Version: "1.0",
+ Public: true,
+ },
+ {
+ Namespace: "net",
+ Version: "1.0",
+ Public: true,
+ },
}
}
diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go
index bcd3186f6c..72d96f41ea 100644
--- a/p2p/protocols/protocol_test.go
+++ b/p2p/protocols/protocol_test.go
@@ -17,16 +17,9 @@
package protocols
import (
- "context"
- "errors"
"fmt"
- "testing"
- "time"
- "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
- "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
- p2ptest "github.com/XinFinOrg/XDPoSChain/p2p/testing"
)
// handshake message type
@@ -64,326 +57,3 @@ func checkProtoHandshake(testVersion uint, testNetworkID string) func(interface{
return nil
}
}
-
-// newProtocol sets up a protocol
-// the run function here demonstrates a typical protocol using peerPool, handshake
-// and messages registered to handlers
-func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) error {
- spec := &Spec{
- Name: "test",
- Version: 42,
- MaxMsgSize: 10 * 1024,
- Messages: []interface{}{
- protoHandshake{},
- hs0{},
- kill{},
- drop{},
- },
- }
- return func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
- peer := NewPeer(p, rw, spec)
-
- // initiate one-off protohandshake and check validity
- ctx, cancel := context.WithTimeout(context.Background(), time.Second)
- defer cancel()
- phs := &protoHandshake{42, "420"}
- hsCheck := checkProtoHandshake(phs.Version, phs.NetworkID)
- _, err := peer.Handshake(ctx, phs, hsCheck)
- if err != nil {
- return err
- }
-
- lhs := &hs0{42}
- // module handshake demonstrating a simple repeatable exchange of same-type message
- hs, err := peer.Handshake(ctx, lhs, nil)
- if err != nil {
- return err
- }
-
- if rmhs := hs.(*hs0); rmhs.C > lhs.C {
- return fmt.Errorf("handshake mismatch remote %v > local %v", rmhs.C, lhs.C)
- }
-
- handle := func(msg interface{}) error {
- switch msg := msg.(type) {
-
- case *protoHandshake:
- return errors.New("duplicate handshake")
-
- case *hs0:
- rhs := msg
- if rhs.C > lhs.C {
- return fmt.Errorf("handshake mismatch remote %v > local %v", rhs.C, lhs.C)
- }
- lhs.C += rhs.C
- return peer.Send(lhs)
-
- case *kill:
- // demonstrates use of peerPool, killing another peer connection as a response to a message
- id := msg.C
- pp.Get(id).Drop(errors.New("killed"))
- return nil
-
- case *drop:
- // for testing we can trigger self induced disconnect upon receiving drop message
- return errors.New("dropped")
-
- default:
- return fmt.Errorf("unknown message type: %T", msg)
- }
- }
-
- pp.Add(peer)
- defer pp.Remove(peer)
- return peer.Run(handle)
- }
-}
-
-func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester {
- conf := adapters.RandomNodeConfig()
- return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp))
-}
-
-func protoHandshakeExchange(id discover.NodeID, proto *protoHandshake) []p2ptest.Exchange {
-
- return []p2ptest.Exchange{
- {
- Expects: []p2ptest.Expect{
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: id,
- },
- },
- },
- {
- Triggers: []p2ptest.Trigger{
- {
- Code: 0,
- Msg: proto,
- Peer: id,
- },
- },
- },
- }
-}
-
-func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) {
- pp := p2ptest.NewTestPeerPool()
- s := protocolTester(t, pp)
- // TODO: make this more than one handshake
- id := s.IDs[0]
- if err := s.TestExchanges(protoHandshakeExchange(id, proto)...); err != nil {
- t.Fatal(err)
- }
- var disconnects []*p2ptest.Disconnect
- for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
- }
- if err := s.TestDisconnected(disconnects...); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestProtoHandshakeVersionMismatch(t *testing.T) {
- runProtoHandshake(t, &protoHandshake{41, "420"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 41 (!= 42)").Error()))
-}
-
-func TestProtoHandshakeNetworkIDMismatch(t *testing.T) {
- runProtoHandshake(t, &protoHandshake{42, "421"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 421 (!= 420)").Error()))
-}
-
-func TestProtoHandshakeSuccess(t *testing.T) {
- runProtoHandshake(t, &protoHandshake{42, "420"})
-}
-
-func moduleHandshakeExchange(id discover.NodeID, resp uint) []p2ptest.Exchange {
-
- return []p2ptest.Exchange{
- {
- Expects: []p2ptest.Expect{
- {
- Code: 1,
- Msg: &hs0{42},
- Peer: id,
- },
- },
- },
- {
- Triggers: []p2ptest.Trigger{
- {
- Code: 1,
- Msg: &hs0{resp},
- Peer: id,
- },
- },
- },
- }
-}
-
-func runModuleHandshake(t *testing.T, resp uint, errs ...error) {
- pp := p2ptest.NewTestPeerPool()
- s := protocolTester(t, pp)
- id := s.IDs[0]
- if err := s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...); err != nil {
- t.Fatal(err)
- }
- if err := s.TestExchanges(moduleHandshakeExchange(id, resp)...); err != nil {
- t.Fatal(err)
- }
- var disconnects []*p2ptest.Disconnect
- for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
- }
- if err := s.TestDisconnected(disconnects...); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestModuleHandshakeError(t *testing.T) {
- runModuleHandshake(t, 43, errors.New("handshake mismatch remote 43 > local 42"))
-}
-
-func TestModuleHandshakeSuccess(t *testing.T) {
- runModuleHandshake(t, 42)
-}
-
-// testing complex interactions over multiple peers, relaying, dropping
-func testMultiPeerSetup(a, b discover.NodeID) []p2ptest.Exchange {
-
- return []p2ptest.Exchange{
- {
- Label: "primary handshake",
- Expects: []p2ptest.Expect{
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: a,
- },
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: b,
- },
- },
- },
- {
- Label: "module handshake",
- Triggers: []p2ptest.Trigger{
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: a,
- },
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: b,
- },
- },
- Expects: []p2ptest.Expect{
- {
- Code: 1,
- Msg: &hs0{42},
- Peer: a,
- },
- {
- Code: 1,
- Msg: &hs0{42},
- Peer: b,
- },
- },
- },
-
- {Label: "alternative module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{41}, Peer: a},
- {Code: 1, Msg: &hs0{41}, Peer: b}}},
- {Label: "repeated module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{1}, Peer: a}}},
- {Label: "receiving repeated module handshake", Expects: []p2ptest.Expect{{Code: 1, Msg: &hs0{43}, Peer: a}}}}
-}
-
-func runMultiplePeers(t *testing.T, peer int, errs ...error) {
- pp := p2ptest.NewTestPeerPool()
- s := protocolTester(t, pp)
-
- if err := s.TestExchanges(testMultiPeerSetup(s.IDs[0], s.IDs[1])...); err != nil {
- t.Fatal(err)
- }
- // after some exchanges of messages, we can test state changes
- // here this is simply demonstrated by the peerPool
- // after the handshake negotiations peers must be added to the pool
- // time.Sleep(1)
- tick := time.NewTicker(10 * time.Millisecond)
- timeout := time.NewTimer(1 * time.Second)
-WAIT:
- for {
- select {
- case <-tick.C:
- if pp.Has(s.IDs[0]) {
- break WAIT
- }
- case <-timeout.C:
- t.Fatal("timeout")
- }
- }
- if !pp.Has(s.IDs[1]) {
- t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs)
- }
-
- // peer 0 sends kill request for peer with index
- err := s.TestExchanges(p2ptest.Exchange{
- Triggers: []p2ptest.Trigger{
- {
- Code: 2,
- Msg: &kill{s.IDs[peer]},
- Peer: s.IDs[0],
- },
- },
- })
-
- if err != nil {
- t.Fatal(err)
- }
-
- // the peer not killed sends a drop request
- err = s.TestExchanges(p2ptest.Exchange{
- Triggers: []p2ptest.Trigger{
- {
- Code: 3,
- Msg: &drop{},
- Peer: s.IDs[(peer+1)%2],
- },
- },
- })
-
- if err != nil {
- t.Fatal(err)
- }
-
- // check the actual discconnect errors on the individual peers
- var disconnects []*p2ptest.Disconnect
- for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
- }
- if err := s.TestDisconnected(disconnects...); err != nil {
- t.Fatal(err)
- }
- // test if disconnected peers have been removed from peerPool
- if pp.Has(s.IDs[peer]) {
- t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.IDs)
- }
-
-}
-
-func TestMultiplePeersDropSelf(t *testing.T) {
- runMultiplePeers(t, 0,
- errors.New("subprotocol error"),
- errors.New("Message handler error: (msg code 3): dropped"),
- )
-}
-
-func TestMultiplePeersDropOther(t *testing.T) {
- runMultiplePeers(t, 1,
- errors.New("Message handler error: (msg code 3): dropped"),
- errors.New("subprotocol error"),
- )
-}
diff --git a/p2p/server.go b/p2p/server.go
index 0aa148d0d1..e113530ecc 100644
--- a/p2p/server.go
+++ b/p2p/server.go
@@ -157,7 +157,7 @@ type Server struct {
newPeerHook func(*Peer)
lock sync.Mutex // protects running
- running bool
+ Running bool
ntab discoverTable
listener net.Listener
@@ -344,7 +344,7 @@ func (srv *Server) Self() *discover.Node {
srv.lock.Lock()
defer srv.lock.Unlock()
- if !srv.running {
+ if !srv.Running {
return &discover.Node{IP: net.ParseIP("0.0.0.0")}
}
return srv.makeSelf(srv.listener, srv.ntab)
@@ -375,10 +375,10 @@ func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *discover
func (srv *Server) Stop() {
srv.lock.Lock()
defer srv.lock.Unlock()
- if !srv.running {
+ if !srv.Running {
return
}
- srv.running = false
+ srv.Running = false
if srv.listener != nil {
// this unblocks listener Accept
srv.listener.Close()
@@ -418,10 +418,10 @@ func (s *sharedUDPConn) Close() error {
func (srv *Server) Start() (err error) {
srv.lock.Lock()
defer srv.lock.Unlock()
- if srv.running {
+ if srv.Running {
return errors.New("server already running")
}
- srv.running = true
+ srv.Running = true
srv.log = srv.Config.Logger
if srv.log == nil {
srv.log = log.New()
@@ -538,7 +538,7 @@ func (srv *Server) Start() (err error) {
srv.loopWG.Add(1)
go srv.run(dialer)
- srv.running = true
+ srv.Running = true
return nil
}
@@ -881,7 +881,7 @@ func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *discover.Nod
func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) error {
// Prevent leftover pending conns from entering the handshake.
srv.lock.Lock()
- running := srv.running
+ running := srv.Running
srv.lock.Unlock()
if !running {
return errServerStopped
diff --git a/p2p/server_test.go b/p2p/server_test.go
index 79c3c2a2f1..19f396d10d 100644
--- a/p2p/server_test.go
+++ b/p2p/server_test.go
@@ -243,7 +243,7 @@ func TestServerTaskScheduling(t *testing.T) {
Config: Config{MaxPeers: 10},
quit: make(chan struct{}),
ntab: fakeTable{},
- running: true,
+ Running: true,
log: log.New(),
}
srv.loopWG.Add(1)
@@ -288,7 +288,7 @@ func TestServerManyTasks(t *testing.T) {
srv = &Server{
quit: make(chan struct{}),
ntab: fakeTable{},
- running: true,
+ Running: true,
log: log.New(),
}
done = make(chan *testTask)
diff --git a/p2p/simulations/adapters/docker.go b/p2p/simulations/adapters/docker.go
index 101f16313c..ee1f211bc1 100644
--- a/p2p/simulations/adapters/docker.go
+++ b/p2p/simulations/adapters/docker.go
@@ -72,11 +72,11 @@ func (d *DockerAdapter) Name() string {
// NewNode returns a new DockerNode using the given config
func (d *DockerAdapter) NewNode(config *NodeConfig) (Node, error) {
- if len(config.Services) == 0 {
- return nil, errors.New("node must have at least one service")
+ if len(config.Lifecycles) == 0 {
+ return nil, errors.New("node must have at least one lifecycle")
}
- for _, service := range config.Services {
- if _, exists := serviceFuncs[service]; !exists {
+ for _, service := range config.Lifecycles {
+ if _, exists := lifecycleConstructorFuncs[service]; !exists {
return nil, fmt.Errorf("unknown node service %q", service)
}
}
@@ -123,7 +123,7 @@ func (n *DockerNode) dockerCommand() *exec.Cmd {
"sh", "-c",
fmt.Sprintf(
`exec docker run --interactive --env _P2P_NODE_CONFIG="${_P2P_NODE_CONFIG}" %s p2p-node %s %s`,
- dockerImage, strings.Join(n.Config.Node.Services, ","), n.ID.String(),
+ dockerImage, strings.Join(n.Config.Node.Lifecycles, ","), n.ID.String(),
),
)
}
diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go
index d9a840f60f..77d1305902 100644
--- a/p2p/simulations/adapters/exec.go
+++ b/p2p/simulations/adapters/exec.go
@@ -75,11 +75,11 @@ func (e *ExecAdapter) Name() string {
// NewNode returns a new ExecNode using the given config
func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
- if len(config.Services) == 0 {
- return nil, errors.New("node must have at least one service")
+ if len(config.Lifecycles) == 0 {
+ return nil, errors.New("node must have at least one service lifecycle")
}
- for _, service := range config.Services {
- if _, exists := serviceFuncs[service]; !exists {
+ for _, service := range config.Lifecycles {
+ if _, exists := lifecycleConstructorFuncs[service]; !exists {
return nil, fmt.Errorf("unknown node service %q", service)
}
}
@@ -238,7 +238,7 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
func (n *ExecNode) execCommand() *exec.Cmd {
return &exec.Cmd{
Path: reexec.Self(),
- Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()},
+ Args: []string{"p2p-node", strings.Join(n.Config.Node.Lifecycles, ","), n.ID.String()},
}
}
@@ -426,43 +426,36 @@ func execP2PNode() {
log.Crit("error creating node stack", "err", err)
}
- // register the services, collecting them into a map so we can wrap
- // them in a snapshot service
- services := make(map[string]node.Service, len(serviceNames))
+ // Register the services, collecting them into a map so they can
+ // be accessed by the snapshot API.
+ services := make(map[string]node.Lifecycle, len(serviceNames))
for _, name := range serviceNames {
- serviceFunc, exists := serviceFuncs[name]
+ lifecycleFunc, exists := lifecycleConstructorFuncs[name]
if !exists {
log.Crit("unknown node service", "name", name)
}
- constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) {
- ctx := &ServiceContext{
- RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs},
- NodeContext: nodeCtx,
- Config: conf.Node,
- }
- if conf.Snapshots != nil {
- ctx.Snapshot = conf.Snapshots[name]
- }
- service, err := serviceFunc(ctx)
- if err != nil {
- return nil, err
- }
- services[name] = service
- return service, nil
+ ctx := &ServiceContext{
+ RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs},
+ Config: conf.Node,
}
- if err := stack.Register(constructor); err != nil {
+ if conf.Snapshots != nil {
+ ctx.Snapshot = conf.Snapshots[name]
+ }
+ service, err := lifecycleFunc(ctx, stack)
+ if err != nil {
log.Crit("error starting service", "name", name, "err", err)
}
+ services[name] = service
+ stack.RegisterLifecycle(service)
}
- // register the snapshot service
- if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
- return &snapshotService{services}, nil
- }); err != nil {
- log.Crit("error starting snapshot service", "err", err)
- }
+ // Add the snapshot API.
+ stack.RegisterAPIs([]rpc.API{{
+ Namespace: "simulation",
+ Version: "1.0",
+ Service: SnapshotAPI{services},
+ }})
- // start the stack
if err := stack.Start(); err != nil {
log.Crit("error stating node stack", "err", err)
}
@@ -474,46 +467,20 @@ func execP2PNode() {
defer signal.Stop(sigc)
<-sigc
log.Info("Received SIGTERM, shutting down...")
- stack.Stop()
+ stack.Close()
}()
// wait for the stack to exit
stack.Wait()
}
-// snapshotService is a node.Service which wraps a list of services and
-// exposes an API to generate a snapshot of those services
-type snapshotService struct {
- services map[string]node.Service
-}
-
-func (s *snapshotService) APIs() []rpc.API {
- return []rpc.API{{
- Namespace: "simulation",
- Version: "1.0",
- Service: SnapshotAPI{s.services},
- }}
-}
-
-func (s *snapshotService) Protocols() []p2p.Protocol {
- return nil
-}
-
-func (s *snapshotService) Start(*p2p.Server) error {
- return nil
-}
-
-func (s *snapshotService) Stop() error {
- return nil
-}
-
const (
envNodeConfig = "_P2P_NODE_CONFIG"
)
// SnapshotAPI provides an RPC method to create snapshots of services
type SnapshotAPI struct {
- services map[string]node.Service
+ services map[string]node.Lifecycle
}
func (api SnapshotAPI) Snapshot() (map[string][]byte, error) {
diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go
index 2b11b39f07..0ad4342609 100644
--- a/p2p/simulations/adapters/inproc.go
+++ b/p2p/simulations/adapters/inproc.go
@@ -35,18 +35,20 @@ import (
// SimAdapter is a NodeAdapter which creates in-memory simulation nodes and
// connects them using in-memory net.Pipe connections
type SimAdapter struct {
- mtx sync.RWMutex
- nodes map[discover.NodeID]*SimNode
- services map[string]ServiceFunc
+ mtx sync.RWMutex
+ nodes map[discover.NodeID]*SimNode
+ lifecycles LifecycleConstructors
}
// NewSimAdapter creates a SimAdapter which is capable of running in-memory
// simulation nodes running any of the given services (the services to run on a
// particular node are passed to the NewNode function in the NodeConfig)
-func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter {
+func NewSimAdapter(services LifecycleConstructors) *SimAdapter {
return &SimAdapter{
- nodes: make(map[discover.NodeID]*SimNode),
- services: services,
+ // nodes: make(map[discover.NodeID]*SimNode),
+ // lifecycles: lifecycles,
+ nodes: make(map[discover.NodeID]*SimNode),
+ lifecycles: services,
}
}
@@ -67,11 +69,11 @@ func (sa *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
}
// check the services are valid
- if len(config.Services) == 0 {
+ if len(config.Lifecycles) == 0 {
return nil, errors.New("node must have at least one service")
}
- for _, service := range config.Services {
- if _, exists := sa.services[service]; !exists {
+ for _, service := range config.Lifecycles {
+ if _, exists := sa.lifecycles[service]; !exists {
return nil, fmt.Errorf("unknown node service %q", service)
}
}
@@ -95,7 +97,7 @@ func (sa *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
config: config,
node: n,
adapter: sa,
- running: make(map[string]node.Service),
+ running: make(map[string]node.Lifecycle),
connected: make(map[discover.NodeID]bool),
}
sa.nodes[id] = simNode
@@ -129,11 +131,7 @@ func (sa *SimAdapter) DialRPC(id discover.NodeID) (*rpc.Client, error) {
if !ok {
return nil, fmt.Errorf("unknown node: %s", id)
}
- handler, err := node.node.RPCHandler()
- if err != nil {
- return nil, err
- }
- return rpc.DialInProc(handler), nil
+ return node.node.Attach()
}
// GetNode returns the node with the given ID if it exists
@@ -153,7 +151,7 @@ type SimNode struct {
config *NodeConfig
adapter *SimAdapter
node *node.Node
- running map[string]node.Service
+ running map[string]node.Lifecycle
client *rpc.Client
registerOnce sync.Once
connected map[discover.NodeID]bool
@@ -202,7 +200,7 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error {
// simulation_snapshot RPC method
func (sn *SimNode) Snapshots() (map[string][]byte, error) {
sn.lock.RLock()
- services := make(map[string]node.Service, len(sn.running))
+ services := make(map[string]node.Lifecycle, len(sn.running))
for name, service := range sn.running {
services[name] = service
}
@@ -227,35 +225,31 @@ func (sn *SimNode) Snapshots() (map[string][]byte, error) {
// Start registers the services and starts the underlying devp2p node
func (sn *SimNode) Start(snapshots map[string][]byte) error {
- newService := func(name string) func(ctx *node.ServiceContext) (node.Service, error) {
- return func(nodeCtx *node.ServiceContext) (node.Service, error) {
- ctx := &ServiceContext{
- RPCDialer: sn.adapter,
- NodeContext: nodeCtx,
- Config: sn.config,
- }
- if snapshots != nil {
- ctx.Snapshot = snapshots[name]
- }
- serviceFunc := sn.adapter.services[name]
- service, err := serviceFunc(ctx)
- if err != nil {
- return nil, err
- }
- sn.running[name] = service
- return service, nil
- }
- }
-
// ensure we only register the services once in the case of the node
// being stopped and then started again
var regErr error
sn.registerOnce.Do(func() {
- for _, name := range sn.config.Services {
- if err := sn.node.Register(newService(name)); err != nil {
+ for _, name := range sn.config.Lifecycles {
+ ctx := &ServiceContext{
+ RPCDialer: sn.adapter,
+ Config: sn.config,
+ }
+ if snapshots != nil {
+ ctx.Snapshot = snapshots[name]
+ }
+ serviceFunc := sn.adapter.lifecycles[name]
+ service, err := serviceFunc(ctx, sn.node)
+ if err != nil {
+
regErr = err
return
}
+ // if the service has already been registered, don't register it again.
+ if _, ok := sn.running[name]; ok {
+ continue
+ }
+ sn.running[name] = service
+ sn.node.RegisterLifecycle(service)
}
})
if regErr != nil {
@@ -267,13 +261,13 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error {
}
// create an in-process RPC client
- handler, err := sn.node.RPCHandler()
+ client, err := sn.node.Attach()
if err != nil {
return err
}
sn.lock.Lock()
- sn.client = rpc.DialInProc(handler)
+ sn.client = client
sn.lock.Unlock()
return nil
@@ -287,14 +281,14 @@ func (sn *SimNode) Stop() error {
sn.client = nil
}
sn.lock.Unlock()
- return sn.node.Stop()
+ return sn.node.Close()
}
// Services returns a copy of the underlying services
-func (sn *SimNode) Services() []node.Service {
+func (sn *SimNode) Services() []node.Lifecycle {
sn.lock.RLock()
defer sn.lock.RUnlock()
- services := make([]node.Service, 0, len(sn.running))
+ services := make([]node.Lifecycle, 0, len(sn.running))
for _, service := range sn.running {
services = append(services, service)
}
@@ -319,7 +313,7 @@ func (sn *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription {
// NodeInfo returns information about the node
func (sn *SimNode) NodeInfo() *p2p.NodeInfo {
server := sn.Server()
- if server == nil {
+ if server.Running == false {
return &p2p.NodeInfo{
ID: sn.ID.String(),
Enode: sn.Node().String(),
diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go
index 59ae2e8ad3..ea5e1a0458 100644
--- a/p2p/simulations/adapters/types.go
+++ b/p2p/simulations/adapters/types.go
@@ -89,11 +89,11 @@ type NodeConfig struct {
// Name is a human friendly name for the node like "node01"
Name string
- // Services are the names of the services which should be run when
- // starting the node (for SimNodes it should be the names of services
- // contained in SimAdapter.services, for other nodes it should be
- // services registered by calling the RegisterService function)
- Services []string
+ // Lifecycles are the names of the service lifecycles which should be run when
+ // starting the node (for SimNodes it should be the names of service lifecycles
+ // contained in SimAdapter.lifecycles, for other nodes it should be
+ // service lifecycles registered by calling the RegisterLifecycle function)
+ Lifecycles []string
// function to sanction or prevent suggesting a peer
Reachable func(id discover.NodeID) bool
@@ -127,7 +127,7 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) {
confJSON := nodeConfigJSON{
ID: n.ID.String(),
Name: n.Name,
- Services: n.Services,
+ Services: n.Lifecycles,
LogFile: n.LogFile,
LogVerbosity: int(n.LogVerbosity),
}
@@ -166,7 +166,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
}
n.Name = confJSON.Name
- n.Services = confJSON.Services
+ n.Lifecycles = confJSON.Services
n.LogFile = confJSON.LogFile
n.LogVerbosity = slog.Level(confJSON.LogVerbosity)
@@ -194,9 +194,8 @@ func RandomNodeConfig() *NodeConfig {
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
@@ -206,27 +205,29 @@ type RPCDialer interface {
DialRPC(id discover.NodeID) (*rpc.Client, error)
}
-// Services is a collection of services which can be run in a simulation
-type Services map[string]ServiceFunc
+// LifecycleConstructor allows a Lifecycle to be constructed during node start-up.
+// 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
-type ServiceFunc func(ctx *ServiceContext) (node.Service, error)
+// LifecycleConstructors stores LifecycleConstructor functions to call during node start-up.
+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
-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.
//
// It should be called in an init function so that it has the opportunity to
// execute the services before main() is called.
-func RegisterServices(services Services) {
- for name, f := range services {
- if _, exists := serviceFuncs[name]; exists {
+func RegisterLifecycles(lifecycles LifecycleConstructors) {
+ for name, f := range lifecycles {
+ if _, exists := lifecycleConstructorFuncs[name]; exists {
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
diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go
index 144035348b..8c7f8b505b 100644
--- a/p2p/simulations/examples/ping-pong.go
+++ b/p2p/simulations/examples/ping-pong.go
@@ -31,7 +31,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
- "github.com/XinFinOrg/XDPoSChain/rpc"
)
var adapterType = flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`)
@@ -45,12 +44,14 @@ func main() {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, false)))
// register a single ping-pong service
- services := map[string]adapters.ServiceFunc{
- "ping-pong": func(ctx *adapters.ServiceContext) (node.Service, error) {
- return newPingPongService(ctx.Config.ID), nil
+ services := map[string]adapters.LifecycleConstructor{
+ "ping-pong": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
+ pps := newPingPongService(ctx.Config.ID)
+ stack.RegisterProtocols(pps.Protocols())
+ return pps, nil
},
}
- adapters.RegisterServices(services)
+ adapters.RegisterLifecycles(services)
// create the NodeAdapter
var adapter adapters.NodeAdapter
@@ -118,11 +119,7 @@ func (p *pingPongService) Protocols() []p2p.Protocol {
}}
}
-func (p *pingPongService) APIs() []rpc.API {
- return nil
-}
-
-func (p *pingPongService) Start(server *p2p.Server) error {
+func (p *pingPongService) Start() error {
p.log.Info("ping-pong service starting")
return nil
}
diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go
index 7121cc4c0d..51678d64dd 100644
--- a/p2p/simulations/http_test.go
+++ b/p2p/simulations/http_test.go
@@ -51,12 +51,15 @@ type testService struct {
state atomic.Value
}
-func newTestService(ctx *adapters.ServiceContext) (node.Service, error) {
+func newTestService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
svc := &testService{
id: ctx.Config.ID,
peers: make(map[discover.NodeID]*testPeer),
}
svc.state.Store(ctx.Snapshot)
+
+ stack.RegisterProtocols(svc.Protocols())
+ stack.RegisterAPIs(svc.APIs())
return svc, nil
}
@@ -113,7 +116,7 @@ func (t *testService) APIs() []rpc.API {
}}
}
-func (t *testService) Start(server *p2p.Server) error {
+func (t *testService) Start() error {
return nil
}
@@ -275,7 +278,7 @@ func (t *TestAPI) Events(ctx context.Context) (*rpc.Subscription, error) {
return rpcSub, nil
}
-var testServices = adapters.Services{
+var testServices = adapters.LifecycleConstructors{
"test": newTestService,
}
diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go
index 2c4a3d5a3f..0addd33262 100644
--- a/p2p/simulations/network.go
+++ b/p2p/simulations/network.go
@@ -82,7 +82,7 @@ func (net *Network) Events() *event.Feed {
// NewNode adds a new node to the network with a random ID
func (net *Network) NewNode() (*Node, error) {
conf := adapters.RandomNodeConfig()
- conf.Services = []string{net.DefaultService}
+ conf.Lifecycles = []string{net.DefaultService}
return net.NewNodeWithConfig(conf)
}
@@ -120,8 +120,8 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error)
}
// if no services are configured, use the default service
- if len(conf.Services) == 0 {
- conf.Services = []string{net.DefaultService}
+ if len(conf.Lifecycles) == 0 {
+ conf.Lifecycles = []string{net.DefaultService}
}
// use the NodeAdapter to create the node
@@ -551,6 +551,10 @@ func (n *Node) NodeInfo() *p2p.NodeInfo {
return nil
}
info := n.Node.NodeInfo()
+ if info == nil {
+ return nil
+ }
+
info.Name = n.Config.Name
return info
}
diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go
index 79803d59b0..a308442701 100644
--- a/p2p/simulations/network_test.go
+++ b/p2p/simulations/network_test.go
@@ -31,7 +31,7 @@ import (
// with each other and that a snapshot fully represents the desired topology
func TestNetworkSimulation(t *testing.T) {
// create simulation network with 20 testService nodes
- adapter := adapters.NewSimAdapter(adapters.Services{
+ adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
"test": newTestService,
})
network := NewNetwork(adapter, &NetworkConfig{
diff --git a/p2p/testing/peerpool.go b/p2p/testing/peerpool.go
deleted file mode 100644
index d34b4c0780..0000000000
--- a/p2p/testing/peerpool.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2017 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 testing
-
-import (
- "fmt"
- "sync"
-
- "github.com/XinFinOrg/XDPoSChain/log"
- "github.com/XinFinOrg/XDPoSChain/p2p/discover"
-)
-
-type TestPeer interface {
- ID() discover.NodeID
- Drop(error)
-}
-
-// TestPeerPool is an example peerPool to demonstrate registration of peer connections
-type TestPeerPool struct {
- lock sync.Mutex
- peers map[discover.NodeID]TestPeer
-}
-
-func NewTestPeerPool() *TestPeerPool {
- return &TestPeerPool{peers: make(map[discover.NodeID]TestPeer)}
-}
-
-func (pp *TestPeerPool) Add(p TestPeer) {
- pp.lock.Lock()
- defer pp.lock.Unlock()
- log.Trace(fmt.Sprintf("pp add peer %v", p.ID()))
- pp.peers[p.ID()] = p
-
-}
-
-func (pp *TestPeerPool) Remove(p TestPeer) {
- pp.lock.Lock()
- defer pp.lock.Unlock()
- delete(pp.peers, p.ID())
-}
-
-func (pp *TestPeerPool) Has(id discover.NodeID) bool {
- pp.lock.Lock()
- defer pp.lock.Unlock()
- _, ok := pp.peers[id]
- return ok
-}
-
-func (pp *TestPeerPool) Get(id discover.NodeID) TestPeer {
- pp.lock.Lock()
- defer pp.lock.Unlock()
- return pp.peers[id]
-}
diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go
deleted file mode 100644
index 39ccc70bd0..0000000000
--- a/p2p/testing/protocolsession.go
+++ /dev/null
@@ -1,280 +0,0 @@
-// Copyright 2017 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 testing
-
-import (
- "errors"
- "fmt"
- "sync"
- "time"
-
- "github.com/XinFinOrg/XDPoSChain/log"
- "github.com/XinFinOrg/XDPoSChain/p2p"
- "github.com/XinFinOrg/XDPoSChain/p2p/discover"
- "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
-)
-
-var errTimedOut = errors.New("timed out")
-
-// ProtocolSession is a quasi simulation of a pivot node running
-// a service and a number of dummy peers that can send (trigger) or
-// receive (expect) messages
-type ProtocolSession struct {
- Server *p2p.Server
- IDs []discover.NodeID
- adapter *adapters.SimAdapter
- events chan *p2p.PeerEvent
-}
-
-// Exchange is the basic units of protocol tests
-// the triggers and expects in the arrays are run immediately and asynchronously
-// thus one cannot have multiple expects for the SAME peer with DIFFERENT message types
-// because it's unpredictable which expect will receive which message
-// (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code)
-// an exchange is defined on a session
-type Exchange struct {
- Label string
- Triggers []Trigger
- Expects []Expect
- Timeout time.Duration
-}
-
-// Trigger is part of the exchange, incoming message for the pivot node
-// sent by a peer
-type Trigger struct {
- Msg interface{} // type of message to be sent
- Code uint64 // code of message is given
- Peer discover.NodeID // the peer to send the message to
- Timeout time.Duration // timeout duration for the sending
-}
-
-// Expect is part of an exchange, outgoing message from the pivot node
-// received by a peer
-type Expect struct {
- Msg interface{} // type of message to expect
- Code uint64 // code of message is now given
- Peer discover.NodeID // the peer that expects the message
- Timeout time.Duration // timeout duration for receiving
-}
-
-// Disconnect represents a disconnect event, used and checked by TestDisconnected
-type Disconnect struct {
- Peer discover.NodeID // discconnected peer
- Error error // disconnect reason
-}
-
-// trigger sends messages from peers
-func (ps *ProtocolSession) trigger(trig Trigger) error {
- simNode, ok := ps.adapter.GetNode(trig.Peer)
- if !ok {
- return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(ps.IDs))
- }
- mockNode, ok := simNode.Services()[0].(*mockNode)
- if !ok {
- return fmt.Errorf("trigger: peer %v is not a mock", trig.Peer)
- }
-
- errc := make(chan error)
-
- go func() {
- errc <- mockNode.Trigger(&trig)
- }()
-
- t := trig.Timeout
- if t == time.Duration(0) {
- t = 1000 * time.Millisecond
- }
- select {
- case err := <-errc:
- return err
- case <-time.After(t):
- return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
- }
-}
-
-// expect checks an expectation of a message sent out by the pivot node
-func (ps *ProtocolSession) expect(exps []Expect) error {
- // construct a map of expectations for each node
- peerExpects := make(map[discover.NodeID][]Expect)
- for _, exp := range exps {
- if exp.Msg == nil {
- return errors.New("no message to expect")
- }
- peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp)
- }
-
- // construct a map of mockNodes for each node
- mockNodes := make(map[discover.NodeID]*mockNode)
- for nodeID := range peerExpects {
- simNode, ok := ps.adapter.GetNode(nodeID)
- if !ok {
- return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(ps.IDs))
- }
- mockNode, ok := simNode.Services()[0].(*mockNode)
- if !ok {
- return fmt.Errorf("trigger: peer %v is not a mock", nodeID)
- }
- mockNodes[nodeID] = mockNode
- }
-
- // done chanell cancels all created goroutines when function returns
- done := make(chan struct{})
- defer close(done)
- // errc catches the first error from
- errc := make(chan error)
-
- wg := &sync.WaitGroup{}
- wg.Add(len(mockNodes))
- for nodeID, mockNode := range mockNodes {
- nodeID := nodeID
- mockNode := mockNode
- go func() {
- defer wg.Done()
-
- // Sum all Expect timeouts to give the maximum
- // time for all expectations to finish.
- // mockNode.Expect checks all received messages against
- // a list of expected messages and timeout for each
- // of them can not be checked separately.
- var t time.Duration
- for _, exp := range peerExpects[nodeID] {
- if exp.Timeout == time.Duration(0) {
- t += 2000 * time.Millisecond
- } else {
- t += exp.Timeout
- }
- }
- alarm := time.NewTimer(t)
- defer alarm.Stop()
-
- // expectErrc is used to check if error returned
- // from mockNode.Expect is not nil and to send it to
- // errc only in that case.
- // done channel will be closed when function
- expectErrc := make(chan error)
- go func() {
- select {
- case expectErrc <- mockNode.Expect(peerExpects[nodeID]...):
- case <-done:
- case <-alarm.C:
- }
- }()
-
- select {
- case err := <-expectErrc:
- if err != nil {
- select {
- case errc <- err:
- case <-done:
- case <-alarm.C:
- errc <- errTimedOut
- }
- }
- case <-done:
- case <-alarm.C:
- errc <- errTimedOut
- }
-
- }()
- }
-
- go func() {
- wg.Wait()
- // close errc when all goroutines finish to return nill err from errc
- close(errc)
- }()
-
- return <-errc
-}
-
-// TestExchanges tests a series of exchanges against the session
-func (ps *ProtocolSession) TestExchanges(exchanges ...Exchange) error {
- for i, e := range exchanges {
- if err := ps.testExchange(e); err != nil {
- return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err)
- }
- log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label))
- }
- return nil
-}
-
-// testExchange tests a single Exchange.
-// Default timeout value is 2 seconds.
-func (ps *ProtocolSession) testExchange(e Exchange) error {
- errc := make(chan error)
- done := make(chan struct{})
- defer close(done)
-
- go func() {
- for _, trig := range e.Triggers {
- err := ps.trigger(trig)
- if err != nil {
- errc <- err
- return
- }
- }
-
- select {
- case errc <- ps.expect(e.Expects):
- case <-done:
- }
- }()
-
- // time out globally or finish when all expectations satisfied
- t := e.Timeout
- if t == 0 {
- t = 2000 * time.Millisecond
- }
- alarm := time.NewTimer(t)
- select {
- case err := <-errc:
- return err
- case <-alarm.C:
- return errTimedOut
- }
-}
-
-// TestDisconnected tests the disconnections given as arguments
-// the disconnect structs describe what disconnect error is expected on which peer
-func (ps *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
- expects := make(map[discover.NodeID]error)
- for _, disconnect := range disconnects {
- expects[disconnect.Peer] = disconnect.Error
- }
-
- timeout := time.After(time.Second)
- for len(expects) > 0 {
- select {
- case event := <-ps.events:
- if event.Type != p2p.PeerEventTypeDrop {
- continue
- }
- expectErr, ok := expects[event.Peer]
- if !ok {
- continue
- }
-
- if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) {
- return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error)
- }
- delete(expects, event.Peer)
- case <-timeout:
- return errors.New("timed out waiting for peers to disconnect")
- }
- }
- return nil
-}
diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go
deleted file mode 100644
index df291202a0..0000000000
--- a/p2p/testing/protocoltester.go
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2017 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 .
-
-/*
-the p2p/testing package provides a unit test scheme to check simple
-protocol message exchanges with one pivot node and a number of dummy peers
-The pivot test node runs a node.Service, the dummy peers run a mock node
-that can be used to send and receive messages
-*/
-
-package testing
-
-import (
- "bytes"
- "fmt"
- "io"
- "strings"
- "sync"
- "testing"
-
- "github.com/XinFinOrg/XDPoSChain/log"
- "github.com/XinFinOrg/XDPoSChain/node"
- "github.com/XinFinOrg/XDPoSChain/p2p"
- "github.com/XinFinOrg/XDPoSChain/p2p/discover"
- "github.com/XinFinOrg/XDPoSChain/p2p/simulations"
- "github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
- "github.com/XinFinOrg/XDPoSChain/rlp"
- "github.com/XinFinOrg/XDPoSChain/rpc"
-)
-
-// ProtocolTester is the tester environment used for unit testing protocol
-// message exchanges. It uses p2p/simulations framework
-type ProtocolTester struct {
- *ProtocolSession
- network *simulations.Network
-}
-
-// NewProtocolTester constructs a new ProtocolTester
-// it takes as argument the pivot node id, the number of dummy peers and the
-// protocol run function called on a peer connection by the p2p server
-func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
- services := adapters.Services{
- "test": func(ctx *adapters.ServiceContext) (node.Service, error) {
- return &testNode{run}, nil
- },
- "mock": func(ctx *adapters.ServiceContext) (node.Service, error) {
- return newMockNode(), nil
- },
- }
- adapter := adapters.NewSimAdapter(services)
- net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{})
- if _, err := net.NewNodeWithConfig(&adapters.NodeConfig{
- ID: id,
- EnableMsgEvents: true,
- Services: []string{"test"},
- }); err != nil {
- panic(err.Error())
- }
- if err := net.Start(id); err != nil {
- panic(err.Error())
- }
-
- node := net.GetNode(id).Node.(*adapters.SimNode)
- peers := make([]*adapters.NodeConfig, n)
- peerIDs := make([]discover.NodeID, n)
- for i := 0; i < n; i++ {
- peers[i] = adapters.RandomNodeConfig()
- peers[i].Services = []string{"mock"}
- peerIDs[i] = peers[i].ID
- }
- events := make(chan *p2p.PeerEvent, 1000)
- node.SubscribeEvents(events)
- ps := &ProtocolSession{
- Server: node.Server(),
- IDs: peerIDs,
- adapter: adapter,
- events: events,
- }
- self := &ProtocolTester{
- ProtocolSession: ps,
- network: net,
- }
-
- self.Connect(id, peers...)
-
- return self
-}
-
-// Stop stops the p2p server
-func (pt *ProtocolTester) Stop() error {
- pt.Server.Stop()
- return nil
-}
-
-// Connect brings up the remote peer node and connects it using the
-// p2p/simulations network connection with the in memory network adapter
-func (pt *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) {
- for _, peer := range peers {
- log.Trace(fmt.Sprintf("start node %v", peer.ID))
- if _, err := pt.network.NewNodeWithConfig(peer); err != nil {
- panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
- }
- if err := pt.network.Start(peer.ID); err != nil {
- panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err))
- }
- log.Trace(fmt.Sprintf("connect to %v", peer.ID))
- if err := pt.network.Connect(selfID, peer.ID); err != nil {
- panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err))
- }
- }
-
-}
-
-// testNode wraps a protocol run function and implements the node.Service
-// interface
-type testNode struct {
- run func(*p2p.Peer, p2p.MsgReadWriter) error
-}
-
-func (tn *testNode) Protocols() []p2p.Protocol {
- return []p2p.Protocol{{
- Length: 100,
- Run: tn.run,
- }}
-}
-
-func (tn *testNode) APIs() []rpc.API {
- return nil
-}
-
-func (tn *testNode) Start(server *p2p.Server) error {
- return nil
-}
-
-func (tn *testNode) Stop() error {
- return nil
-}
-
-// mockNode is a testNode which doesn't actually run a protocol, instead
-// exposing channels so that tests can manually trigger and expect certain
-// messages
-type mockNode struct {
- testNode
-
- trigger chan *Trigger
- expect chan []Expect
- err chan error
- stop chan struct{}
- stopOnce sync.Once
-}
-
-func newMockNode() *mockNode {
- mock := &mockNode{
- trigger: make(chan *Trigger),
- expect: make(chan []Expect),
- err: make(chan error),
- stop: make(chan struct{}),
- }
- mock.testNode.run = mock.Run
- return mock
-}
-
-// Run is a protocol run function which just loops waiting for tests to
-// instruct it to either trigger or expect a message from the peer
-func (mn *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
- for {
- select {
- case trig := <-mn.trigger:
- mn.err <- p2p.Send(rw, trig.Code, trig.Msg)
- case exps := <-mn.expect:
- mn.err <- expectMsgs(rw, exps)
- case <-mn.stop:
- return nil
- }
- }
-}
-
-func (mn *mockNode) Trigger(trig *Trigger) error {
- mn.trigger <- trig
- return <-mn.err
-}
-
-func (mn *mockNode) Expect(exp ...Expect) error {
- mn.expect <- exp
- return <-mn.err
-}
-
-func (mn *mockNode) Stop() error {
- mn.stopOnce.Do(func() { close(mn.stop) })
- return nil
-}
-
-func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error {
- matched := make([]bool, len(exps))
- for {
- msg, err := rw.ReadMsg()
- if err != nil {
- if err == io.EOF {
- break
- }
- return err
- }
- actualContent, err := io.ReadAll(msg.Payload)
- if err != nil {
- return err
- }
- var found bool
- for i, exp := range exps {
- if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(exp.Msg)) {
- if matched[i] {
- return fmt.Errorf("message #%d received two times", i)
- }
- matched[i] = true
- found = true
- break
- }
- }
- if !found {
- expected := make([]string, 0)
- for i, exp := range exps {
- if matched[i] {
- continue
- }
- expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(exp.Msg)))
- }
- return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or "))
- }
- done := true
- for _, m := range matched {
- if !m {
- done = false
- break
- }
- }
- if done {
- return nil
- }
- }
- for i, m := range matched {
- if !m {
- return fmt.Errorf("expected message #%d not received", i)
- }
- }
- return nil
-}
-
-// mustEncodeMsg uses rlp to encode a message.
-// In case of error it panics.
-func mustEncodeMsg(msg interface{}) []byte {
- contentEnc, err := rlp.EncodeToBytes(msg)
- if err != nil {
- panic("content encode error: " + err.Error())
- }
- return contentEnc
-}