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/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"golang.org/x/sync/syncmap" "golang.org/x/sync/syncmap"
@ -67,7 +68,7 @@ func (XDCx *XDCX) Protocols() []p2p.Protocol {
return []p2p.Protocol{} return []p2p.Protocol{}
} }
func (XDCx *XDCX) Start(server *p2p.Server) error { func (XDCx *XDCX) Start() error {
return nil return nil
} }
@ -91,7 +92,7 @@ func NewMongoDBEngine(cfg *Config) *XDCxDAO.MongoDatabase {
return mongoDB return mongoDB
} }
func New(cfg *Config) *XDCX { func New(stack *node.Node, cfg *Config) *XDCX {
XDCX := &XDCX{ XDCX := &XDCX{
orderNonce: make(map[common.Address]*big.Int), orderNonce: make(map[common.Address]*big.Int),
Triegc: prque.New[int64, common.Hash](nil), Triegc: prque.New[int64, common.Hash](nil),
@ -111,6 +112,9 @@ func New(cfg *Config) *XDCX {
XDCX.StateCache = tradingstate.NewDatabase(XDCX.db) XDCX.StateCache = tradingstate.NewDatabase(XDCX.db)
XDCX.settings.Store(overflowIdx, false) XDCX.settings.Store(overflowIdx, false)
stack.RegisterAPIs(XDCX.APIs())
stack.RegisterProtocols(XDCX.Protocols())
stack.RegisterLifecycle(XDCX)
return XDCX return XDCX
} }

View file

@ -9,6 +9,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/node"
) )
func Test_getCancelFeeV1(t *testing.T) { func Test_getCancelFeeV1(t *testing.T) {
@ -90,7 +91,12 @@ func Test_getCancelFeeV1(t *testing.T) {
} }
func Test_getCancelFee(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() db := rawdb.NewMemoryDatabase()
stateCache := tradingstate.NewDatabase(db) stateCache := tradingstate.NewDatabase(db)
tradingStateDb, _ := tradingstate.New(types.EmptyRootHash, stateCache) tradingStateDb, _ := tradingstate.New(types.EmptyRootHash, stateCache)

View file

@ -19,6 +19,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
) )
@ -50,7 +51,7 @@ func (l *Lending) Protocols() []p2p.Protocol {
return []p2p.Protocol{} return []p2p.Protocol{}
} }
func (l *Lending) Start(server *p2p.Server) error { func (l *Lending) Start() error {
return nil return nil
} }
@ -58,7 +59,7 @@ func (l *Lending) Stop() error {
return nil return nil
} }
func New(XDCx *XDCx.XDCX) *Lending { func New(stack *node.Node, XDCx *XDCx.XDCX) *Lending {
lending := &Lending{ lending := &Lending{
orderNonce: make(map[common.Address]*big.Int), orderNonce: make(map[common.Address]*big.Int),
Triegc: prque.New[int64, common.Hash](nil), Triegc: prque.New[int64, common.Hash](nil),
@ -67,6 +68,12 @@ func New(XDCx *XDCx.XDCX) *Lending {
} }
lending.StateCache = lendingstate.NewDatabase(XDCx.GetLevelDB()) lending.StateCache = lendingstate.NewDatabase(XDCx.GetLevelDB())
lending.XDCx = XDCx lending.XDCx = XDCx
// Register the backend on the node
stack.RegisterAPIs(lending.APIs())
stack.RegisterProtocols(lending.Protocols())
stack.RegisterLifecycle(lending)
return lending return lending
} }

View file

@ -11,6 +11,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/node"
) )
func Test_getCancelFeeV1(t *testing.T) { func Test_getCancelFeeV1(t *testing.T) {
@ -95,7 +96,12 @@ func Test_getCancelFeeV1(t *testing.T) {
} }
func Test_getCancelFee(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() db := rawdb.NewMemoryDatabase()
stateCache := tradingstate.NewDatabase(db) stateCache := tradingstate.NewDatabase(db)
tradingStateDb, _ := tradingstate.New(types.EmptyRootHash, stateCache) tradingStateDb, _ := tradingstate.New(types.EmptyRootHash, stateCache)
@ -113,7 +119,7 @@ func Test_getCancelFee(t *testing.T) {
// set tokenBPrice = 1 XDC // set tokenBPrice = 1 XDC
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenB, common.XDCNativeAddressBinary), common.BasePrice) tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenB, common.XDCNativeAddressBinary), common.BasePrice)
l := New(XDCx) l := New(stack, XDCx)
type CancelFeeArg struct { type CancelFeeArg struct {
borrowFeeRate *big.Int borrowFeeRate *big.Int

View file

@ -30,6 +30,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/XDCx" "github.com/XinFinOrg/XDPoSChain/XDCx"
"github.com/XinFinOrg/XDPoSChain/XDCxlending" "github.com/XinFinOrg/XDPoSChain/XDCxlending"
"github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/accounts/abi" "github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/accounts/keystore" "github.com/XinFinOrg/XDPoSChain/accounts/keystore"
@ -49,6 +51,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/eth/filters" "github.com/XinFinOrg/XDPoSChain/eth/filters"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
) )
@ -117,8 +120,16 @@ func NewXDCSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64, chainConf
var DefaultConfig = XDCx.Config{ var DefaultConfig = XDCx.Config{
DataDir: "", DataDir: "",
} }
XDCXServ := XDCx.New(&DefaultConfig) stack, err := node.New(&node.Config{DataDir: ""})
lendingServ := XDCxlending.New(XDCXServ) 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 { consensus.GetXDCXService = func() utils.TradingService {
return XDCXServ return XDCXServ

View file

@ -174,7 +174,7 @@ func initGenesis(ctx *cli.Context) error {
} }
// Open an initialise both full and light databases // Open an initialise both full and light databases
stack, _ := makeFullNode(ctx) stack, _ := makeConfigNode(ctx)
defer stack.Close() defer stack.Close()
name := "chaindata" name := "chaindata"
@ -195,7 +195,7 @@ func importChain(ctx *cli.Context) error {
if ctx.Args().Len() < 1 { if ctx.Args().Len() < 1 {
utils.Fatalf("This command requires an argument.") utils.Fatalf("This command requires an argument.")
} }
stack, cfg := makeFullNode(ctx) stack, _, cfg := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
// Start metrics export if enabled // Start metrics export if enabled
@ -273,7 +273,7 @@ func exportChain(ctx *cli.Context) error {
utils.Fatalf("This command requires an argument.") utils.Fatalf("This command requires an argument.")
} }
stack, _ := makeFullNode(ctx) stack, _, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
chain, db := utils.MakeChain(ctx, stack, true) chain, db := utils.MakeChain(ctx, stack, true)
@ -310,7 +310,7 @@ func importPreimages(ctx *cli.Context) error {
utils.Fatalf("This command requires an argument.") utils.Fatalf("This command requires an argument.")
} }
stack, _ := makeFullNode(ctx) stack, _, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, false) db := utils.MakeChainDatabase(ctx, stack, false)
@ -329,7 +329,7 @@ func exportPreimages(ctx *cli.Context) error {
if ctx.Args().Len() < 1 { if ctx.Args().Len() < 1 {
utils.Fatalf("This command requires an argument.") utils.Fatalf("This command requires an argument.")
} }
stack, _ := makeFullNode(ctx) stack, _, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
db := utils.MakeChainDatabase(ctx, stack, true) db := utils.MakeChainDatabase(ctx, stack, true)
@ -344,7 +344,7 @@ func exportPreimages(ctx *cli.Context) error {
} }
func dump(ctx *cli.Context) error { func dump(ctx *cli.Context) error {
stack, _ := makeFullNode(ctx) stack, _, _ := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
chain, chainDb := utils.MakeChain(ctx, stack, true) chain, chainDb := utils.MakeChain(ctx, stack, true)

View file

@ -24,6 +24,7 @@ import (
"math/big" "math/big"
"os" "os"
"reflect" "reflect"
"runtime"
"slices" "slices"
"strings" "strings"
"unicode" "unicode"
@ -35,6 +36,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/cmd/utils" "github.com/XinFinOrg/XDPoSChain/cmd/utils"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/eth/ethconfig" "github.com/XinFinOrg/XDPoSChain/eth/ethconfig"
"github.com/XinFinOrg/XDPoSChain/internal/ethapi"
"github.com/XinFinOrg/XDPoSChain/internal/flags" "github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/metrics" "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) stack, cfg := makeConfigNode(ctx)
// Start metrics export if enabled // Start metrics export if enabled
@ -224,15 +226,29 @@ func makeFullNode(ctx *cli.Context) (*node.Node, XDCConfig) {
// Register XDCX's OrderBook service if requested. // Register XDCX's OrderBook service if requested.
// enable in default // enable in default
utils.RegisterXDCXService(stack, &cfg.XDCX) XDCXServ, lendingServ := utils.RegisterXDCXService(stack, &cfg.XDCX)
utils.RegisterEthService(stack, &cfg.Eth, cfg.Node.Version) 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. // Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != "" { if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
} }
return stack, cfg return stack, backend, cfg
} }
// dumpConfig is the dumpconfig command. // dumpConfig is the dumpconfig command.

View file

@ -75,12 +75,12 @@ JavaScript API. See https://github.com/XinFinOrg/XDPoSChain/wiki/JavaScript-Cons
// same time. // same time.
func localConsole(ctx *cli.Context) error { func localConsole(ctx *cli.Context) error {
// Create and start the node based on the CLI flags // Create and start the node based on the CLI flags
node, cfg := makeFullNode(ctx) stack, backend, cfg := makeFullNode(ctx)
startNode(ctx, node, cfg) startNode(ctx, stack, backend, cfg)
defer node.Close() defer stack.Close()
// Attach to the newly started node and start the JavaScript console // Attach to the newly started node and start the JavaScript console
client, err := node.Attach() client, err := stack.Attach()
if err != nil { if err != nil {
utils.Fatalf("Failed to attach to the inproc XDC: %v", err) utils.Fatalf("Failed to attach to the inproc XDC: %v", err)
} }
@ -180,12 +180,12 @@ func dialRPC(endpoint string) (*rpc.Client, error) {
// everything down. // everything down.
func ephemeralConsole(ctx *cli.Context) error { func ephemeralConsole(ctx *cli.Context) error {
// Create and start the node based on the CLI flags // Create and start the node based on the CLI flags
node, cfg := makeFullNode(ctx) stack, backend, cfg := makeFullNode(ctx)
startNode(ctx, node, cfg) startNode(ctx, stack, backend, cfg)
defer node.Close() defer stack.Close()
// Attach to the newly started node and start the JavaScript console // Attach to the newly started node and start the JavaScript console
client, err := node.Attach() client, err := stack.Attach()
if err != nil { if err != nil {
utils.Fatalf("Failed to attach to the inproc XDC: %v", err) 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/eth"
"github.com/XinFinOrg/XDPoSChain/ethclient" "github.com/XinFinOrg/XDPoSChain/ethclient"
"github.com/XinFinOrg/XDPoSChain/internal/debug" "github.com/XinFinOrg/XDPoSChain/internal/debug"
"github.com/XinFinOrg/XDPoSChain/internal/ethapi"
"github.com/XinFinOrg/XDPoSChain/internal/flags" "github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/metrics" "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 // It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down. // blocking mode, waiting for it to be shut down.
func XDC(ctx *cli.Context) error { func XDC(ctx *cli.Context) error {
node, cfg := makeFullNode(ctx) stack, backend, cfg := makeFullNode(ctx)
defer node.Close() defer stack.Close()
startNode(ctx, node, cfg) startNode(ctx, stack, backend, cfg)
node.Wait() stack.Wait()
return nil return nil
} }
// startNode boots up the system node and all registered protocols, after which // startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner. // miner.
func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) { func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, cfg XDCConfig) {
// Start up the node itself // Start up the node itself
utils.StartNode(stack) utils.StartNode(stack)
@ -324,17 +325,17 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
}() }()
// Start auxiliary services if enabled // Start auxiliary services if enabled
var ethereum *eth.Ethereum ethBackend, ok := backend.(*eth.EthAPIBackend)
if err := stack.Service(&ethereum); err != nil { if !ok {
utils.Fatalf("Ethereum service not running: %v", err) utils.Fatalf("Ethereum service not running")
} }
if engine, ok := ethereum.Engine().(*XDPoS.XDPoS); ok { if engine, ok := ethBackend.Engine().(*XDPoS.XDPoS); ok {
go func() { go func() {
started := false started := false
ok := false ok := false
slaveMode := ctx.IsSet(utils.XDCSlaveModeFlag.Name) slaveMode := ctx.IsSet(utils.XDCSlaveModeFlag.Name)
var err error var err error
ok, err = ethereum.ValidateMasternode() ok, err = ethBackend.ValidateMasternode()
if err != nil { if err != nil {
utils.Fatalf("Can't verify masternode permission: %v", err) 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 { type threaded interface {
SetThreads(threads int) SetThreads(threads int)
} }
if th, ok := ethereum.Engine().(threaded); ok { if th, ok := ethBackend.Engine().(threaded); ok {
th.SetThreads(threads) th.SetThreads(threads)
} }
} }
// Set the gas price to the limits from the CLI and start mining // Set the gas price to the limits from the CLI and start mining
ethereum.TxPool().SetGasPrice(cfg.Eth.GasPrice) ethBackend.TxPool().SetGasPrice(cfg.Eth.GasPrice)
if err := ethereum.StartStaking(true); err != nil { if err := ethBackend.StartStaking(true); err != nil {
utils.Fatalf("Failed to start staking: %v", err) utils.Fatalf("Failed to start staking: %v", err)
} }
started = true started = true
@ -366,17 +367,17 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
for range core.CheckpointCh { for range core.CheckpointCh {
log.Info("Checkpoint!!! It's time to reconcile node's state...") log.Info("Checkpoint!!! It's time to reconcile node's state...")
log.Info("Update consensus parameters") log.Info("Update consensus parameters")
chain := ethereum.BlockChain() chain := ethBackend.BlockChain()
engine.UpdateParams(chain.CurrentHeader()) engine.UpdateParams(chain.CurrentHeader())
ok, err = ethereum.ValidateMasternode() ok, err = ethBackend.ValidateMasternode()
if err != nil { if err != nil {
utils.Fatalf("Can't verify masternode permission: %v", err) utils.Fatalf("Can't verify masternode permission: %v", err)
} }
if !ok { if !ok {
if started { if started {
log.Info("Only masternode can propose and verify blocks. Cancelling staking on this node...") log.Info("Only masternode can propose and verify blocks. Cancelling staking on this node...")
ethereum.StopStaking() ethBackend.StopStaking()
started = false started = false
log.Info("Cancelled mining mode!!!") log.Info("Cancelled mining mode!!!")
} }
@ -391,13 +392,13 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
type threaded interface { type threaded interface {
SetThreads(threads int) SetThreads(threads int)
} }
if th, ok := ethereum.Engine().(threaded); ok { if th, ok := ethBackend.Engine().(threaded); ok {
th.SetThreads(threads) th.SetThreads(threads)
} }
} }
// Set the gas price to the limits from the CLI and start mining // Set the gas price to the limits from the CLI and start mining
ethereum.TxPool().SetGasPrice(cfg.Eth.GasPrice) ethBackend.TxPool().SetGasPrice(cfg.Eth.GasPrice)
if err := ethereum.StartStaking(true); err != nil { if err := ethBackend.StartStaking(true); err != nil {
utils.Fatalf("Failed to start staking: %v", err) utils.Fatalf("Failed to start staking: %v", err)
} }
started = true started = true

View file

@ -309,7 +309,7 @@ func createNode(ctx *cli.Context) error {
config.PrivateKey = privKey config.PrivateKey = privKey
} }
if services := ctx.String("services"); services != "" { if services := ctx.String("services"); services != "" {
config.Services = strings.Split(services, ",") config.Lifecycles = strings.Split(services, ",")
} }
node, err := client.CreateNode(config) node, err := client.CreateNode(config)
if err != nil { if err != nil {

View file

@ -74,7 +74,7 @@ func StartNode(stack *node.Node) {
defer signal.Stop(sigc) defer signal.Stop(sigc)
<-sigc <-sigc
log.Info("Got interrupt, shutting down...") log.Info("Got interrupt, shutting down...")
go stack.Stop() go stack.Close()
for i := 10; i > 0; i-- { for i := 10; i > 0; i-- {
<-sigc <-sigc
if i > 1 { if i > 1 {

View file

@ -32,6 +32,7 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/XDCx" "github.com/XinFinOrg/XDPoSChain/XDCx"
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
"github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/accounts/keystore" "github.com/XinFinOrg/XDPoSChain/accounts/keystore"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
@ -43,10 +44,13 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/txpool" "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "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/ethconfig"
"github.com/XinFinOrg/XDPoSChain/eth/filters" "github.com/XinFinOrg/XDPoSChain/eth/filters"
"github.com/XinFinOrg/XDPoSChain/eth/gasprice" "github.com/XinFinOrg/XDPoSChain/eth/gasprice"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/ethstats"
"github.com/XinFinOrg/XDPoSChain/internal/ethapi" "github.com/XinFinOrg/XDPoSChain/internal/ethapi"
"github.com/XinFinOrg/XDPoSChain/internal/flags" "github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/XinFinOrg/XDPoSChain/log" "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. // SetupNetwork configures the system for either the main net or some test network.
func SetupNetwork(ctx *cli.Context) { func SetupNetwork(ctx *cli.Context) {
// TODO(fjl): move target gas limit into config // 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 { if confOverride != nil {
confOverride(ethConf) confOverride(ethConf)
} }
if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { ethBackend, err := eth.New(stack, ethConf, &XDCx.XDCX{}, &XDCxlending.Lending{})
return eth.New(ctx, ethConf, &XDCx.XDCX{}, &XDCxlending.Lending{}) if err != nil {
}); err != nil {
t.Fatalf("failed to register Ethereum protocol: %v", err) t.Fatalf("failed to register Ethereum protocol: %v", err)
} }
// Start the node and assemble the JavaScript console around it // Start the node and assemble the JavaScript console around it
@ -132,13 +131,11 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
t.Fatalf("failed to create JavaScript console: %v", err) t.Fatalf("failed to create JavaScript console: %v", err)
} }
// Create the final tester and return // Create the final tester and return
var ethereum *eth.Ethereum
stack.Service(&ethereum)
return &tester{ return &tester{
workspace: workspace, workspace: workspace,
stack: stack, stack: stack,
ethereum: ethereum, ethereum: ethBackend,
console: console, console: console,
input: prompter, input: prompter,
output: printer, output: printer,

View file

@ -38,6 +38,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/bloombits" "github.com/XinFinOrg/XDPoSChain/core/bloombits"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/downloader"
@ -46,31 +47,32 @@ import (
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/miner"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
) )
// EthApiBackend implements ethapi.Backend for full nodes // EthAPIBackend implements ethapi.Backend for full nodes
type EthApiBackend struct { type EthAPIBackend struct {
eth *Ethereum eth *Ethereum
gpo *gasprice.Oracle gpo *gasprice.Oracle
XDPoS *XDPoS.XDPoS XDPoS *XDPoS.XDPoS
} }
func (b *EthApiBackend) ChainConfig() *params.ChainConfig { func (b *EthAPIBackend) ChainConfig() *params.ChainConfig {
return b.eth.chainConfig return b.eth.chainConfig
} }
func (b *EthApiBackend) CurrentBlock() *types.Block { func (b *EthAPIBackend) CurrentBlock() *types.Block {
return b.eth.blockchain.CurrentBlock() return b.eth.blockchain.CurrentBlock()
} }
func (b *EthApiBackend) SetHead(number uint64) { func (b *EthAPIBackend) SetHead(number uint64) {
b.eth.protocolManager.downloader.Cancel() b.eth.protocolManager.downloader.Cancel()
b.eth.blockchain.SetHead(number) 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 // Pending block is only known by the miner
if blockNr == rpc.PendingBlockNumber { if blockNr == rpc.PendingBlockNumber {
blockNr = rpc.LatestBlockNumber blockNr = rpc.LatestBlockNumber
@ -103,7 +105,7 @@ func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNum
return header, nil 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 { if blockNr, ok := blockNrOrHash.Number(); ok {
return b.HeaderByNumber(ctx, blockNr) 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") 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 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 // Pending block is only known by the miner
if blockNr == rpc.PendingBlockNumber { if blockNr == rpc.PendingBlockNumber {
blockNr = rpc.LatestBlockNumber 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 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 return b.eth.blockchain.GetBlockByHash(hash), nil
} }
// GetBody returns body of a block. It does not resolve special block numbers. // 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{}) { if number < 0 || hash == (common.Hash{}) {
return nil, errors.New("invalid arguments; expect hash and no special block numbers") 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") 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 { if blockNr, ok := blockNrOrHash.Number(); ok {
return b.BlockByNumber(ctx, blockNr) 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") 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() 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 // Pending state is only known by the miner
if blockNr == rpc.PendingBlockNumber { if blockNr == rpc.PendingBlockNumber {
blockNr = rpc.LatestBlockNumber blockNr = rpc.LatestBlockNumber
@ -210,7 +212,7 @@ func (b *EthApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.
return stateDb, header, err 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 { if blockNr, ok := blockNrOrHash.Number(); ok {
return b.StateAndHeaderByNumber(ctx, blockNr) 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") 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 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 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 return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil
} }
func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int { func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash) 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 } vmError := func() error { return nil }
if vmConfig == nil { if vmConfig == nil {
vmConfig = b.eth.blockchain.GetVMConfig() 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 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) 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) 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) 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) 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) 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) 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) return b.eth.txPool.AddLocal(signedTx)
} }
// SendOrderTx send order via backend // 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) return b.eth.orderPool.AddLocal(signedTx)
} }
// SendLendingTx send order via backend // 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) 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) pending := b.eth.txPool.Pending(false)
var txs types.Transactions var txs types.Transactions
for _, batch := range pending { for _, batch := range pending {
@ -308,89 +310,97 @@ func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) {
return txs, nil 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) 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 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() 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() 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) 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() 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() 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) return b.eth.TxPool().SubscribeNewTxsEvent(ch)
} }
func (b *EthApiBackend) Downloader() *downloader.Downloader { func (b *EthAPIBackend) Downloader() *downloader.Downloader {
return b.eth.Downloader() return b.eth.Downloader()
} }
func (b *EthApiBackend) ProtocolVersion() int { func (b *EthAPIBackend) ProtocolVersion() int {
return b.eth.EthVersion() 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) 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) 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) return new(big.Int)
} }
func (b *EthApiBackend) ChainDb() ethdb.Database { func (b *EthAPIBackend) ChainDb() ethdb.Database {
return b.eth.ChainDb() return b.eth.ChainDb()
} }
func (b *EthApiBackend) EventMux() *event.TypeMux { func (b *EthAPIBackend) EventMux() *event.TypeMux {
return b.eth.EventMux() return b.eth.EventMux()
} }
func (b *EthApiBackend) RPCGasCap() uint64 { func (b *EthAPIBackend) RPCGasCap() uint64 {
return b.eth.config.RPCGasCap return b.eth.config.RPCGasCap
} }
func (b *EthApiBackend) AccountManager() *accounts.Manager { func (b *EthAPIBackend) AccountManager() *accounts.Manager {
return b.eth.AccountManager() return b.eth.AccountManager()
} }
func (b *EthApiBackend) RPCTxFeeCap() float64 { func (b *EthAPIBackend) RPCTxFeeCap() float64 {
return b.eth.config.RPCTxFeeCap return b.eth.config.RPCTxFeeCap
} }
func (b *EthApiBackend) BloomStatus() (uint64, uint64) { func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
sections, _, _ := b.eth.bloomIndexer.Sections() sections, _, _ := b.eth.bloomIndexer.Sections()
return params.BloomBitsBlocks, 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++ { for i := 0; i < bloomFilterThreads; i++ {
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) 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) { // func (b *EthApiBackend) GetIPCClient() (*ethclient.Client, error) {
client, err := b.eth.blockchain.GetClient() client, err := b.eth.blockchain.GetClient()
if err != nil { if err != nil {
@ -400,19 +410,15 @@ func (b *EthApiBackend) GetIPCClient() (bind.ContractBackend, error) {
return client, nil return client, nil
} }
func (b *EthApiBackend) GetEngine() consensus.Engine { func (b *EthAPIBackend) CurrentHeader() *types.Header {
return b.eth.engine
}
func (b *EthApiBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader() 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) 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) header := s.eth.blockchain.GetHeaderByHash(hash)
if header != nil { if header != nil {
data, err := os.ReadFile(filepath.Join(common.StoreRewardFolder, header.Number.String()+"."+header.Hash().Hex())) 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 // 2. Get list signers + reward at that checkpoint
// 3. Find out the list signers_reward for input masternode's reward // 3. Find out the list signers_reward for input masternode's reward
// 4. Calculate voters's rewards for input masternode // 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 chain := b.eth.blockchain
block := chain.CurrentBlock() block := chain.CurrentBlock()
number := block.Number().Uint64() number := block.Number().Uint64()
engine := b.GetEngine().(*XDPoS.XDPoS) engine := b.Engine().(*XDPoS.XDPoS)
foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr
// calculate for 2 epochs ago // 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 // 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 chain := b.eth.blockchain
checkpointBlock := chain.GetBlockByNumber(checkpoint.Uint64()) checkpointBlock := chain.GetBlockByNumber(checkpoint.Uint64())
statedb, err := chain.StateAt(checkpointBlock.Root()) 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 // GetEpochDuration return latest generating velocity epoch by minute
// ie 30min for each epoch // ie 30min for each epoch
func (b *EthApiBackend) GetEpochDuration() *big.Int { func (b *EthAPIBackend) GetEpochDuration() *big.Int {
chain := b.eth.blockchain chain := b.eth.blockchain
block := chain.CurrentBlock() block := chain.CurrentBlock()
number := block.Number().Uint64() number := block.Number().Uint64()
@ -561,7 +567,7 @@ func (b *EthApiBackend) GetEpochDuration() *big.Int {
} }
// GetMasternodesCap return a cap of all masternode at a checkpoint // 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) checkpointBlock := b.eth.blockchain.GetBlockByNumber(checkpoint)
statedb, err := b.eth.blockchain.StateAt(checkpointBlock.Root()) statedb, err := b.eth.blockchain.StateAt(checkpointBlock.Root())
if err != nil { if err != nil {
@ -583,19 +589,19 @@ func (b *EthApiBackend) GetMasternodesCap(checkpoint uint64) map[common.Address]
return masternodesCap return masternodesCap
} }
func (b *EthApiBackend) GetBlocksHashCache(blockNr uint64) []common.Hash { func (b *EthAPIBackend) GetBlocksHashCache(blockNr uint64) []common.Hash {
return b.eth.blockchain.GetBlocksHashCache(blockNr) 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) return b.eth.blockchain.AreTwoBlockSamePath(bh1, bh2)
} }
// GetOrderNonce get order nonce // 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() XDCxService := b.eth.GetXDCX()
if XDCxService != nil { if XDCxService != nil {
author, err := b.GetEngine().Author(b.CurrentBlock().Header()) author, err := b.Engine().Author(b.CurrentBlock().Header())
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -608,10 +614,39 @@ func (b *EthApiBackend) GetOrderNonce(address common.Hash) (uint64, error) {
return 0, errors.New("cannot find XDCx service") return 0, errors.New("cannot find XDCx service")
} }
func (b *EthApiBackend) XDCxService() *XDCx.XDCX { func (b *EthAPIBackend) XDCxService() *XDCx.XDCX {
return b.eth.XDCX return b.eth.XDCX
} }
func (b *EthApiBackend) LendingService() *XDCxlending.Lending { func (b *EthAPIBackend) LendingService() *XDCxlending.Lending {
return b.eth.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" "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. // Ethereum implements the Ethereum full node service.
type Ethereum struct { type Ethereum struct {
config *ethconfig.Config config *ethconfig.Config
@ -80,7 +73,6 @@ type Ethereum struct {
lendingPool *txpool.LendingPool lendingPool *txpool.LendingPool
blockchain *core.BlockChain blockchain *core.BlockChain
protocolManager *ProtocolManager protocolManager *ProtocolManager
lesServer LesServer
// DB interfaces // DB interfaces
chainDb ethdb.Database // Block chain database chainDb ethdb.Database // Block chain database
@ -92,7 +84,7 @@ type Ethereum struct {
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
ApiBackend *EthApiBackend ApiBackend *EthAPIBackend
miner *miner.Miner miner *miner.Miner
gasPrice *big.Int gasPrice *big.Int
@ -101,19 +93,16 @@ type Ethereum struct {
networkId uint64 networkId uint64
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI
p2pServer *p2p.Server
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
XDCX *XDCx.XDCX XDCX *XDCx.XDCX
Lending *XDCxlending.Lending Lending *XDCxlending.Lending
} }
func (e *Ethereum) AddLesServer(ls LesServer) {
e.lesServer = ls
ls.SetBloomBitsIndexer(e.bloomIndexer)
}
// New creates a new Ethereum object (including the // New creates a new Ethereum object (including the
// initialisation of the common Ethereum object) // initialisation of the common Ethereum object)
func New(ctx *node.ServiceContext, config *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 { if config.SyncMode == downloader.LightSync {
return nil, errors.New("can't run eth.Ethereum in light sync mode, light mode has been deprecated") 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 // 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 { if err != nil {
return nil, err return nil, err
} }
@ -147,15 +136,16 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
config: config, config: config,
chainDb: chainDb, chainDb: chainDb,
chainConfig: chainConfig, chainConfig: chainConfig,
eventMux: ctx.EventMux, eventMux: stack.EventMux(),
accountManager: ctx.AccountManager, accountManager: stack.AccountManager(),
engine: CreateConsensusEngine(ctx, &config.Ethash, chainConfig, chainDb), engine: CreateConsensusEngine(stack, &config.Ethash, chainConfig, chainDb),
shutdownChan: make(chan bool), shutdownChan: make(chan bool),
networkId: networkID, networkId: networkID,
gasPrice: config.GasPrice, gasPrice: config.GasPrice,
etherbase: config.Etherbase, etherbase: config.Etherbase,
bloomRequests: make(chan chan *bloombits.Retrieval), bloomRequests: make(chan chan *bloombits.Retrieval),
bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
p2pServer: stack.Server(),
} }
// Inject XDCX Service into main Eth Service. // Inject XDCX Service into main Eth Service.
if XDCXServ != nil { if XDCXServ != nil {
@ -237,7 +227,7 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX
eth.bloomIndexer.Start(eth.blockchain) eth.bloomIndexer.Start(eth.blockchain)
if config.TxPool.Journal != "" { if config.TxPool.Journal != "" {
config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal) config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
} }
eth.txPool = txpool.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain) eth.txPool = txpool.NewTxPool(config.TxPool, eth.chainConfig, eth.blockchain)
eth.orderPool = txpool.NewOrderPool(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 { 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 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)) eth.miner.SetExtra(makeExtraData(config.ExtraData))
if eth.chainConfig.XDPoS != nil { if eth.chainConfig.XDPoS != nil {
eth.ApiBackend = &EthApiBackend{eth, nil, eth.engine.(*XDPoS.XDPoS)} eth.ApiBackend = &EthAPIBackend{eth, nil, eth.engine.(*XDPoS.XDPoS)}
} else { } else {
eth.ApiBackend = &EthApiBackend{eth, nil, nil} eth.ApiBackend = &EthAPIBackend{eth, nil, nil}
} }
eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, config.GPO, config.GasPrice) eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, config.GPO, config.GasPrice)
// Set global ipc endpoint. // Set global ipc endpoint.
eth.blockchain.IPCEndpoint = ctx.GetConfig().IPCEndpoint() eth.blockchain.IPCEndpoint = stack.IPCEndpoint()
if eth.chainConfig.XDPoS != nil { if eth.chainConfig.XDPoS != nil {
c := eth.engine.(*XDPoS.XDPoS) 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 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 // 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 delegated-proof-of-stake is requested, set it up
if chainConfig.XDPoS != nil { if chainConfig.XDPoS != nil {
return XDPoS.New(chainConfig, db) return XDPoS.New(chainConfig, db)
@ -374,7 +371,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chai
return ethash.NewShared() return ethash.NewShared()
default: default:
engine := ethash.New(ethash.Config{ engine := ethash.New(ethash.Config{
CacheDir: ctx.ResolvePath(config.CacheDir), CacheDir: stack.ResolvePath(config.CacheDir),
CachesInMem: config.CachesInMem, CachesInMem: config.CachesInMem,
CachesOnDisk: config.CachesOnDisk, CachesOnDisk: config.CachesOnDisk,
DatasetDir: config.DatasetDir, 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) EthVersion() int { return int(e.protocolManager.SubProtocols[0].Version) }
func (e *Ethereum) NetVersion() uint64 { return e.networkId } func (e *Ethereum) NetVersion() uint64 { return e.networkId }
func (e *Ethereum) Downloader() *downloader.Downloader { return e.protocolManager.downloader } 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 // Protocols returns all the currently configured
// network protocols to start.
func (e *Ethereum) Protocols() []p2p.Protocol { func (e *Ethereum) Protocols() []p2p.Protocol {
if e.lesServer == nil { return e.protocolManager.SubProtocols
return e.protocolManager.SubProtocols
}
return append(e.protocolManager.SubProtocols, e.lesServer.Protocols()...)
} }
// Start implements node.Service, starting all internal goroutines needed by the // Start implements node.Lifecycle, starting all internal goroutines needed by the
// Ethereum protocol implementation. // Ethereum protocol implementation.
func (e *Ethereum) Start(srvr *p2p.Server) error { func (e *Ethereum) Start() error {
// Start the bloom bits servicing goroutines // Start the bloom bits servicing goroutines
e.startBloomHandlers(params.BloomBitsBlocks) 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 // 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.LightServ > 0 {
if 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, srvr.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 maxPeers -= e.config.LightPeers
} }
// Start the networking layer and the light server if requested // Start the networking layer and the light server if requested
e.protocolManager.Start(maxPeers) e.protocolManager.Start(maxPeers)
if e.lesServer != nil {
e.lesServer.Start(srvr)
}
return nil return nil
} }
// Stop implements node.Service, terminating all internal goroutines used by the // Stop implements node.Lifecycle, terminating all internal goroutines used by the
// Ethereum protocol. // Ethereum protocol.
func (e *Ethereum) Stop() error { func (e *Ethereum) Stop() error {
e.bloomIndexer.Close() e.bloomIndexer.Close()
e.blockchain.Stop() e.blockchain.Stop()
e.protocolManager.Stop() e.protocolManager.Stop()
if e.lesServer != nil {
e.lesServer.Stop()
}
e.txPool.Stop() e.txPool.Stop()
e.miner.Stop() e.miner.Stop()
e.eventMux.Stop() e.eventMux.Stop()

View file

@ -38,8 +38,11 @@ import (
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth" "github.com/XinFinOrg/XDPoSChain/eth"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/miner"
"github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -63,22 +66,35 @@ type consensusEngine interface {
SubscribeForensicsEvent(chan<- types.ForensicsEvent) event.Subscription SubscribeForensicsEvent(chan<- types.ForensicsEvent) event.Subscription
} }
type txPool interface { // backend encompasses the bare-minimum functionality needed for ethstats reporting
// SubscribeNewTxsEvent should return an event subscription of type backend interface {
// NewTxsEvent and send events to the given channel. SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) 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 { // fullNodeBackend encompasses the functionality necessary for a full node
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription // 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 // Service implements an Ethereum netstats reporting daemon that pushes local
// chain statistics up to a monitoring server. // chain statistics up to a monitoring server.
type Service struct { type Service struct {
server *p2p.Server // Peer-to-peer server to retrieve networking infos server *p2p.Server // Peer-to-peer server to retrieve networking infos
eth *eth.Ethereum // Full Ethereum service if monitoring a full node backend backend
engine consensus.Engine // Consensus engine to retrieve variadic block fields engine consensus.Engine // Consensus engine to retrieve variadic block fields
node string // Name of the node to display on the monitoring page node string // Name of the node to display on the monitoring page
pass string // Password to authorize access to 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. // 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 // Parse the netstats connection url
re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)")
parts := re.FindStringSubmatch(url) parts := re.FindStringSubmatch(url)
if len(parts) != 5 { 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 ethstats := &Service{
var engine consensus.Engine backend: backend,
if ethServ != nil { engine: engine,
engine = ethServ.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, node.RegisterLifecycle(ethstats)
engine: engine, return nil
node: parts[1],
pass: parts[3],
host: parts[4],
pongCh: make(chan struct{}),
histCh: make(chan []uint64, 1),
}, nil
} }
// Protocols implements node.Service, returning the P2P network protocols used // Start implements node.Lifecycle, starting up the monitoring and reporting daemon.
// by the stats service (nil as it doesn't use the devp2p overlay network). func (s *Service) Start() error {
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
go s.loop() go s.loop()
log.Info("Stats daemon started") log.Info("Stats daemon started")
return nil 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 { func (s *Service) Stop() error {
log.Info("Stats daemon stopped") log.Info("Stats daemon stopped")
return nil return nil
@ -185,26 +191,17 @@ func (s *Service) Stop() error {
// until termination. // until termination.
func (s *Service) loop() { func (s *Service) loop() {
// Subscribe to chain events to execute updates on // 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) chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
headSub := blockchain.SubscribeChainHeadEvent(chainHeadCh) headSub := s.backend.SubscribeChainHeadEvent(chainHeadCh)
defer headSub.Unsubscribe() defer headSub.Unsubscribe()
txEventCh := make(chan core.NewTxsEvent, txChanSize) txEventCh := make(chan core.NewTxsEvent, txChanSize)
txSub := txpool.SubscribeNewTxsEvent(txEventCh) txSub := s.backend.SubscribeNewTxsEvent(txEventCh)
defer txSub.Unsubscribe() defer txSub.Unsubscribe()
// Forensics events // Forensics events
forensicsEventCh := make(chan types.ForensicsEvent) forensicsEventCh := make(chan types.ForensicsEvent)
if engine != nil { if engine, ok := s.engine.(consensusEngine); ok && engine != nil {
forensicsSub := engine.SubscribeForensicsEvent(forensicsEventCh) forensicsSub := engine.SubscribeForensicsEvent(forensicsEventCh)
defer forensicsSub.Unsubscribe() defer forensicsSub.Unsubscribe()
} }
@ -640,19 +637,30 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
txs []txStats txs []txStats
uncles []*types.Header 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 { if block == nil {
block = s.eth.BlockChain().CurrentBlock() block = fullBackend.CurrentBlock()
} }
header = block.Header() 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())) txs = make([]txStats, len(block.Transactions()))
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
txs[i].Hash = tx.Hash() txs[i].Hash = tx.Hash()
} }
uncles = block.Uncles() 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 // Assemble and return the block stats
author, err := s.engine.Author(header) author, err := s.engine.Author(header)
@ -687,10 +695,7 @@ func (s *Service) reportHistory(conn *connWrapper, list []uint64) error {
indexes = append(indexes, list...) indexes = append(indexes, list...)
} else { } else {
// No indexes requested, send back the top ones // No indexes requested, send back the top ones
var head int64 head := s.backend.CurrentHeader().Number.Int64()
if s.eth != nil {
head = s.eth.BlockChain().CurrentHeader().Number.Int64()
}
start := head - historyUpdateRange + 1 start := head - historyUpdateRange + 1
if start < 0 { if start < 0 {
start = 0 start = 0
@ -702,10 +707,15 @@ func (s *Service) reportHistory(conn *connWrapper, list []uint64) error {
// Gather the batch of blocks to report // Gather the batch of blocks to report
history := make([]*blockStats, len(indexes)) history := make([]*blockStats, len(indexes))
for i, number := range indexes { for i, number := range indexes {
fullBackend, ok := s.backend.(fullNodeBackend)
// Retrieve the next block if it's known to us // Retrieve the next block if it's known to us
var block *types.Block var block *types.Block
if s.eth != nil { if ok {
block = s.eth.BlockChain().GetBlockByNumber(number) 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 we do have the block, add to the history and continue
if block != nil { if block != nil {
@ -741,10 +751,7 @@ type pendStats struct {
// it to the stats server. // it to the stats server.
func (s *Service) reportPending(conn *connWrapper) error { func (s *Service) reportPending(conn *connWrapper) error {
// Retrieve the pending count from the local blockchain // Retrieve the pending count from the local blockchain
var pending int pending, _ := s.backend.Stats()
if s.eth != nil {
pending, _ = s.eth.TxPool().Stats()
}
// Assemble the transaction stats and send it to the server // Assemble the transaction stats and send it to the server
log.Trace("Sending pending transactions to ethstats", "count", pending) log.Trace("Sending pending transactions to ethstats", "count", pending)
@ -771,7 +778,7 @@ type nodeStats struct {
Uptime int `json:"uptime"` 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. // mining layer and reports it to the stats server.
func (s *Service) reportStats(conn *connWrapper) error { func (s *Service) reportStats(conn *connWrapper) error {
// Gather the syncing and mining infos from the local miner instance // Gather the syncing and mining infos from the local miner instance
@ -781,17 +788,22 @@ func (s *Service) reportStats(conn *connWrapper) error {
syncing bool syncing bool
gasprice int gasprice int
) )
if s.eth != nil { // check if backend is a full node
mining = s.eth.Miner().Mining() fullBackend, ok := s.backend.(fullNodeBackend)
hashrate = int(s.eth.Miner().HashRate()) if ok {
mining = fullBackend.Miner().Mining()
hashrate = int(fullBackend.Miner().HashRate())
sync := s.eth.Downloader().Progress() sync := fullBackend.Downloader().Progress()
syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
price, _ := s.eth.ApiBackend.SuggestGasTipCap(context.Background()) price, _ := s.backend.SuggestGasTipCap(context.Background())
gasprice = int(price.Uint64()) gasprice = int(price.Uint64())
if basefee := s.eth.ApiBackend.CurrentHeader().BaseFee; basefee != nil { if basefee := fullBackend.CurrentHeader().BaseFee; basefee != nil {
gasprice += int(basefee.Uint64()) 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 // Assemble the node stats and send it to the server
@ -803,7 +815,7 @@ func (s *Service) reportStats(conn *connWrapper) error {
Active: true, Active: true,
Mining: mining, Mining: mining,
Hashrate: hashrate, Hashrate: hashrate,
Peers: s.eth.GetPeer(), Peers: s.server.PeerCount(),
GasPrice: gasprice, GasPrice: gasprice,
Syncing: syncing, Syncing: syncing,
Uptime: 100, 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()) { if prevBlockNumber >= latestBlockNumber || !s.b.ChainConfig().IsTIP2019(b.Number()) {
prevBlockNumber = curBlockNumber prevBlockNumber = curBlockNumber
} }
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok { if engine, ok := s.b.Engine().(*XDPoS.XDPoS); ok {
// Get block epoc latest. // Get block epoc latest.
return engine.GetMasternodesByNumber(s.chainReader, prevBlockNumber), nil return engine.GetMasternodesByNumber(s.chainReader, prevBlockNumber), nil
} else { } else {
@ -980,7 +980,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd
var maxMasternodes int var maxMasternodes int
if header.Number.Cmp(s.b.ChainConfig().XDPoS.V2.SwitchBlock) == 1 { 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) round, err := engine.EngineV2.GetRoundNumber(header)
if err != nil { if err != nil {
return result, err return result, err
@ -1008,7 +1008,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd
} }
// Get masternode list // 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) masternodes = engine.GetMasternodesFromCheckpointHeader(header)
if len(masternodes) == 0 { if len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64()) 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 // 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) masternodes = engine.GetMasternodesFromCheckpointHeader(header)
if len(masternodes) == 0 { if len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64()) 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 var maxMasternodes int
if header.Number.Cmp(s.b.ChainConfig().XDPoS.V2.SwitchBlock) == 1 { 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) round, err := engine.EngineV2.GetRoundNumber(header)
if err != nil { if err != nil {
return result, err return result, err
@ -1251,7 +1251,7 @@ func (s *PublicBlockChainAPI) GetCheckpointFromEpoch(ctx context.Context, epochN
if epochNum == rpc.LatestEpochNumber { if epochNum == rpc.LatestEpochNumber {
blockNumer := s.b.CurrentBlock().Number() 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 err error
var currentEpoch uint64 var currentEpoch uint64
checkpointNumber, currentEpoch, err = engine.GetCurrentEpochSwitchBlock(s.chainReader, blockNumer) 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 { if block == nil {
return nil, fmt.Errorf("nil block in DoCall: number=%d, hash=%s", header.Number.Uint64(), header.Hash().Hex()) 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 { if err != nil {
return nil, err 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) { func (s *PublicBlockChainAPI) rpcOutputBlock(b *types.Block, inclTx bool, fullTx bool, ctx context.Context) (map[string]interface{}, error) {
fields := RPCMarshalHeader(b.Header()) fields := RPCMarshalHeader(b.Header())
fields["size"] = hexutil.Uint64(b.Size()) 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 { if inclTx {
formatTx := func(tx *types.Transaction) (interface{}, error) { formatTx := func(tx *types.Transaction) (interface{}, error) {
@ -1720,7 +1720,7 @@ func (s *PublicBlockChainAPI) findNearestSignedBlock(ctx context.Context, b *typ
} }
// Get block epoc latest // 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 { if err != nil {
log.Error("[findNearestSignedBlock] Error while trying to get current Epoch switch block", "Number", signedBlockNumber) 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 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) { 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) signedBlock := s.findNearestSignedBlock(ctx, b)
if signedBlock == nil { if signedBlock == nil {
@ -1839,7 +1839,7 @@ func (s *PublicBlockChainAPI) rpcOutputBlockSigners(b *types.Block, ctx context.
return []common.Address{}, err return []common.Address{}, err
} }
engine, ok := s.b.GetEngine().(*XDPoS.XDPoS) engine, ok := s.b.Engine().(*XDPoS.XDPoS)
if !ok { if !ok {
log.Error("Undefined XDPoS consensus engine") log.Error("Undefined XDPoS consensus engine")
return []common.Address{}, nil return []common.Address{}, nil
@ -2018,7 +2018,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
if block == nil { if block == nil {
return nil, 0, nil, fmt.Errorf("nil block in AccessList: number=%d, hash=%s", header.Number.Uint64(), header.Hash().Hex()) 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 { if err != nil {
return nil, 0, nil, err return nil, 0, nil, err
} }
@ -2619,7 +2619,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBestBid(ctx context.Context, baseToken
if XDCxService == nil { if XDCxService == nil {
return result, errors.New("not find XDCX service") 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 { if err != nil {
return result, err return result, err
} }
@ -2644,7 +2644,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBestAsk(ctx context.Context, baseToken
if XDCxService == nil { if XDCxService == nil {
return result, errors.New("not found XDCX service") 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 { if err != nil {
return result, err return result, err
} }
@ -2668,7 +2668,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBidTree(ctx context.Context, baseToken
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2692,7 +2692,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetPrice(ctx context.Context, baseToken,
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2716,7 +2716,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLastEpochPrice(ctx context.Context, ba
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2740,7 +2740,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetCurrentEpochPrice(ctx context.Context,
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2764,7 +2764,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetAskTree(ctx context.Context, baseToken
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2788,7 +2788,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetOrderById(ctx context.Context, baseTok
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2813,7 +2813,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetTradingOrderBookInfo(ctx context.Conte
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2837,7 +2837,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLiquidationPriceTree(ctx context.Conte
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2861,7 +2861,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetInvestingTree(ctx context.Context, len
if lendingService == nil { if lendingService == nil {
return nil, errors.New("XDCX Lending service not found") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2885,7 +2885,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBorrowingTree(ctx context.Context, len
if lendingService == nil { if lendingService == nil {
return nil, errors.New("XDCX Lending service not found") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2909,7 +2909,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderBookInfo(tx context.Contex
if lendingService == nil { if lendingService == nil {
return nil, errors.New("XDCX Lending service not found") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2933,7 +2933,7 @@ func (s *PublicXDCXTransactionPoolAPI) getLendingOrderTree(ctx context.Context,
if lendingService == nil { if lendingService == nil {
return nil, errors.New("not find XDCX Lending service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2957,7 +2957,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingTradeTree(ctx context.Context,
if lendingService == nil { if lendingService == nil {
return nil, errors.New("not find XDCX lending service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -2981,7 +2981,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLiquidationTimeTree(ctx context.Contex
if lendingService == nil { if lendingService == nil {
return nil, errors.New("not find XDCX Lending service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -3005,7 +3005,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderCount(ctx context.Context,
if lendingService == nil { if lendingService == nil {
return nil, errors.New("not find XDCX Lending service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -3027,7 +3027,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBestInvesting(ctx context.Context, len
if lendingService == nil { if lendingService == nil {
return result, errors.New("not find XDCX Lending service") 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 { if err != nil {
return result, err return result, err
} }
@ -3049,7 +3049,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBestBorrowing(ctx context.Context, len
if lendingService == nil { if lendingService == nil {
return result, errors.New("not find XDCX Lending service") 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 { if err != nil {
return result, err return result, err
} }
@ -3070,7 +3070,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBids(ctx context.Context, baseToken, q
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -3094,7 +3094,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetAsks(ctx context.Context, baseToken, q
if XDCxService == nil { if XDCxService == nil {
return nil, errors.New("not find XDCX service") 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 { if err != nil {
return nil, err return nil, err
} }
@ -3118,7 +3118,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetInvests(ctx context.Context, lendingTo
if lendingService == nil { if lendingService == nil {
return nil, errors.New("XDCX Lending service not found") 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 { if err != nil {
return nil, err return nil, err
} }
@ -3142,7 +3142,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetBorrows(ctx context.Context, lendingTo
if lendingService == nil { if lendingService == nil {
return nil, errors.New("XDCX Lending service not found") 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 { if err != nil {
return nil, err return nil, err
} }
@ -3200,7 +3200,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingOrderById(ctx context.Context,
if lendingService == nil { if lendingService == nil {
return lendingItem, errors.New("not find XDCX lending service") 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 { if err != nil {
return lendingItem, err return lendingItem, err
} }
@ -3227,7 +3227,7 @@ func (s *PublicXDCXTransactionPoolAPI) GetLendingTradeById(ctx context.Context,
if lendingService == nil { if lendingService == nil {
return lendingItem, errors.New("not find XDCX Lending service") 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 { if err != nil {
return lendingItem, err return lendingItem, err
} }
@ -3535,7 +3535,7 @@ func GetSignersFromBlocks(b Backend, blockNumber uint64, blockHash common.Hash,
mapMN[node] = true mapMN[node] = true
} }
signer := types.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(blockNumber)) 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 limitNumber := blockNumber + common.LimitTimeFinality
currentNumber := b.CurrentBlock().NumberU64() currentNumber := b.CurrentBlock().NumberU64()
if limitNumber > currentNumber { if limitNumber > currentNumber {

View file

@ -70,7 +70,7 @@ type Backend interface {
GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error)
PendingBlockAndReceipts() (*types.Block, types.Receipts) PendingBlockAndReceipts() (*types.Block, types.Receipts)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) 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) 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 SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
@ -93,9 +93,9 @@ type Backend interface {
SendLendingTx(ctx context.Context, signedTx *types.LendingTransaction) error SendLendingTx(ctx context.Context, signedTx *types.LendingTransaction) error
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
Engine() consensus.Engine
CurrentBlock() *types.Block CurrentBlock() *types.Block
GetIPCClient() (bind.ContractBackend, error) GetIPCClient() (bind.ContractBackend, error)
GetEngine() consensus.Engine
GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int
GetVotersRewards(common.Address) map[common.Address]*big.Int GetVotersRewards(common.Address) map[common.Address]*big.Int
@ -116,6 +116,7 @@ type Backend interface {
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
BloomStatus() (uint64, uint64) BloomStatus() (uint64, uint64)
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
GetPeer() int
} }
func GetAPIs(apiBackend Backend, chainReader consensus.ChainReader) []rpc.API { 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) RPCTxFeeCap() float64 { return 0 }
func (b *backendMock) UnprotectedAllowed() bool { return false } func (b *backendMock) UnprotectedAllowed() bool { return false }
func (b *backendMock) SetHead(number uint64) {} 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) { func (b *backendMock) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
return nil, nil return nil, nil
@ -345,7 +346,7 @@ func (b *backendMock) GetReceipts(ctx context.Context, hash common.Hash) (types.
return nil, nil 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 return nil
} }
@ -412,10 +413,6 @@ func (b *backendMock) GetBlocksHashCache(blockNr uint64) []common.Hash {
return []common.Hash{} return []common.Hash{}
} }
func (b *backendMock) GetEngine() consensus.Engine {
return nil
}
func (b *backendMock) GetEpochDuration() *big.Int { func (b *backendMock) GetEpochDuration() *big.Int {
return nil return nil
} }

View file

@ -18,32 +18,52 @@ package node
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/internal/debug"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
) )
// PrivateAdminAPI is the collection of administrative API methods exposed only // apis returns the collection of built-in RPC APIs.
// over a secure RPC channel. func (n *Node) apis() []rpc.API {
type PrivateAdminAPI struct { return []rpc.API{
node *Node // Node interfaced by this 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 // privateAdminAPI is the collection of administrative API methods exposed only
// of the node itself. // over a secure RPC channel.
func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI { type privateAdminAPI struct {
return &PrivateAdminAPI{node: node} node *Node // Node interfaced by this API
} }
// AddPeer requests connecting to a remote node, and also maintaining the new // AddPeer requests connecting to a remote node, and also maintaining the new
// connection at all times, even reconnecting if it is lost. // 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 // Make sure the server is running, fail otherwise
server := api.node.Server() server := api.node.Server()
if server == nil { 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 // 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 // Make sure the server is running, fail otherwise
server := api.node.Server() server := api.node.Server()
if server == nil { 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 // 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 // Make sure the server is running, fail otherwise
server := api.node.Server() server := api.node.Server()
if server == nil { 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 // RemoveTrustedPeer removes a remote node from the trusted peer set, but it
// does not disconnect it automatically. // 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 // Make sure the server is running, fail otherwise
server := api.node.Server() server := api.node.Server()
if server == nil { 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 // PeerEvents creates an RPC subscription which receives peer events from the
// node's p2p.Server // 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 // Make sure the server is running, fail otherwise
server := api.node.Server() server := api.node.Server()
if server == nil { if server == nil {
@ -144,14 +164,11 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription,
} }
// StartRPC starts the HTTP RPC API server. // 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() api.node.lock.Lock()
defer api.node.lock.Unlock() defer api.node.lock.Unlock()
if api.node.httpHandler != nil { // Determine host and port.
return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint)
}
if host == nil { if host == nil {
h := DefaultHTTPHost h := DefaultHTTPHost
if api.node.config.HTTPHost != "" { 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 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 { if cors != nil {
allowedOrigins = nil config.CorsAllowedOrigins = nil
for _, origin := range strings.Split(*cors, ",") { 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 { if vhosts != nil {
allowedVHosts = nil config.Vhosts = nil
for _, vhost := range strings.Split(*host, ",") { 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 { if apis != nil {
modules = nil config.Modules = nil
for _, m := range strings.Split(*apis, ",") { 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 false, err
} }
return true, nil return true, nil
} }
// StopRPC terminates an already running HTTP RPC API endpoint. // StopRPC shuts down the HTTP server.
func (api *PrivateAdminAPI) StopRPC() (bool, error) { func (api *privateAdminAPI) StopRPC() (bool, error) {
api.node.lock.Lock() api.node.http.stop()
defer api.node.lock.Unlock()
if api.node.httpHandler == nil {
return false, errors.New("HTTP RPC not running")
}
api.node.stopHTTP()
return true, nil return true, nil
} }
// StartWS starts the websocket RPC API server. // 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() api.node.lock.Lock()
defer api.node.lock.Unlock() defer api.node.lock.Unlock()
if api.node.wsHandler != nil { // Determine host and port.
return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint)
}
if host == nil { if host == nil {
h := DefaultWSHost h := DefaultWSHost
if api.node.config.WSHost != "" { if api.node.config.WSHost != "" {
@ -225,55 +240,56 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str
port = &api.node.config.WSPort port = &api.node.config.WSPort
} }
origins := api.node.config.WSOrigins // Determine config.
if allowedOrigins != nil { config := wsConfig{
origins = nil Modules: api.node.config.WSModules,
for _, origin := range strings.Split(*allowedOrigins, ",") { Origins: api.node.config.WSOrigins,
origins = append(origins, strings.TrimSpace(origin))
}
} }
modules := api.node.config.WSModules
if apis != nil { if apis != nil {
modules = nil config.Modules = nil
for _, m := range strings.Split(*apis, ",") { 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 false, err
} }
return true, nil if err := server.enableWS(api.node.rpcAPIs, config); err != nil {
} return false, err
// 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")
} }
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 return true, nil
} }
// PublicAdminAPI is the collection of administrative API methods exposed over // StopWS terminates all WebSocket servers.
// both secure and unsecure RPC channels. func (api *privateAdminAPI) StopWS() (bool, error) {
type PublicAdminAPI struct { api.node.http.stopWS()
node *Node // Node interfaced by this API api.node.ws.stop()
return true, nil
} }
// NewPublicAdminAPI creates a new API definition for the public admin methods // publicAdminAPI is the collection of administrative API methods exposed over
// of the node itself. // both secure and unsecure RPC channels.
func NewPublicAdminAPI(node *Node) *PublicAdminAPI { type publicAdminAPI struct {
return &PublicAdminAPI{node: node} node *Node // Node interfaced by this API
} }
// Peers retrieves all the information we know about each individual peer at the // Peers retrieves all the information we know about each individual peer at the
// protocol granularity. // protocol granularity.
func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { func (api *publicAdminAPI) Peers() ([]*p2p.PeerInfo, error) {
server := api.node.Server() server := api.node.Server()
if server == nil { if server == nil {
return nil, ErrNodeStopped 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 // NodeInfo retrieves all the information we know about the host node at the
// protocol granularity. // protocol granularity.
func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { func (api *publicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
server := api.node.Server() server := api.node.Server()
if server == nil { if server == nil {
return nil, ErrNodeStopped return nil, ErrNodeStopped
@ -292,27 +308,22 @@ func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
} }
// Datadir retrieves the current data directory the node is using. // Datadir retrieves the current data directory the node is using.
func (api *PublicAdminAPI) Datadir() string { func (api *publicAdminAPI) Datadir() string {
return api.node.DataDir() return api.node.DataDir()
} }
// PublicWeb3API offers helper utils // publicWeb3API offers helper utils
type PublicWeb3API struct { type publicWeb3API struct {
stack *Node stack *Node
} }
// NewPublicWeb3API creates a new Web3Service instance
func NewPublicWeb3API(stack *Node) *PublicWeb3API {
return &PublicWeb3API{stack}
}
// ClientVersion returns the node name // ClientVersion returns the node name
func (s *PublicWeb3API) ClientVersion() string { func (s *publicWeb3API) ClientVersion() string {
return s.stack.Server().Name return s.stack.Server().Name
} }
// Sha3 applies the ethereum sha3 implementation on the input. // Sha3 applies the ethereum sha3 implementation on the input.
// It assumes the input is hex encoded. // 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) 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 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. 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 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 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 Node also creates the shared store of encrypted Ethereum account keys. Services can access
the account manager through the service context. 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 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 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 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. 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 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 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: "db" and "db-2". The following files will be created in the data directory:
data-directory/ data-directory/
A/ A/
nodekey -- devp2p node key of instance A nodekey -- devp2p node key of instance A
nodes/ -- devp2p discovery knowledge database of instance A nodes/ -- devp2p discovery knowledge database of instance A
db/ -- LevelDB content for "db" db/ -- LevelDB content for "db"
A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A
B/ B/
nodekey -- devp2p node key of node B nodekey -- devp2p node key of node B
nodes/ -- devp2p discovery knowledge database of instance B nodes/ -- devp2p discovery knowledge database of instance B
static-nodes.json -- devp2p static node list of instance B static-nodes.json -- devp2p static node list of instance B
db/ -- LevelDB content for "db" db/ -- LevelDB content for "db"
db-2/ -- LevelDB content for "db-2" db-2/ -- LevelDB content for "db-2"
B.ipc -- JSON-RPC UNIX domain socket endpoint of instance B B.ipc -- JSON-RPC UNIX domain socket endpoint of instance B
keystore/ -- account key store, used by both instances keystore/ -- account key store, used by both instances
*/ */
package node package node

View file

@ -17,51 +17,12 @@
package node package node
import ( import (
"net"
"net/http"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rpc" "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 // checkModuleAvailability checks that all names given in modules are actually
// available API services. It assumes that the MetadataApi module ("rpc") is always available; // available API services. It assumes that the MetadataApi module ("rpc") is always available;
// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints. // the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints.
@ -87,7 +48,7 @@ func CheckTimeouts(timeouts *rpc.HTTPTimeouts) {
log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadTimeout) log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadTimeout)
timeouts.ReadTimeout = 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) log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", rpc.DefaultHTTPTimeouts.WriteTimeout)
timeouts.WriteTimeout = rpc.DefaultHTTPTimeouts.WriteTimeout timeouts.WriteTimeout = rpc.DefaultHTTPTimeouts.WriteTimeout
} }

View file

@ -39,17 +39,6 @@ func convertFileLockError(err error) error {
return err return err
} }
// DuplicateServiceError is returned during Node startup if a registered service
// constructor returns a service of the same type that was already started.
type DuplicateServiceError struct {
Kind reflect.Type
}
// Error generates a textual representation of the duplicate service error.
func (e *DuplicateServiceError) Error() string {
return fmt.Sprintf("duplicate service: %v", e.Kind)
}
// StopError is returned if a Node fails to stop either any of its registered // StopError is returned if a Node fails to stop either any of its registered
// services or itself. // services or itself.
type StopError struct { type StopError struct {

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 package node
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -32,7 +30,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/internal/debug"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
@ -48,40 +45,29 @@ type Node struct {
keyDir string // key store directory keyDir string // key store directory
keyDirTemp bool // If true, key directory will be removed by Stop 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 ephemKeystore string // if non-empty, the key directory that will be removed by Stop
instanceDirLock flock.Releaser // prevents concurrent use of instance directory 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
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 state int // Tracks state of node lifecycle
serviceFuncs []ServiceConstructor // Service constructors (in dependency order)
services map[reflect.Type]Service // Currently running services
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 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 inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled) databases map[*closeTrackingDB]struct{} // All open databases
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
} }
const ( const (
initializingState = iota initializingState = iota
runningState
closedState
) )
// New creates a new P2P node, ready for protocol registration. // New creates a new P2P node, ready for protocol registration.
@ -114,13 +100,21 @@ func New(conf *Config) (*Node, error) {
} }
node := &Node{ node := &Node{
config: conf, config: conf,
eventmux: new(event.TypeMux), inprocHandler: rpc.NewServer(),
log: conf.Logger, eventmux: new(event.TypeMux),
serviceFuncs: []ServiceConstructor{}, log: conf.Logger,
ipcEndpoint: conf.IPCEndpoint(), stop: make(chan struct{}),
httpEndpoint: conf.HTTPEndpoint(), server: &p2p.Server{Config: conf.P2P},
wsEndpoint: conf.WSEndpoint(), 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) keyDir, isEphem, err := getKeyStoreDir(conf)
@ -133,18 +127,108 @@ func New(conf *Config) (*Node, error) {
// are required to add the backends later on. // are required to add the backends later on.
node.accman = accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed}) 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 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 // Close stops the Node and releases resources acquired in
// Node constructor New. // Node constructor New.
func (n *Node) Close() error { func (n *Node) Close() error {
var errs []error n.startStopLock.Lock()
defer n.startStopLock.Unlock()
// Terminate all subsystems and collect any errors n.lock.Lock()
if err := n.Stop(); err != nil && err != ErrNodeStopped { state := n.state
errs = append(errs, err) 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 { if err := n.accman.Close(); err != nil {
errs = append(errs, err) 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) { switch len(errs) {
case 0: case 0:
return nil 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 // startNetworking starts all network endpoints.
// the passed constructor must be unique in its type with regard to sibling ones. func (n *Node) startNetworking() error {
func (n *Node) Register(constructor ServiceConstructor) error { n.log.Info("Starting peer-to-peer node", "instance", n.server.Name)
n.lock.Lock() if err := n.server.Start(); err != nil {
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 {
return convertFileLockError(err) return convertFileLockError(err)
} }
// Start each of the services err := n.startRPC()
var started []reflect.Type if err != nil {
for kind, service := range services { n.stopRPC()
// Start the next service, stopping all previous upon failure n.server.Stop()
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)
} }
// Lastly, start the configured RPC interfaces return err
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{})
// 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 return nil
} }
@ -286,67 +322,128 @@ func (n *Node) openDataDir() error {
if err != nil { if err != nil {
return convertFileLockError(err) return convertFileLockError(err)
} }
n.instanceDirLock = release n.dirLock = release
return nil 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 // startup. It's not meant to be called at any time afterwards as it makes certain
// assumptions about the state of the node. // assumptions about the state of the node.
func (n *Node) startRPC(services map[reflect.Type]Service) error { func (n *Node) startRPC() error {
// Gather all the possible APIs to surface if err := n.startInProc(); err != nil {
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 {
return err return err
} }
if err := n.startIPC(apis); err != nil {
n.stopInProc() // Configure IPC.
return err if n.ipc.endpoint != "" {
} if err := n.ipc.start(n.rpcAPIs); err != nil {
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()
return err return err
} }
} }
// All API endpoints started successfully // Configure HTTP.
n.rpcAPIs = apis if n.config.HTTPHost != "" {
return nil 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) wsServerForPort(port int) *httpServer {
func (n *Node) startInProc(apis []rpc.API) error { if n.config.HTTPHost == "" || n.http.port == port {
// Register all the APIs exposed by the services return n.http
handler := rpc.NewServer() }
for _, api := range apis { return n.ws
if err := handler.RegisterName(api.Namespace, api.Service); err != nil { }
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 return err
} }
n.log.Debug("InProc registered", "namespace", api.Namespace)
} }
n.inprocHandler = handler
return nil return nil
} }
// stopInProc terminates the in-process RPC endpoint. // stopInProc terminates the in-process RPC endpoint.
func (n *Node) stopInProc() { func (n *Node) stopInProc() {
if n.inprocHandler != nil { n.inprocHandler.Stop()
n.inprocHandler.Stop() }
n.inprocHandler = nil
// 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. // 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...) n.rpcAPIs = append(n.rpcAPIs, apis...)
} }
// startIPC initializes and starts the IPC RPC endpoint. // RegisterHandler mounts a handler on the given path on the canonical HTTP server.
func (n *Node) startIPC(apis []rpc.API) error { //
if n.ipcEndpoint == "" { // The name of the handler is shown in a log message when the HTTP server starts
return nil // IPC disabled. // and should be a descriptive term for the service provided by the handler.
} func (n *Node) RegisterHandler(name, path string, handler http.Handler) {
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 {
n.lock.Lock() n.lock.Lock()
defer n.lock.Unlock() defer n.lock.Unlock()
// Short circuit if the node's not running if n.state != initializingState {
if n.server == nil { panic("can't register HTTP handler on running/stopped node")
return ErrNodeStopped
} }
n.http.mux.Handle(path, handler)
// Terminate the API, services and the p2p server. n.http.handlerNames[path] = name
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
} }
// Attach creates an RPC client attached to an in-process API handler. // Attach creates an RPC client attached to an in-process API handler.
func (n *Node) Attach() (*rpc.Client, error) { 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 return rpc.DialInProc(n.inprocHandler), nil
} }
// RPCHandler returns the in-process RPC request handler. // RPCHandler returns the in-process RPC request handler.
func (n *Node) RPCHandler() (*rpc.Server, error) { func (n *Node) RPCHandler() (*rpc.Server, error) {
n.lock.RLock() n.lock.Lock()
defer n.lock.RUnlock() defer n.lock.Unlock()
if n.inprocHandler == nil { if n.state == closedState {
return nil, ErrNodeStopped return nil, ErrNodeStopped
} }
return n.inprocHandler, nil 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 // Server retrieves the currently running P2P network layer. This method is meant
// only to inspect fields of the currently running server, life cycle management // only to inspect fields of the currently running server. Callers should not
// should be left to this Node entity. // start or stop the returned server.
func (n *Node) Server() *p2p.Server { func (n *Node) Server() *p2p.Server {
n.lock.RLock() n.lock.Lock()
defer n.lock.RUnlock() defer n.lock.Unlock()
return n.server 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. // DataDir retrieves the current datadir used by the protocol stack.
// Deprecated: No files should be stored in this directory, use InstanceDir instead. // Deprecated: No files should be stored in this directory, use InstanceDir instead.
func (n *Node) DataDir() string { 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. // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack.
func (n *Node) IPCEndpoint() string { func (n *Node) IPCEndpoint() string {
return n.ipcEndpoint return n.ipc.endpoint
} }
// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. // HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack.
func (n *Node) HTTPEndpoint() string { func (n *Node) HTTPEndpoint() string {
n.lock.Lock() return "http://" + n.http.listenAddr()
defer n.lock.Unlock()
if n.httpListenerAddr != nil {
return n.httpListenerAddr.String()
}
return n.httpEndpoint
} }
// WSEndpoint retrieves the current WS endpoint used by the protocol stack. // WSEndpoint retrieves the current WS endpoint used by the protocol stack.
func (n *Node) WSEndpoint() string { func (n *Node) WSEndpoint() string {
n.lock.Lock() if n.http.wsAllowed() {
defer n.lock.Unlock() return "ws://" + n.http.listenAddr()
if n.wsListenerAddr != nil {
return n.wsListenerAddr.String()
} }
return n.wsEndpoint return "ws://" + n.ws.listenAddr()
} }
// EventMux retrieves the event multiplexer used by all the network services in // 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 // previous can be found) from within the node's instance directory. If the node is
// ephemeral, a memory database is returned. // ephemeral, a memory database is returned.
func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) { func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) {
if n.config.DataDir == "" { n.lock.Lock()
return rawdb.NewMemoryDatabase(), nil 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. // 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) return n.config.ResolvePath(x)
} }
// apis returns the collection of RPC descriptors this node offers. // closeTrackingDB wraps the Close method of a database. When the database is closed by the
func (n *Node) apis() []rpc.API { // service, the wrapper removes it from the node's database map. This ensures that Node
return []rpc.API{ // won't auto-close the database if it is closed by the service that opened it.
{ type closeTrackingDB struct {
Namespace: "admin", ethdb.Database
Version: "1.0", n *Node
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,
},
}
} }
// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, func (db *closeTrackingDB) Close() error {
// and then registers all of the APIs exposed by the services. db.n.lock.Lock()
func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { delete(db.n.databases, db)
if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { db.n.lock.Unlock()
log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) return db.Database.Close()
} }
// Generate the whitelist based on the allowed modules
whitelist := make(map[string]bool) // wrapDatabase ensures the database will be auto-closed when Node is closed.
for _, module := range modules { func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database {
whitelist[module] = true wrapper := &closeTrackingDB{db, n}
} n.databases[wrapper] = struct{}{}
// Register all the APIs exposed by the services return wrapper
for _, api := range apis { }
if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
if err := srv.RegisterName(api.Namespace, api.Service); err != nil { // closeDatabases closes all open databases.
return err 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" "log"
"github.com/XinFinOrg/XDPoSChain/node" "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. // life cycle management.
// //
// The following methods are needed to implement a node.Service: // The following methods are needed to implement a node.Lifecycle:
// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on
// - APIs() []rpc.API - api methods the service wants to expose on rpc channels
// - Start() error - method invoked when the node is ready to start the service // - Start() error - method invoked when the node is ready to start the service
// - Stop() error - method invoked when the node terminates the service // - Stop() error - method invoked when the node terminates the service
type SampleService struct{} type SampleLifecycle struct{}
func (s *SampleService) Protocols() []p2p.Protocol { return nil } func (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil }
func (s *SampleService) APIs() []rpc.API { return nil } func (s *SampleLifecycle) Stop() error { fmt.Println("Service stopping..."); return nil }
func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil }
func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil }
func ExampleService() { func ExampleLifecycle() {
// Create a network node to run protocols with the default values. // Create a network node to run protocols with the default values.
stack, err := node.New(&node.Config{}) stack, err := node.New(&node.Config{})
if err != nil { if err != nil {
@ -48,29 +42,19 @@ func ExampleService() {
} }
defer stack.Close() defer stack.Close()
// Create and register a simple network service. This is done through the definition // Create and register a simple network Lifecycle.
// of a node.ServiceConstructor that will instantiate a node.Service. The reason for service := new(SampleLifecycle)
// the factory method approach is to support service restarts without relying on the
// individual implementations' support for such operations. stack.RegisterLifecycle(service)
constructor := func(context *node.ServiceContext) (node.Service, error) {
return new(SampleService), nil
}
if err := stack.Register(constructor); err != nil {
log.Fatalf("Failed to register service: %v", err)
}
// Boot up the entire protocol stack, do a restart and terminate // Boot up the entire protocol stack, do a restart and terminate
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
log.Fatalf("Failed to start the protocol stack: %v", err) log.Fatalf("Failed to start the protocol stack: %v", err)
} }
if err := stack.Restart(); err != nil { if err := stack.Close(); err != nil {
log.Fatalf("Failed to restart the protocol stack: %v", err)
}
if err := stack.Stop(); err != nil {
log.Fatalf("Failed to stop the protocol stack: %v", err) log.Fatalf("Failed to stop the protocol stack: %v", err)
} }
// Output: // Output:
// Service starting... // Service starting...
// Service stopping... // Service stopping...
// Service starting...
// Service stopping...
} }

View file

@ -18,12 +18,16 @@ package node
import ( import (
"errors" "errors"
"fmt"
"io"
"net"
"net/http" "net/http"
"reflect" "reflect"
"strings"
"testing" "testing"
"time"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -40,20 +44,27 @@ func testNodeConfig() *Config {
} }
} }
// Tests that an empty protocol stack can be started, restarted and stopped. // Tests that an empty protocol stack can be closed more than once.
func TestNodeLifeCycle(t *testing.T) { func TestNodeCloseMultipleTimes(t *testing.T) {
stack, err := New(testNodeConfig()) stack, err := New(testNodeConfig())
if err != nil { if err != nil {
t.Fatalf("failed to create protocol stack: %v", err) t.Fatalf("failed to create protocol stack: %v", err)
} }
defer stack.Close() stack.Close()
// Ensure that a stopped node can be stopped again // Ensure that a stopped node can be stopped again
for i := 0; i < 3; i++ { 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) 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 // Ensure that a node can be successfully started, but only once
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
t.Fatalf("failed to start node: %v", err) t.Fatalf("failed to start node: %v", err)
@ -61,17 +72,12 @@ func TestNodeLifeCycle(t *testing.T) {
if err := stack.Start(); err != ErrNodeRunning { if err := stack.Start(); err != ErrNodeRunning {
t.Fatalf("start failure mismatch: have %v, want %v", 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 // 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) 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) 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 { if err := original.Start(); err != nil {
t.Fatalf("failed to start original protocol stack: %v", err) 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 // Create a second node based on the same data directory and ensure failure
duplicate, err := New(&Config{DataDir: dir}) _, err = New(&Config{DataDir: dir})
if err != nil { if err != ErrDatadirUsed {
t.Fatalf("failed to create duplicate protocol stack: %v", err)
}
defer duplicate.Close()
if err := duplicate.Start(); err != ErrDatadirUsed {
t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed) t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed)
} }
} }
// Tests whether services can be registered and duplicates caught. // Tests whether a Lifecycle can be registered.
func TestServiceRegistry(t *testing.T) { func TestLifecycleRegistry_Successful(t *testing.T) {
stack, err := New(testNodeConfig()) stack, err := New(testNodeConfig())
if err != nil { if err != nil {
t.Fatalf("failed to create protocol stack: %v", err) t.Fatalf("failed to create protocol stack: %v", err)
} }
defer stack.Close() defer stack.Close()
// Register a batch of unique services and ensure they start successfully noop := NewNoop()
services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC} stack.RegisterLifecycle(noop)
for i, constructor := range services {
if err := stack.Register(constructor); err != nil { if !containsLifecycle(stack.lifecycles, noop) {
t.Fatalf("service #%d: registration failed: %v", i, err) 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) for _, api := range fs.APIs() {
} if !containsAPI(stack.rpcAPIs, api) {
if err := stack.Stop(); err != nil { t.Fatalf("api %v was not successfully registered", api)
t.Fatalf("failed to stop original service stack: %v", err)
}
// Duplicate one of the services and retry starting the node
if err := stack.Register(NewNoopServiceB); err != nil {
t.Fatalf("duplicate registration failed: %v", err)
}
if err := stack.Start(); err == nil {
t.Fatalf("duplicate service started")
} else {
if _, ok := err.(*DuplicateServiceError); !ok {
t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{})
} }
} }
} }
// Tests that registered services get started and stopped correctly. // This test checks that open databases are closed with node.
func TestServiceLifeCycle(t *testing.T) { func TestNodeCloseClosesDB(t *testing.T) {
stack, err := New(testNodeConfig()) stack, _ := New(testNodeConfig())
if err != nil {
t.Fatalf("failed to create protocol stack: %v", err)
}
defer stack.Close() defer stack.Close()
// Register a batch of life-cycle instrumented services db, err := stack.OpenDatabase("mydb", 0, 0, "", false)
services := map[string]InstrumentingWrapper{ if err != nil {
"A": InstrumentedServiceMakerA, t.Fatal("can't open DB:", err)
"B": InstrumentedServiceMakerB,
"C": InstrumentedServiceMakerC,
} }
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) started := make(map[string]bool)
stopped := make(map[string]bool) stopped := make(map[string]bool)
for id, maker := range services { // Create a batch of instrumented services
id := id // Closure for the constructor lifecycles := map[string]Lifecycle{
constructor := func(*ServiceContext) (Service, error) { "A": &InstrumentedService{
return &InstrumentedService{ startHook: func() { started["A"] = true },
startHook: func(*p2p.Server) { started[id] = true }, stopHook: func() { stopped["A"] = true },
stopHook: func() { stopped[id] = true }, },
}, nil "B": &InstrumentedService{
} startHook: func() { started["B"] = true },
if err := stack.Register(maker(constructor)); err != nil { stopHook: func() { stopped["B"] = true },
t.Fatalf("service %s: registration failed: %v", id, err) },
} "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 // Start the node and check that all services are running
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
t.Fatalf("failed to start protocol stack: %v", err) t.Fatalf("failed to start protocol stack: %v", err)
} }
for id := range services { for id := range lifecycles {
if !started[id] { if !started[id] {
t.Fatalf("service %s: freshly started service not running", 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 // 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) t.Fatalf("failed to stop protocol stack: %v", err)
} }
for id := range services { for id := range lifecycles {
if !stopped[id] { if !stopped[id] {
t.Fatalf("service %s: freshly terminated service still running", id) t.Fatalf("service %s: freshly terminated service still running", id)
} }
} }
} }
// Tests that services are restarted cleanly as new instances. // Tests that if a Lifecycle fails to start, all others started before it will be
func TestServiceRestarts(t *testing.T) { // shut down.
func TestLifecycleStartupError(t *testing.T) {
stack, err := New(testNodeConfig()) stack, err := New(testNodeConfig())
if err != nil { if err != nil {
t.Fatalf("failed to create protocol stack: %v", err) t.Fatalf("failed to create protocol stack: %v", err)
} }
defer stack.Close() 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) started := make(map[string]bool)
for id, maker := range services { stopped := make(map[string]bool)
id := id // Closure for the constructor
constructor := func(*ServiceContext) (Service, error) { // Create a batch of instrumented services
return &InstrumentedService{ lifecycles := map[string]Lifecycle{
startHook: func(*p2p.Server) { started[id] = true }, "A": &InstrumentedService{
}, nil startHook: func() { started["A"] = true },
} stopHook: func() { stopped["A"] = true },
if err := stack.Register(maker(constructor)); err != nil { },
t.Fatalf("service %s: registration failed: %v", id, err) "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 // Register a service that fails to construct itself
failure := errors.New("fail") failure := errors.New("fail")
failer := func(*ServiceContext) (Service, error) { failer := &InstrumentedService{start: failure}
return nil, failure stack.RegisterLifecycle(failer)
}
if err := stack.Register(failer); err != nil {
t.Fatalf("failer registration failed: %v", err)
}
// Start the protocol stack and ensure none of the services get started
for i := 0; i < 100; i++ {
if err := stack.Start(); err != failure {
t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure)
}
for id := range services {
if started[id] {
t.Fatalf("service %s: started should not have", id)
}
delete(started, id)
}
}
}
// Tests that if a service fails to start, all others started before it will be
// shut down.
func TestServiceStartupAbortion(t *testing.T) {
stack, err := New(testNodeConfig())
if err != nil {
t.Fatalf("failed to create protocol stack: %v", err)
}
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 // Start the protocol stack and ensure all started services stop
for i := 0; i < 100; i++ { if err := stack.Start(); err != failure {
if err := stack.Start(); err != failure { t.Fatalf("stack startup failure mismatch: have %v, want %v", err, failure)
t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) }
} for id := range lifecycles {
for id := range services { if started[id] && !stopped[id] {
if started[id] && !stopped[id] { t.Fatalf("service %s: started but not stopped", id)
t.Fatalf("service %s: started but not stopped", id)
}
delete(started, id)
delete(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. // not influence the rest of the shutdown invocations.
func TestServiceTerminationGuarantee(t *testing.T) { func TestLifecycleTerminationGuarantee(t *testing.T) {
stack, err := New(testNodeConfig()) stack, err := New(testNodeConfig())
if err != nil { if err != nil {
t.Fatalf("failed to create protocol stack: %v", err) t.Fatalf("failed to create protocol stack: %v", err)
} }
defer stack.Close() defer stack.Close()
// Register a batch of good services
services := map[string]InstrumentingWrapper{
"A": InstrumentedServiceMakerA,
"B": InstrumentedServiceMakerB,
"C": InstrumentedServiceMakerC,
}
started := make(map[string]bool) started := make(map[string]bool)
stopped := make(map[string]bool) stopped := make(map[string]bool)
for id, maker := range services { // Create a batch of instrumented services
id := id // Closure for the constructor lifecycles := map[string]Lifecycle{
constructor := func(*ServiceContext) (Service, error) { "A": &InstrumentedService{
return &InstrumentedService{ startHook: func() { started["A"] = true },
startHook: func(*p2p.Server) { started[id] = true }, stopHook: func() { stopped["A"] = true },
stopHook: func() { stopped[id] = true }, },
}, nil "B": &InstrumentedService{
} startHook: func() { started["B"] = true },
if err := stack.Register(maker(constructor)); err != nil { stopHook: func() { stopped["B"] = true },
t.Fatalf("service %s: registration failed: %v", id, err) },
} "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 // Register a service that fails to shot down cleanly
failure := errors.New("fail") failure := errors.New("fail")
failer := func(*ServiceContext) (Service, error) { failer := &InstrumentedService{stop: failure}
return &InstrumentedService{ stack.RegisterLifecycle(failer)
stop: failure,
}, nil
}
if err := stack.Register(failer); err != nil {
t.Fatalf("failer registration failed: %v", err)
}
// Start the protocol stack, and ensure that a failing shut down terminates all // 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
// 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
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
t.Fatalf("failed to start protocol stack: %v", err) t.Fatalf("failed to start protocol stack: %v", err)
} }
defer stack.Stop() for id := range lifecycles {
if !started[id] {
protocols := stack.Server().Protocols t.Fatalf("service %s: service not running", id)
if len(protocols) != 26 { }
t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) if stopped[id] {
} t.Fatalf("service %s: service already stopped", id)
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)
}
} }
} }
// 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. // Tests whether a handler can be successfully mounted on the canonical HTTP server
func TestAPIGather(t *testing.T) { // on the given path
stack, err := New(testNodeConfig()) func TestRegisterHandler_Successful(t *testing.T) {
if err != nil { node := createNode(t, 7878, 7979)
t.Fatalf("failed to create protocol stack: %v", err)
}
defer stack.Close()
// Register a batch of services with some configured APIs // create and mount handler
calls := make(chan string, 1) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
makeAPI := func(result string) *OneMethodAPI { w.Write([]byte("success"))
return &OneMethodAPI{fun: func() { calls <- result }} })
} node.RegisterHandler("test", "/test", handler)
services := map[string]struct {
APIs []rpc.API // start node
Maker InstrumentingWrapper if err := node.Start(); err != nil {
}{ t.Fatalf("could not start node: %v", err)
"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},
} }
for id, config := range services { // create HTTP request
config := config httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7878/test", nil)
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)
if err != nil { if err != nil {
t.Error("could not issue new http request ", err) 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) 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 { // Tests that the given handler will not be successfully mounted since no HTTP server
conf := &Config{HTTPPort: 7453, WSPort: 7453} // 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) node, err := New(conf)
if err != nil { 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 { 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 return node
} }
func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { func doHTTPRequest(t *testing.T, req *http.Request) *http.Response {
client := &http.Client{} client := http.DefaultClient
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { 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() }) t.Cleanup(func() { resp.Body.Close() })
return resp 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 ( import (
"compress/gzip" "compress/gzip"
"context"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"sort"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
@ -30,6 +34,287 @@ import (
"github.com/rs/cors" "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 // NewHTTPHandlerStack returns wrapped http-related handlers
func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string, timeouts *rpc.HTTPTimeouts) http.Handler { func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string, timeouts *rpc.HTTPTimeouts) http.Handler {
// Wrap the CORS-handler within a host-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 = newVHostHandler(vhosts, handler)
handler = newGzipHandler(handler) handler = newGzipHandler(handler)
// make sure timeout values are meaningful // decrease 1 second to let TimeoutHandler trigger first
CheckTimeouts(timeouts) // 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 // 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 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 type ipcServer struct {
// request to the websocket protocol. If not, serves the request with the http handler. log log.Logger
func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { endpoint string
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isWebsocket(r) { mu sync.Mutex
ws.ServeHTTP(w, r) listener net.Listener
log.Debug("serving websocket request") srv *rpc.Server
return }
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) return 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")
} }

View file

@ -1,40 +1,96 @@
package node package node
import ( import (
"bytes"
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/internal/testlog"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { // TestCorsHandler makes sure CORS are properly handled on the http server.
srv := rpc.NewServer() func TestCorsHandler(t *testing.T) {
srv := createAndStartServer(t, httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, wsConfig{})
defer srv.stop()
handler := NewWebsocketUpgradeHandler(nil, srv.WebsocketHandler([]string{})) resp := testRequest(t, "origin", "test.com", "", srv)
ts := httptest.NewServer(handler) assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin"))
defer ts.Close()
responses := make(chan *http.Response) resp2 := testRequest(t, "origin", "bad", "", srv)
go func(responses chan *http.Response) { assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin"))
client := &http.Client{} }
req, _ := http.NewRequest(http.MethodGet, ts.URL, nil) // TestVhosts makes sure vhosts are properly handled on the http server.
req.Header.Set("Connection", "upgrade") func TestVhosts(t *testing.T) {
req.Header.Set("Upgrade", "websocket") srv := createAndStartServer(t, httpConfig{Vhosts: []string{"test"}}, false, wsConfig{})
req.Header.Set("Sec-WebSocket-Version", "13") defer srv.stop()
req.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==")
resp, err := client.Do(req) resp := testRequest(t, "", "", "test", srv)
if err != nil { assert.Equal(t, resp.StatusCode, http.StatusOK)
t.Error("could not issue a GET request to the test http server", err)
}
responses <- resp
}(responses)
response := <-responses resp2 := testRequest(t, "", "", "bad", srv)
assert.Equal(t, "websocket", response.Header.Get("Upgrade")) 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. // 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 package node
import ( import (
"reflect"
"github.com/XinFinOrg/XDPoSChain/p2p" "github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/rpc" "github.com/XinFinOrg/XDPoSChain/rpc"
) )
// NoopService is a trivial implementation of the Service interface. // NoopLifecycle is a trivial implementation of the Service interface.
type NoopService struct{} type NoopLifecycle struct{}
func (s *NoopService) Protocols() []p2p.Protocol { return nil } func (s *NoopLifecycle) Start() error { return nil }
func (s *NoopService) APIs() []rpc.API { return nil } func (s *NoopLifecycle) Stop() error { return nil }
func (s *NoopService) Start(*p2p.Server) error { return nil }
func (s *NoopService) Stop() error { return nil }
func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil } 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. // signatures but different outer types.
type NoopServiceA struct{ NoopService } type Noop struct{ NoopLifecycle }
type NoopServiceB struct{ NoopService }
type NoopServiceC struct{ NoopService }
func NewNoopServiceA(*ServiceContext) (Service, error) { return new(NoopServiceA), nil } // InstrumentedService is an implementation of Lifecycle for which all interface
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
// methods can be instrumented both return value as well as event hook wise. // methods can be instrumented both return value as well as event hook wise.
type InstrumentedService struct { type InstrumentedService struct {
start error
stop error
startHook func()
stopHook func()
protocols []p2p.Protocol 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) Start() error {
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 {
if s.startHook != nil { if s.startHook != nil {
s.startHook(server) s.startHook()
} }
return s.start return s.start
} }
@ -86,48 +65,49 @@ func (s *InstrumentedService) Stop() error {
return s.stop return s.stop
} }
// InstrumentingWrapper is a method to specialize a service constructor returning type FullService struct{}
// a generic InstrumentedService into one returning a wrapping specific one.
type InstrumentingWrapper func(base ServiceConstructor) ServiceConstructor
func InstrumentingWrapperMaker(base ServiceConstructor, kind reflect.Type) ServiceConstructor { func NewFullService(stack *Node) (*FullService, error) {
return func(ctx *ServiceContext) (Service, error) { fs := new(FullService)
obj, err := base(ctx)
if err != nil {
return nil, err
}
wrapper := reflect.New(kind)
wrapper.Elem().Field(0).Set(reflect.ValueOf(obj).Elem())
return wrapper.Interface().(Service), nil 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 func (f *FullService) APIs() []rpc.API {
// same method signatures but different outer types. return []rpc.API{
type InstrumentedServiceA struct{ InstrumentedService } {
type InstrumentedServiceB struct{ InstrumentedService } Namespace: "admin",
type InstrumentedServiceC struct{ InstrumentedService } Version: "1.0",
},
func InstrumentedServiceMakerA(base ServiceConstructor) ServiceConstructor { {
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceA{})) Namespace: "debug",
} Version: "1.0",
Public: true,
func InstrumentedServiceMakerB(base ServiceConstructor) ServiceConstructor { },
return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceB{})) {
} Namespace: "net",
Version: "1.0",
func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor { Public: true,
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()
} }
} }

View file

@ -17,16 +17,9 @@
package protocols package protocols
import ( import (
"context"
"errors"
"fmt" "fmt"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters"
p2ptest "github.com/XinFinOrg/XDPoSChain/p2p/testing"
) )
// handshake message type // handshake message type
@ -64,326 +57,3 @@ func checkProtoHandshake(testVersion uint, testNetworkID string) func(interface{
return nil 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) newPeerHook func(*Peer)
lock sync.Mutex // protects running lock sync.Mutex // protects running
running bool Running bool
ntab discoverTable ntab discoverTable
listener net.Listener listener net.Listener
@ -344,7 +344,7 @@ func (srv *Server) Self() *discover.Node {
srv.lock.Lock() srv.lock.Lock()
defer srv.lock.Unlock() defer srv.lock.Unlock()
if !srv.running { if !srv.Running {
return &discover.Node{IP: net.ParseIP("0.0.0.0")} return &discover.Node{IP: net.ParseIP("0.0.0.0")}
} }
return srv.makeSelf(srv.listener, srv.ntab) 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() { func (srv *Server) Stop() {
srv.lock.Lock() srv.lock.Lock()
defer srv.lock.Unlock() defer srv.lock.Unlock()
if !srv.running { if !srv.Running {
return return
} }
srv.running = false srv.Running = false
if srv.listener != nil { if srv.listener != nil {
// this unblocks listener Accept // this unblocks listener Accept
srv.listener.Close() srv.listener.Close()
@ -418,10 +418,10 @@ func (s *sharedUDPConn) Close() error {
func (srv *Server) Start() (err error) { func (srv *Server) Start() (err error) {
srv.lock.Lock() srv.lock.Lock()
defer srv.lock.Unlock() defer srv.lock.Unlock()
if srv.running { if srv.Running {
return errors.New("server already running") return errors.New("server already running")
} }
srv.running = true srv.Running = true
srv.log = srv.Config.Logger srv.log = srv.Config.Logger
if srv.log == nil { if srv.log == nil {
srv.log = log.New() srv.log = log.New()
@ -538,7 +538,7 @@ func (srv *Server) Start() (err error) {
srv.loopWG.Add(1) srv.loopWG.Add(1)
go srv.run(dialer) go srv.run(dialer)
srv.running = true srv.Running = true
return nil 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 { func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) error {
// Prevent leftover pending conns from entering the handshake. // Prevent leftover pending conns from entering the handshake.
srv.lock.Lock() srv.lock.Lock()
running := srv.running running := srv.Running
srv.lock.Unlock() srv.lock.Unlock()
if !running { if !running {
return errServerStopped return errServerStopped

View file

@ -243,7 +243,7 @@ func TestServerTaskScheduling(t *testing.T) {
Config: Config{MaxPeers: 10}, Config: Config{MaxPeers: 10},
quit: make(chan struct{}), quit: make(chan struct{}),
ntab: fakeTable{}, ntab: fakeTable{},
running: true, Running: true,
log: log.New(), log: log.New(),
} }
srv.loopWG.Add(1) srv.loopWG.Add(1)
@ -288,7 +288,7 @@ func TestServerManyTasks(t *testing.T) {
srv = &Server{ srv = &Server{
quit: make(chan struct{}), quit: make(chan struct{}),
ntab: fakeTable{}, ntab: fakeTable{},
running: true, Running: true,
log: log.New(), log: log.New(),
} }
done = make(chan *testTask) done = make(chan *testTask)

View file

@ -72,11 +72,11 @@ func (d *DockerAdapter) Name() string {
// NewNode returns a new DockerNode using the given config // NewNode returns a new DockerNode using the given config
func (d *DockerAdapter) NewNode(config *NodeConfig) (Node, error) { func (d *DockerAdapter) NewNode(config *NodeConfig) (Node, error) {
if len(config.Services) == 0 { if len(config.Lifecycles) == 0 {
return nil, errors.New("node must have at least one service") return nil, errors.New("node must have at least one lifecycle")
} }
for _, service := range config.Services { for _, service := range config.Lifecycles {
if _, exists := serviceFuncs[service]; !exists { if _, exists := lifecycleConstructorFuncs[service]; !exists {
return nil, fmt.Errorf("unknown node service %q", service) return nil, fmt.Errorf("unknown node service %q", service)
} }
} }
@ -123,7 +123,7 @@ func (n *DockerNode) dockerCommand() *exec.Cmd {
"sh", "-c", "sh", "-c",
fmt.Sprintf( fmt.Sprintf(
`exec docker run --interactive --env _P2P_NODE_CONFIG="${_P2P_NODE_CONFIG}" %s p2p-node %s %s`, `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 // NewNode returns a new ExecNode using the given config
func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
if len(config.Services) == 0 { if len(config.Lifecycles) == 0 {
return nil, errors.New("node must have at least one service") return nil, errors.New("node must have at least one service lifecycle")
} }
for _, service := range config.Services { for _, service := range config.Lifecycles {
if _, exists := serviceFuncs[service]; !exists { if _, exists := lifecycleConstructorFuncs[service]; !exists {
return nil, fmt.Errorf("unknown node service %q", service) 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 { func (n *ExecNode) execCommand() *exec.Cmd {
return &exec.Cmd{ return &exec.Cmd{
Path: reexec.Self(), 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) log.Crit("error creating node stack", "err", err)
} }
// register the services, collecting them into a map so we can wrap // Register the services, collecting them into a map so they can
// them in a snapshot service // be accessed by the snapshot API.
services := make(map[string]node.Service, len(serviceNames)) services := make(map[string]node.Lifecycle, len(serviceNames))
for _, name := range serviceNames { for _, name := range serviceNames {
serviceFunc, exists := serviceFuncs[name] lifecycleFunc, exists := lifecycleConstructorFuncs[name]
if !exists { if !exists {
log.Crit("unknown node service", "name", name) log.Crit("unknown node service", "name", name)
} }
constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) { ctx := &ServiceContext{
ctx := &ServiceContext{ RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs},
RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, Config: conf.Node,
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
} }
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) log.Crit("error starting service", "name", name, "err", err)
} }
services[name] = service
stack.RegisterLifecycle(service)
} }
// register the snapshot service // Add the snapshot API.
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { stack.RegisterAPIs([]rpc.API{{
return &snapshotService{services}, nil Namespace: "simulation",
}); err != nil { Version: "1.0",
log.Crit("error starting snapshot service", "err", err) Service: SnapshotAPI{services},
} }})
// start the stack
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
log.Crit("error stating node stack", "err", err) log.Crit("error stating node stack", "err", err)
} }
@ -474,46 +467,20 @@ func execP2PNode() {
defer signal.Stop(sigc) defer signal.Stop(sigc)
<-sigc <-sigc
log.Info("Received SIGTERM, shutting down...") log.Info("Received SIGTERM, shutting down...")
stack.Stop() stack.Close()
}() }()
// wait for the stack to exit // wait for the stack to exit
stack.Wait() 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 ( const (
envNodeConfig = "_P2P_NODE_CONFIG" envNodeConfig = "_P2P_NODE_CONFIG"
) )
// SnapshotAPI provides an RPC method to create snapshots of services // SnapshotAPI provides an RPC method to create snapshots of services
type SnapshotAPI struct { type SnapshotAPI struct {
services map[string]node.Service services map[string]node.Lifecycle
} }
func (api SnapshotAPI) Snapshot() (map[string][]byte, error) { 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 // SimAdapter is a NodeAdapter which creates in-memory simulation nodes and
// connects them using in-memory net.Pipe connections // connects them using in-memory net.Pipe connections
type SimAdapter struct { type SimAdapter struct {
mtx sync.RWMutex mtx sync.RWMutex
nodes map[discover.NodeID]*SimNode nodes map[discover.NodeID]*SimNode
services map[string]ServiceFunc lifecycles LifecycleConstructors
} }
// NewSimAdapter creates a SimAdapter which is capable of running in-memory // 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 // 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) // 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{ return &SimAdapter{
nodes: make(map[discover.NodeID]*SimNode), // nodes: make(map[discover.NodeID]*SimNode),
services: services, // 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 // 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") return nil, errors.New("node must have at least one service")
} }
for _, service := range config.Services { for _, service := range config.Lifecycles {
if _, exists := sa.services[service]; !exists { if _, exists := sa.lifecycles[service]; !exists {
return nil, fmt.Errorf("unknown node service %q", service) return nil, fmt.Errorf("unknown node service %q", service)
} }
} }
@ -95,7 +97,7 @@ func (sa *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
config: config, config: config,
node: n, node: n,
adapter: sa, adapter: sa,
running: make(map[string]node.Service), running: make(map[string]node.Lifecycle),
connected: make(map[discover.NodeID]bool), connected: make(map[discover.NodeID]bool),
} }
sa.nodes[id] = simNode sa.nodes[id] = simNode
@ -129,11 +131,7 @@ func (sa *SimAdapter) DialRPC(id discover.NodeID) (*rpc.Client, error) {
if !ok { if !ok {
return nil, fmt.Errorf("unknown node: %s", id) return nil, fmt.Errorf("unknown node: %s", id)
} }
handler, err := node.node.RPCHandler() return node.node.Attach()
if err != nil {
return nil, err
}
return rpc.DialInProc(handler), nil
} }
// GetNode returns the node with the given ID if it exists // GetNode returns the node with the given ID if it exists
@ -153,7 +151,7 @@ type SimNode struct {
config *NodeConfig config *NodeConfig
adapter *SimAdapter adapter *SimAdapter
node *node.Node node *node.Node
running map[string]node.Service running map[string]node.Lifecycle
client *rpc.Client client *rpc.Client
registerOnce sync.Once registerOnce sync.Once
connected map[discover.NodeID]bool connected map[discover.NodeID]bool
@ -202,7 +200,7 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error {
// simulation_snapshot RPC method // simulation_snapshot RPC method
func (sn *SimNode) Snapshots() (map[string][]byte, error) { func (sn *SimNode) Snapshots() (map[string][]byte, error) {
sn.lock.RLock() 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 { for name, service := range sn.running {
services[name] = service 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 // Start registers the services and starts the underlying devp2p node
func (sn *SimNode) Start(snapshots map[string][]byte) error { 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 // ensure we only register the services once in the case of the node
// being stopped and then started again // being stopped and then started again
var regErr error var regErr error
sn.registerOnce.Do(func() { sn.registerOnce.Do(func() {
for _, name := range sn.config.Services { for _, name := range sn.config.Lifecycles {
if err := sn.node.Register(newService(name)); err != nil { 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 regErr = err
return 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 { if regErr != nil {
@ -267,13 +261,13 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error {
} }
// create an in-process RPC client // create an in-process RPC client
handler, err := sn.node.RPCHandler() client, err := sn.node.Attach()
if err != nil { if err != nil {
return err return err
} }
sn.lock.Lock() sn.lock.Lock()
sn.client = rpc.DialInProc(handler) sn.client = client
sn.lock.Unlock() sn.lock.Unlock()
return nil return nil
@ -287,14 +281,14 @@ func (sn *SimNode) Stop() error {
sn.client = nil sn.client = nil
} }
sn.lock.Unlock() sn.lock.Unlock()
return sn.node.Stop() return sn.node.Close()
} }
// Services returns a copy of the underlying services // Services returns a copy of the underlying services
func (sn *SimNode) Services() []node.Service { func (sn *SimNode) Services() []node.Lifecycle {
sn.lock.RLock() sn.lock.RLock()
defer sn.lock.RUnlock() 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 { for _, service := range sn.running {
services = append(services, service) services = append(services, service)
} }
@ -319,7 +313,7 @@ func (sn *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription {
// NodeInfo returns information about the node // NodeInfo returns information about the node
func (sn *SimNode) NodeInfo() *p2p.NodeInfo { func (sn *SimNode) NodeInfo() *p2p.NodeInfo {
server := sn.Server() server := sn.Server()
if server == nil { if server.Running == false {
return &p2p.NodeInfo{ return &p2p.NodeInfo{
ID: sn.ID.String(), ID: sn.ID.String(),
Enode: sn.Node().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 is a human friendly name for the node like "node01"
Name string Name string
// Services are the names of the services which should be run when // Lifecycles are the names of the service lifecycles which should be run when
// starting the node (for SimNodes it should be the names of services // starting the node (for SimNodes it should be the names of service lifecycles
// contained in SimAdapter.services, for other nodes it should be // contained in SimAdapter.lifecycles, for other nodes it should be
// services registered by calling the RegisterService function) // service lifecycles registered by calling the RegisterLifecycle function)
Services []string Lifecycles []string
// function to sanction or prevent suggesting a peer // function to sanction or prevent suggesting a peer
Reachable func(id discover.NodeID) bool Reachable func(id discover.NodeID) bool
@ -127,7 +127,7 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) {
confJSON := nodeConfigJSON{ confJSON := nodeConfigJSON{
ID: n.ID.String(), ID: n.ID.String(),
Name: n.Name, Name: n.Name,
Services: n.Services, Services: n.Lifecycles,
LogFile: n.LogFile, LogFile: n.LogFile,
LogVerbosity: int(n.LogVerbosity), LogVerbosity: int(n.LogVerbosity),
} }
@ -166,7 +166,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
} }
n.Name = confJSON.Name n.Name = confJSON.Name
n.Services = confJSON.Services n.Lifecycles = confJSON.Services
n.LogFile = confJSON.LogFile n.LogFile = confJSON.LogFile
n.LogVerbosity = slog.Level(confJSON.LogVerbosity) n.LogVerbosity = slog.Level(confJSON.LogVerbosity)
@ -194,9 +194,8 @@ func RandomNodeConfig() *NodeConfig {
type ServiceContext struct { type ServiceContext struct {
RPCDialer RPCDialer
NodeContext *node.ServiceContext Config *NodeConfig
Config *NodeConfig Snapshot []byte
Snapshot []byte
} }
// RPCDialer is used when initialising services which need to connect to // RPCDialer is used when initialising services which need to connect to
@ -206,27 +205,29 @@ type RPCDialer interface {
DialRPC(id discover.NodeID) (*rpc.Client, error) DialRPC(id discover.NodeID) (*rpc.Client, error)
} }
// Services is a collection of services which can be run in a simulation // LifecycleConstructor allows a Lifecycle to be constructed during node start-up.
type Services map[string]ServiceFunc // While the service-specific package usually takes care of Lifecycle creation and registration,
// for testing purposes, it is useful to be able to construct a Lifecycle on spot.
type LifecycleConstructor func(ctx *ServiceContext, stack *node.Node) (node.Lifecycle, error)
// ServiceFunc returns a node.Service which can be used to boot a devp2p node // LifecycleConstructors stores LifecycleConstructor functions to call during node start-up.
type ServiceFunc func(ctx *ServiceContext) (node.Service, error) type LifecycleConstructors map[string]LifecycleConstructor
// serviceFuncs is a map of registered services which are used to boot devp2p // lifecycleConstructorFuncs is a map of registered services which are used to boot devp2p
// nodes // nodes
var serviceFuncs = make(Services) var lifecycleConstructorFuncs = make(LifecycleConstructors)
// RegisterServices registers the given Services which can then be used to // RegisterLifecycles registers the given Services which can then be used to
// start devp2p nodes using either the Exec or Docker adapters. // start devp2p nodes using either the Exec or Docker adapters.
// //
// It should be called in an init function so that it has the opportunity to // It should be called in an init function so that it has the opportunity to
// execute the services before main() is called. // execute the services before main() is called.
func RegisterServices(services Services) { func RegisterLifecycles(lifecycles LifecycleConstructors) {
for name, f := range services { for name, f := range lifecycles {
if _, exists := serviceFuncs[name]; exists { if _, exists := lifecycleConstructorFuncs[name]; exists {
panic(fmt.Sprintf("node service already exists: %q", name)) panic(fmt.Sprintf("node service already exists: %q", name))
} }
serviceFuncs[name] = f lifecycleConstructorFuncs[name] = f
} }
// now we have registered the services, run reexec.Init() which will // now we have registered the services, run reexec.Init() which will

View file

@ -31,7 +31,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/p2p/discover" "github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations" "github.com/XinFinOrg/XDPoSChain/p2p/simulations"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/adapters" "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")`) 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))) log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, false)))
// register a single ping-pong service // register a single ping-pong service
services := map[string]adapters.ServiceFunc{ services := map[string]adapters.LifecycleConstructor{
"ping-pong": func(ctx *adapters.ServiceContext) (node.Service, error) { "ping-pong": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
return newPingPongService(ctx.Config.ID), nil pps := newPingPongService(ctx.Config.ID)
stack.RegisterProtocols(pps.Protocols())
return pps, nil
}, },
} }
adapters.RegisterServices(services) adapters.RegisterLifecycles(services)
// create the NodeAdapter // create the NodeAdapter
var adapter adapters.NodeAdapter var adapter adapters.NodeAdapter
@ -118,11 +119,7 @@ func (p *pingPongService) Protocols() []p2p.Protocol {
}} }}
} }
func (p *pingPongService) APIs() []rpc.API { func (p *pingPongService) Start() error {
return nil
}
func (p *pingPongService) Start(server *p2p.Server) error {
p.log.Info("ping-pong service starting") p.log.Info("ping-pong service starting")
return nil return nil
} }

View file

@ -51,12 +51,15 @@ type testService struct {
state atomic.Value state atomic.Value
} }
func newTestService(ctx *adapters.ServiceContext) (node.Service, error) { func newTestService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
svc := &testService{ svc := &testService{
id: ctx.Config.ID, id: ctx.Config.ID,
peers: make(map[discover.NodeID]*testPeer), peers: make(map[discover.NodeID]*testPeer),
} }
svc.state.Store(ctx.Snapshot) svc.state.Store(ctx.Snapshot)
stack.RegisterProtocols(svc.Protocols())
stack.RegisterAPIs(svc.APIs())
return svc, nil return svc, nil
} }
@ -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 return nil
} }
@ -275,7 +278,7 @@ func (t *TestAPI) Events(ctx context.Context) (*rpc.Subscription, error) {
return rpcSub, nil return rpcSub, nil
} }
var testServices = adapters.Services{ var testServices = adapters.LifecycleConstructors{
"test": newTestService, "test": newTestService,
} }

View file

@ -82,7 +82,7 @@ func (net *Network) Events() *event.Feed {
// NewNode adds a new node to the network with a random ID // NewNode adds a new node to the network with a random ID
func (net *Network) NewNode() (*Node, error) { func (net *Network) NewNode() (*Node, error) {
conf := adapters.RandomNodeConfig() conf := adapters.RandomNodeConfig()
conf.Services = []string{net.DefaultService} conf.Lifecycles = []string{net.DefaultService}
return net.NewNodeWithConfig(conf) 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 no services are configured, use the default service
if len(conf.Services) == 0 { if len(conf.Lifecycles) == 0 {
conf.Services = []string{net.DefaultService} conf.Lifecycles = []string{net.DefaultService}
} }
// use the NodeAdapter to create the node // use the NodeAdapter to create the node
@ -551,6 +551,10 @@ func (n *Node) NodeInfo() *p2p.NodeInfo {
return nil return nil
} }
info := n.Node.NodeInfo() info := n.Node.NodeInfo()
if info == nil {
return nil
}
info.Name = n.Config.Name info.Name = n.Config.Name
return info return info
} }

View file

@ -31,7 +31,7 @@ import (
// with each other and that a snapshot fully represents the desired topology // with each other and that a snapshot fully represents the desired topology
func TestNetworkSimulation(t *testing.T) { func TestNetworkSimulation(t *testing.T) {
// create simulation network with 20 testService nodes // create simulation network with 20 testService nodes
adapter := adapters.NewSimAdapter(adapters.Services{ adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
"test": newTestService, "test": newTestService,
}) })
network := NewNetwork(adapter, &NetworkConfig{ 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
}