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