all: refactor package node #21105 (#923)

This commit is contained in:
JukLee0ira 2025-04-10 18:52:49 +08:00 committed by GitHub
parent 0cc4813e48
commit b1e08e6642
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 2213 additions and 2900 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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.

View file

@ -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)
}

View file

@ -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(&ethereum); 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

View file

@ -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 {

View file

@ -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 {

View file

@ -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

View file

@ -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(&ethServ)
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)
}
}

View file

@ -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(&ethereum)
return &tester{
workspace: workspace,
stack: stack,
ethereum: ethereum,
ethereum: ethBackend,
console: console,
input: prompter,
output: printer,

View file

@ -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
}

View file

@ -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()

View file

@ -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,

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
}

350
node/api_test.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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 "
}

View file

@ -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

View file

@ -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
}

View file

@ -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 {

31
node/lifecycle.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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
}

View file

@ -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...
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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.

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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()
}

View file

@ -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,
},
}
}

View file

@ -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 <peer>
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"),
)
}

View file

@ -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

View file

@ -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)

View file

@ -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(),
),
)
}

View file

@ -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) {

View file

@ -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(),

View file

@ -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

View file

@ -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
}

View file

@ -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,
}

View file

@ -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
}

View file

@ -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{

View file

@ -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 <http://www.gnu.org/licenses/>.
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]
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
/*
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
}