diff --git a/ROADMAP.md b/ROADMAP.md index 15215578e..728cd26cb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -102,3 +102,36 @@ startup to inform about this, it's very easy to deal with this configuration change, just replace one line. Removal of SecondsPerBlock is scheduled for May-June 2023 (~0.103.0 release). + +## Services/node address and port configuration + +Version 0.100.0 of NeoGo introduces a multiple binding addresses capability to +the node's services (RPC server, TLS RPC configuration, Prometheus, Pprof) and +the node itself. It allows to specify several listen addresses/ports using an +array of "address:port" pairs in the service's `Addresses` config section and +array of "address:port:announcedPort" tuples in the `ApplicationConfiguration`'s +`Addresses` node config section. Deprecated `Address` and `Port` sections of +`RPC`, `Prometheus`, `Pprof` subsections of the `ApplicationConfiguration` +as far as the one of RPC server's `TLSConfig` are still available, but will be +removed, so please convert your node configuration file to use new `P2P`-level +`Addresses` section for the node services. Deprecated `Address`, `NodePort` and +`AnnouncedPort` sections of `ApplicationConfiguration` will also be removed +eventually, so please update your node configuration file to use `Addresses` +section for the P2P addresses configuration. + +Removal of these config sections is scheduled for May-June 2023 (~0.103.0 release). + +## P2P application settings configuration + +Version 0.100.0 of NeoGo marks the following P2P application settings as +deprecated: `AttemptConnPeers`, `BroadcastFactor`, `DialTimeout`, +`ExtensiblePoolSize`, `MaxPeers`, `MinPeers`, `PingInterval`, `PingTimeout`, +`ProtoTickInterval`. These settings are moved to a separate `P2P` section of +`ApplicationConfiguration`. The `DialTimeout`, `PingInterval`, `PingTimeout`, +`ProtoTickInterval` settings are converted to more precise `Duration` format +(allowing for subsecond time). Please, update your node configuration (all you +need is to move specified settings under the `P2P` section and convert +time-related settings to `Duration` format). + +Removal of deprecated P2P related application settings is scheduled for May-June +2023 (~0.103.0 release). \ No newline at end of file diff --git a/cli/nep_test/nep11_test.go b/cli/nep_test/nep11_test.go index adcc22e29..a8ea2741f 100644 --- a/cli/nep_test/nep11_test.go +++ b/cli/nep_test/nep11_test.go @@ -47,7 +47,7 @@ func TestNEP11Import(t *testing.T) { args := []string{ "neo-go", "wallet", "nep11", "import", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", walletPath, } // missing token hash @@ -123,7 +123,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // transfer funds to contract owner e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--to", nftOwnerAddr, "--token", "GAS", @@ -139,7 +139,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // mint 1 HASHY token by transferring 10 GAS to HASHY contract e.In.WriteString(nftOwnerPass + "\r") e.Run(t, "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wall, "--to", h.StringLE(), "--token", "GAS", @@ -166,7 +166,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // check the balance cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", wall, "--address", nftOwnerAddr} checkBalanceResult := func(t *testing.T, acc string, ids ...[]byte) { @@ -201,7 +201,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // import token e.Run(t, "neo-go", "wallet", "nep11", "import", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wall, "--token", h.StringLE()) @@ -211,7 +211,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // balance check: all accounts e.Run(t, "neo-go", "wallet", "nep11", "balance", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wall, "--token", h.StringLE()) checkBalanceResult(t, nftOwnerAddr, tokenID) @@ -223,7 +223,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // ownerOf: missing contract hash cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOf", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } e.RunWithError(t, cmdOwnerOf...) cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE()) @@ -238,7 +238,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // tokensOf: missing contract hash cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } e.RunWithError(t, cmdTokensOf...) cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE()) @@ -254,7 +254,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // properties: no contract cmdProperties := []string{ "neo-go", "wallet", "nep11", "properties", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } e.RunWithError(t, cmdProperties...) cmdProperties = append(cmdProperties, "--token", h.StringLE()) @@ -280,7 +280,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { // tokens: missing contract hash cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } e.RunWithError(t, cmdTokens...) cmdTokens = append(cmdTokens, "--token", h.StringLE()) @@ -298,7 +298,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { cmdTransfer := []string{ "neo-go", "wallet", "nep11", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", wall, "--to", testcli.ValidatorAddr, "--from", nftOwnerAddr, @@ -328,7 +328,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { verifyH := deployVerifyContract(t, e) cmdTransfer = []string{ "neo-go", "wallet", "nep11", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", wall, "--to", verifyH.StringLE(), "--from", nftOwnerAddr, @@ -398,7 +398,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { // mint 1.00 NFSO token by transferring 10 GAS to NFSO contract e.In.WriteString(testcli.ValidatorPass + "\r") e.Run(t, "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wall, "--to", h.StringLE(), "--token", "GAS", @@ -432,7 +432,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { // check properties e.Run(t, "neo-go", "wallet", "nep11", "properties", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--token", h.StringLE(), "--id", hex.EncodeToString(token1ID)) jProps := e.GetNextLine(t) @@ -449,7 +449,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { // check the balance cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", wall, "--address", testcli.ValidatorAddr} checkBalanceResult := func(t *testing.T, acc string, objs ...idAmount) { @@ -481,7 +481,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { // import token e.Run(t, "neo-go", "wallet", "nep11", "import", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wall, "--token", h.StringLE()) @@ -500,7 +500,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { // ownerOfD: missing contract hash cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOfD", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } e.RunWithError(t, cmdOwnerOf...) cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE()) @@ -515,7 +515,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { // tokensOf: missing contract hash cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } e.RunWithError(t, cmdTokensOf...) cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE()) @@ -533,7 +533,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { // properties: no contract cmdProperties := []string{ "neo-go", "wallet", "nep11", "properties", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } e.RunWithError(t, cmdProperties...) cmdProperties = append(cmdProperties, "--token", h.StringLE()) @@ -566,7 +566,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { // tokens: missing contract hash cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], } e.RunWithError(t, cmdTokens...) cmdTokens = append(cmdTokens, "--token", h.StringLE()) @@ -582,7 +582,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { cmdTransfer := []string{ "neo-go", "wallet", "nep11", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", wall, "--to", nftOwnerAddr, "--from", testcli.ValidatorAddr, @@ -612,7 +612,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) { verifyH := deployVerifyContract(t, e) cmdTransfer = []string{ "neo-go", "wallet", "nep11", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", wall, "--to", verifyH.StringLE(), "--from", testcli.ValidatorAddr, diff --git a/cli/nep_test/nep17_test.go b/cli/nep_test/nep17_test.go index 6a6a78a1d..5964added 100644 --- a/cli/nep_test/nep17_test.go +++ b/cli/nep_test/nep17_test.go @@ -21,7 +21,7 @@ func TestNEP17Balance(t *testing.T) { e := testcli.NewExecutor(t, true) cmdbalance := []string{"neo-go", "wallet", "nep17", "balance"} cmdbase := append(cmdbalance, - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, ) cmd := append(cmdbase, "--address", testcli.ValidatorAddr) @@ -122,7 +122,7 @@ func TestNEP17Transfer(t *testing.T) { e := testcli.NewExecutor(t, true) args := []string{ "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--to", w.Accounts[0].Address, "--token", "NEO", @@ -182,7 +182,7 @@ func TestNEP17Transfer(t *testing.T) { t.Run("default address", func(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--from", testcli.ValidatorAddr, "--force", @@ -208,7 +208,7 @@ func TestNEP17Transfer(t *testing.T) { t.Run("with signers", func(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--from", testcli.ValidatorAddr, "--force", @@ -221,7 +221,7 @@ func TestNEP17Transfer(t *testing.T) { validTil := e.Chain.BlockHeight() + 100 cmd := []string{ "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--to", address.Uint160ToString(e.Chain.GetNotaryContractScriptHash()), "--token", "GAS", @@ -262,7 +262,7 @@ func TestNEP17MultiTransfer(t *testing.T) { require.NoError(t, err) args := []string{ "neo-go", "wallet", "nep17", "multitransfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--from", testcli.ValidatorAddr, "--force", @@ -318,26 +318,26 @@ func TestNEP17ImportToken(t *testing.T) { // missing token hash e.RunWithError(t, "neo-go", "wallet", "nep17", "import", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath) // additional parameter e.RunWithError(t, "neo-go", "wallet", "nep17", "import", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--token", gasContractHash.StringLE(), "useless") e.Run(t, "neo-go", "wallet", "nep17", "import", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--token", gasContractHash.StringLE()) e.Run(t, "neo-go", "wallet", "nep17", "import", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--token", address.Uint160ToString(neoContractHash)) // try address instead of sh // not a NEP-17 token e.RunWithError(t, "neo-go", "wallet", "nep17", "import", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--token", nnsContractHash.StringLE()) diff --git a/cli/options/cli_options_test.go b/cli/options/cli_options_test.go index 4353de239..5ba67a24d 100644 --- a/cli/options/cli_options_test.go +++ b/cli/options/cli_options_test.go @@ -24,7 +24,7 @@ func TestGetRPCClient(t *testing.T) { t.Run("success", func(t *testing.T) { set := flag.NewFlagSet("flagSet", flag.ExitOnError) - set.String(options.RPCEndpointFlag, "http://"+e.RPC.Addr, "") + set.String(options.RPCEndpointFlag, "http://"+e.RPC.Addresses()[0], "") ctx := cli.NewContext(app.New(), set, nil) gctx, _ := options.GetTimeoutContext(ctx) _, ec := options.GetRPCClient(gctx, ctx) diff --git a/cli/query/query_test.go b/cli/query/query_test.go index 0f7bbd4e6..84b58914a 100644 --- a/cli/query/query_test.go +++ b/cli/query/query_test.go @@ -30,7 +30,7 @@ func TestQueryTx(t *testing.T) { transferArgs := []string{ "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--to", w.Accounts[0].Address, "--token", "NEO", @@ -47,7 +47,7 @@ func TestQueryTx(t *testing.T) { tx, ok := e.Chain.GetMemPool().TryGetValue(txHash) require.True(t, ok) - args := []string{"neo-go", "query", "tx", "--rpc-endpoint", "http://" + e.RPC.Addr} + args := []string{"neo-go", "query", "tx", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} e.Run(t, append(args, txHash.StringLE())...) e.CheckNextLine(t, `Hash:\s+`+txHash.StringLE()) e.CheckNextLine(t, `OnChain:\s+false`) @@ -74,7 +74,7 @@ func TestQueryTx(t *testing.T) { t.Run("FAULT", func(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "invokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--force", @@ -144,7 +144,7 @@ func compareQueryTxVerbose(t *testing.T, e *testcli.Executor, tx *transaction.Tr func TestQueryHeight(t *testing.T) { e := testcli.NewExecutor(t, true) - args := []string{"neo-go", "query", "height", "--rpc-endpoint", "http://" + e.RPC.Addr} + args := []string{"neo-go", "query", "height", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} e.Run(t, args...) e.CheckNextLine(t, `^Latest block: [0-9]+$`) e.CheckNextLine(t, `^Validated state: [0-9]+$`) diff --git a/cli/server/server.go b/cli/server/server.go index b48a7ac15..6f132bb52 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -141,8 +141,14 @@ func initBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *m pprof := metrics.NewPprofService(cfg.ApplicationConfiguration.Pprof, log) go chain.Run() - go prometheus.Start() - go pprof.Start() + err = prometheus.Start() + if err != nil { + return nil, nil, nil, cli.NewExitError(fmt.Errorf("failed to start Prometheus service: %w", err), 1) + } + err = pprof.Start() + if err != nil { + return nil, nil, nil, cli.NewExitError(fmt.Errorf("failed to start Pprof service: %w", err), 1) + } return chain, prometheus, pprof, nil } @@ -440,7 +446,10 @@ func startServer(ctx *cli.Context) error { grace, cancel := context.WithCancel(newGraceContext()) defer cancel() - serverConfig := network.NewServerConfig(cfg) + serverConfig, err := network.NewServerConfig(cfg) + if err != nil { + return cli.NewExitError(err, 1) + } chain, prometheus, pprof, err := initBCWithMetrics(cfg, log) if err != nil { @@ -540,10 +549,18 @@ Main: } pprof.ShutDown() pprof = metrics.NewPprofService(cfgnew.ApplicationConfiguration.Pprof, log) - go pprof.Start() + err = pprof.Start() + if err != nil { + shutdownErr = fmt.Errorf("failed to start Pprof service: %w", err) + cancel() // Fatal error, like for RPC server. + } prometheus.ShutDown() prometheus = metrics.NewPrometheusService(cfgnew.ApplicationConfiguration.Prometheus, log) - go prometheus.Start() + err = prometheus.Start() + if err != nil { + shutdownErr = fmt.Errorf("failed to start Prometheus service: %w", err) + cancel() // Fatal error, like for RPC server. + } case sigusr1: if oracleSrv != nil { serv.DelService(oracleSrv) @@ -621,15 +638,15 @@ Main: // In case global Address (of the node) provided and RPC/Prometheus/Pprof don't have configured addresses they will // use global one. So Node and RPC and Prometheus and Pprof will run on one address. func configureAddresses(cfg *config.ApplicationConfiguration) { - if cfg.Address != "" { - if cfg.RPC.Address == "" { - cfg.RPC.Address = cfg.Address + if cfg.Address != nil && *cfg.Address != "" { //nolint:staticcheck // SA1019: cfg.Address is deprecated + if cfg.RPC.Address == nil || *cfg.RPC.Address == "" { //nolint:staticcheck // SA1019: cfg.RPC.Address is deprecated + cfg.RPC.Address = cfg.Address //nolint:staticcheck // SA1019: cfg.RPC.Address is deprecated } - if cfg.Prometheus.Address == "" { - cfg.Prometheus.Address = cfg.Address + if cfg.Prometheus.Address == nil || *cfg.Prometheus.Address == "" { //nolint:staticcheck // SA1019: cfg.Prometheus.Address is deprecated + cfg.Prometheus.Address = cfg.Address //nolint:staticcheck // SA1019: cfg.Prometheus.Address is deprecated } - if cfg.Pprof.Address == "" { - cfg.Pprof.Address = cfg.Address + if cfg.Pprof.Address == nil || *cfg.Pprof.Address == "" { //nolint:staticcheck // SA1019: cfg.Pprof.Address is deprecated + cfg.Pprof.Address = cfg.Address //nolint:staticcheck // SA1019: cfg.Pprof.Address is deprecated } } } diff --git a/cli/server/server_test.go b/cli/server/server_test.go index f13d4664b..5a7a36948 100644 --- a/cli/server/server_test.go +++ b/cli/server/server_test.go @@ -312,57 +312,61 @@ func TestRestoreDB(t *testing.T) { require.NoError(t, restoreDB(ctx)) } +// TestConfigureAddresses checks deprecated code compatibility, it should be removed +// along with deprecated `Address` field removal. func TestConfigureAddresses(t *testing.T) { defaultAddress := "http://localhost:10333" customAddress := "http://localhost:10334" t.Run("default addresses", func(t *testing.T) { cfg := &config.ApplicationConfiguration{ - Address: defaultAddress, + Address: &defaultAddress, //nolint:staticcheck // SA1019: Address is deprecated } configureAddresses(cfg) - require.Equal(t, defaultAddress, cfg.RPC.Address) - require.Equal(t, defaultAddress, cfg.Prometheus.Address) - require.Equal(t, defaultAddress, cfg.Pprof.Address) + require.Equal(t, defaultAddress, *cfg.RPC.Address) //nolint:staticcheck // SA1019: cfg.RPC.Address is deprecated + require.Equal(t, defaultAddress, *cfg.Prometheus.Address) //nolint:staticcheck // SA1019: cfg.Prometheus.Address is deprecated + require.Equal(t, defaultAddress, *cfg.Pprof.Address) //nolint:staticcheck // SA1019: cfg.Pprof.Address is deprecated }) t.Run("custom RPC address", func(t *testing.T) { cfg := &config.ApplicationConfiguration{ - Address: defaultAddress, + Address: &defaultAddress, //nolint:staticcheck // SA1019: Address is deprecated RPC: config.RPC{ - Address: customAddress, + BasicService: config.BasicService{ + Address: &customAddress, //nolint:staticcheck // SA1019: Address is deprecated + }, }, } configureAddresses(cfg) - require.Equal(t, cfg.RPC.Address, customAddress) - require.Equal(t, cfg.Prometheus.Address, defaultAddress) - require.Equal(t, cfg.Pprof.Address, defaultAddress) + require.Equal(t, *cfg.RPC.Address, customAddress) //nolint:staticcheck // SA1019: cfg.RPC.Address is deprecated + require.Equal(t, *cfg.Prometheus.Address, defaultAddress) //nolint:staticcheck // SA1019: cfg.Prometheus.Address is deprecated + require.Equal(t, *cfg.Pprof.Address, defaultAddress) //nolint:staticcheck // SA1019: cfg.Pprof.Address is deprecated }) t.Run("custom Pprof address", func(t *testing.T) { cfg := &config.ApplicationConfiguration{ - Address: defaultAddress, + Address: &defaultAddress, //nolint:staticcheck // SA1019: Address is deprecated Pprof: config.BasicService{ - Address: customAddress, + Address: &customAddress, //nolint:staticcheck // SA1019: Address is deprecated }, } configureAddresses(cfg) - require.Equal(t, cfg.RPC.Address, defaultAddress) - require.Equal(t, cfg.Prometheus.Address, defaultAddress) - require.Equal(t, cfg.Pprof.Address, customAddress) + require.Equal(t, *cfg.RPC.Address, defaultAddress) //nolint:staticcheck // SA1019: cfg.RPC.Address is deprecated + require.Equal(t, *cfg.Prometheus.Address, defaultAddress) //nolint:staticcheck // SA1019: cfg.Prometheus.Address is deprecated + require.Equal(t, *cfg.Pprof.Address, customAddress) //nolint:staticcheck // SA1019: cfg.Pprof.Address is deprecated }) t.Run("custom Prometheus address", func(t *testing.T) { cfg := &config.ApplicationConfiguration{ - Address: defaultAddress, + Address: &defaultAddress, //nolint:staticcheck // SA1019: Address is deprecated Prometheus: config.BasicService{ - Address: customAddress, + Address: &customAddress, //nolint:staticcheck // SA1019: Address is deprecated }, } configureAddresses(cfg) - require.Equal(t, cfg.RPC.Address, defaultAddress) - require.Equal(t, cfg.Prometheus.Address, customAddress) - require.Equal(t, cfg.Pprof.Address, defaultAddress) + require.Equal(t, *cfg.RPC.Address, defaultAddress) //nolint:staticcheck // SA1019: cfg.RPC.Address is deprecated + require.Equal(t, *cfg.Prometheus.Address, customAddress) //nolint:staticcheck // SA1019: cfg.Prometheus.Address is deprecated + require.Equal(t, *cfg.Pprof.Address, defaultAddress) //nolint:staticcheck // SA1019: cfg.Pprof.Address is deprecated }) } diff --git a/cli/smartcontract/contract_test.go b/cli/smartcontract/contract_test.go index 4d9a8c81c..917f07221 100644 --- a/cli/smartcontract/contract_test.go +++ b/cli/smartcontract/contract_test.go @@ -310,7 +310,7 @@ func TestDeployBigContract(t *testing.T) { e.In.WriteString(testcli.ValidatorPass + "\r") e.RunWithError(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", manifestName) } @@ -333,7 +333,7 @@ func TestContractDeployWithData(t *testing.T) { e := testcli.NewExecutor(t, true) cmd := []string{ "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", manifestName, "--force", @@ -363,7 +363,7 @@ func TestContractDeployWithData(t *testing.T) { require.NoError(t, err) e.Run(t, "neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], h.StringLE(), "getValueWithKey", "key1", ) @@ -375,7 +375,7 @@ func TestContractDeployWithData(t *testing.T) { require.Equal(t, []byte{12}, res.Stack[0].Value()) e.Run(t, "neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], h.StringLE(), "getValueWithKey", "key2", ) @@ -408,33 +408,33 @@ func TestDeployWithSigners(t *testing.T) { t.Run("missing nef", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", "", "--manifest", manifestName) }) t.Run("missing manifest", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", "") }) t.Run("corrupted data", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", manifestName, "[", "str1") }) t.Run("invalid data", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", manifestName, "str1", "str2") }) t.Run("missing wallet", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", manifestName, "[", "str1", "str2", "]") @@ -447,7 +447,7 @@ func TestDeployWithSigners(t *testing.T) { }) e.In.WriteString(testcli.ValidatorPass + "\r") e.Run(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", manifestName, "--force", @@ -534,7 +534,7 @@ func TestContractManifestGroups(t *testing.T) { e.In.WriteString(testcli.ValidatorPass + "\r") e.Run(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--in", nefName, "--manifest", manifestName, "--force", "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr) @@ -557,22 +557,22 @@ func TestContract_TestInvokeScript(t *testing.T) { t.Run("missing in", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr) + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) }) t.Run("unexisting in", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--in", badNef) }) t.Run("invalid nef", func(t *testing.T) { require.NoError(t, os.WriteFile(badNef, []byte("qwer"), os.ModePerm)) e.RunWithError(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--in", badNef) }) t.Run("invalid signers", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--in", goodNef, "--", "not-a-valid-signer") }) t.Run("no RPC endpoint", func(t *testing.T) { @@ -582,34 +582,34 @@ func TestContract_TestInvokeScript(t *testing.T) { }) t.Run("good", func(t *testing.T) { e.Run(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--in", goodNef) }) t.Run("good with hashed signer", func(t *testing.T) { e.Run(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--in", goodNef, "--", util.Uint160{1, 2, 3}.StringLE()) }) t.Run("good with addressed signer", func(t *testing.T) { e.Run(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--in", goodNef, "--", address.Uint160ToString(util.Uint160{1, 2, 3})) }) t.Run("historic, invalid", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--historic", "bad", "--in", goodNef) }) t.Run("historic, index", func(t *testing.T) { e.Run(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--historic", "0", "--in", goodNef) }) t.Run("historic, hash", func(t *testing.T) { e.Run(t, "neo-go", "contract", "testinvokescript", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--historic", e.Chain.GetHeaderHash(0).StringLE(), "--in", goodNef) }) @@ -639,7 +639,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { require.NoError(t, err) require.NoError(t, os.WriteFile(configPath, yml, 0666)) e.Run(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, "--force", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--force", "--wallet-config", configPath, "--address", testcli.ValidatorAddr, "--in", nefName, "--manifest", manifestName) @@ -663,7 +663,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { }) cmd := []string{"neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://" + e.RPC.Addr} + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} t.Run("missing hash", func(t *testing.T) { e.RunWithError(t, cmd...) }) @@ -704,7 +704,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { t.Run("real invoke", func(t *testing.T) { cmd := []string{"neo-go", "contract", "invokefunction", - "--rpc-endpoint", "http://" + e.RPC.Addr} + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]} t.Run("missing wallet", func(t *testing.T) { cmd := append(cmd, h.StringLE(), "getValue") e.RunWithError(t, cmd...) @@ -778,7 +778,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { txout := filepath.Join(tmpDir, "test_contract_tx.json") cmd = []string{"neo-go", "contract", "invokefunction", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--out", txout, "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, } @@ -837,7 +837,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { t.Run("test Storage.Find", func(t *testing.T) { cmd := []string{"neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], h.StringLE(), "testFind"} t.Run("keys only", func(t *testing.T) { @@ -901,7 +901,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { stateBeforeUpdate = mptBeforeUpdate.Root e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "invokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--force", h.StringLE(), "update", @@ -913,14 +913,14 @@ func TestComlileAndInvokeFunction(t *testing.T) { indexAfterUpdate = e.Chain.BlockHeight() e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], h.StringLE(), "getValue") checkGetValueOut("on update|sub update") }) t.Run("historic", func(t *testing.T) { t.Run("bad ref", func(t *testing.T) { e.RunWithError(t, "neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--historic", "bad", h.StringLE(), "getValue") }) @@ -931,7 +931,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { } { t.Run(name, func(t *testing.T) { e.Run(t, "neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--historic", ref, h.StringLE(), "getValue") }) @@ -939,7 +939,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { } t.Run("updated historic", func(t *testing.T) { e.Run(t, "neo-go", "contract", "testinvokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--historic", strconv.FormatUint(uint64(indexAfterUpdate), 10), h.StringLE(), "getValue") checkGetValueOut("on update|sub update") diff --git a/cli/wallet/candidate_test.go b/cli/wallet/candidate_test.go index 822d764f4..446b9a82f 100644 --- a/cli/wallet/candidate_test.go +++ b/cli/wallet/candidate_test.go @@ -22,7 +22,7 @@ func TestRegisterCandidate(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--from", testcli.ValidatorAddr, "--force", @@ -31,29 +31,29 @@ func TestRegisterCandidate(t *testing.T) { e.CheckTxPersisted(t) e.Run(t, "neo-go", "query", "committee", - "--rpc-endpoint", "http://"+e.RPC.Addr) + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) e.CheckNextLine(t, "^\\s*"+validatorHex) e.Run(t, "neo-go", "query", "candidates", - "--rpc-endpoint", "http://"+e.RPC.Addr) + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) e.CheckNextLine(t, "^\\s*Key.+$") // Header. e.CheckEOF(t) // missing address e.RunWithError(t, "neo-go", "wallet", "candidate", "register", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet) // additional parameter e.RunWithError(t, "neo-go", "wallet", "candidate", "register", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", validatorAddress, "error") e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "candidate", "register", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", validatorAddress, "--force") @@ -68,14 +68,14 @@ func TestRegisterCandidate(t *testing.T) { t.Run("VoteUnvote", func(t *testing.T) { // positional instead of a flag. e.RunWithError(t, "neo-go", "wallet", "candidate", "vote", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", validatorAddress, validatorHex) // not "--candidate hex", but "hex". e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "candidate", "vote", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", validatorAddress, "--candidate", validatorHex, @@ -89,18 +89,18 @@ func TestRegisterCandidate(t *testing.T) { require.Equal(t, b, vs[0].Votes) e.Run(t, "neo-go", "query", "committee", - "--rpc-endpoint", "http://"+e.RPC.Addr) + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) e.CheckNextLine(t, "^\\s*"+validatorHex) e.Run(t, "neo-go", "query", "candidates", - "--rpc-endpoint", "http://"+e.RPC.Addr) + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) e.CheckNextLine(t, "^\\s*Key.+$") // Header. e.CheckNextLine(t, "^\\s*"+validatorHex+"\\s*"+b.String()+"\\s*true\\s*true$") e.CheckEOF(t) // check state e.Run(t, "neo-go", "query", "voter", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], validatorAddress) e.CheckNextLine(t, "^\\s*Voted:\\s+"+validatorHex+"\\s+\\("+validatorAddress+"\\)$") e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$") @@ -110,7 +110,7 @@ func TestRegisterCandidate(t *testing.T) { // unvote e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "candidate", "vote", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", validatorAddress, "--force") @@ -123,7 +123,7 @@ func TestRegisterCandidate(t *testing.T) { // check state e.Run(t, "neo-go", "query", "voter", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], validatorAddress) e.CheckNextLine(t, "^\\s*Voted:\\s+"+"null") // no vote. e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$") @@ -133,18 +133,18 @@ func TestRegisterCandidate(t *testing.T) { // missing address e.RunWithError(t, "neo-go", "wallet", "candidate", "unregister", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet) // additional argument e.RunWithError(t, "neo-go", "wallet", "candidate", "unregister", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", validatorAddress, "argument") e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "candidate", "unregister", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--address", validatorAddress, "--force") @@ -156,7 +156,7 @@ func TestRegisterCandidate(t *testing.T) { // query voter: missing address e.RunWithError(t, "neo-go", "query", "voter") // Excessive parameters. - e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addr, validatorAddress, validatorAddress) - e.RunWithError(t, "neo-go", "query", "committee", "--rpc-endpoint", "http://"+e.RPC.Addr, "something") - e.RunWithError(t, "neo-go", "query", "candidates", "--rpc-endpoint", "http://"+e.RPC.Addr, "something") + e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], validatorAddress, validatorAddress) + e.RunWithError(t, "neo-go", "query", "committee", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something") + e.RunWithError(t, "neo-go", "query", "candidates", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something") } diff --git a/cli/wallet/multisig_test.go b/cli/wallet/multisig_test.go index 0eb3a2219..5972c3659 100644 --- a/cli/wallet/multisig_test.go +++ b/cli/wallet/multisig_test.go @@ -55,7 +55,7 @@ func TestSignMultisigTx(t *testing.T) { // Transfer funds to the multisig. e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--from", testcli.ValidatorAddr, "--force", @@ -74,7 +74,7 @@ func TestSignMultisigTx(t *testing.T) { }) e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet1Path, "--from", multisigAddr, "--to", priv.Address(), "--token", "NEO", "--amount", "1", "--out", txPath) @@ -99,7 +99,7 @@ func TestSignMultisigTx(t *testing.T) { // invalid out e.In.WriteString("pass\r") e.RunWithError(t, "neo-go", "wallet", "sign", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet2Path, "--address", multisigAddr, "--in", txPath, "--out", t.TempDir()) @@ -118,7 +118,7 @@ func TestSignMultisigTx(t *testing.T) { }) e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet1Path, "--from", multisigAddr, "--to", priv.Address(), "--token", "NEO", "--amount", "1", "--out", txPath) @@ -152,11 +152,11 @@ func TestSignMultisigTx(t *testing.T) { t.Run("excessive parameters", func(t *testing.T) { e.RunWithError(t, "neo-go", "util", "txdump", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], txPath, "garbage") }) e.Run(t, "neo-go", "util", "txdump", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], txPath) e.CheckTxTestInvokeOutput(t, 11) res := new(result.Invoke) @@ -194,7 +194,7 @@ func TestSignMultisigTx(t *testing.T) { t.Run("sign, save and send", func(t *testing.T) { e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "sign", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet2Path, "--address", multisigAddr, "--in", txPath, "--out", txPath) e.CheckTxPersisted(t) @@ -202,7 +202,7 @@ func TestSignMultisigTx(t *testing.T) { t.Run("double-sign", func(t *testing.T) { e.In.WriteString("pass\r") e.RunWithError(t, "neo-go", "wallet", "sign", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet2Path, "--address", multisigAddr, "--in", txPath, "--out", txPath) }) @@ -217,13 +217,13 @@ func TestSignMultisigTx(t *testing.T) { e.In.WriteString("acc\rpass\rpass\r") e.Run(t, "neo-go", "wallet", "import-deployed", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet1Path, "--wif", simplePriv.WIF(), "--contract", h.StringLE()) e.In.WriteString("pass\r") e.Run(t, "neo-go", "contract", "invokefunction", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet1Path, "--address", multisigHash.StringLE(), // test with scripthash instead of address "--out", txPath, e.Chain.GoverningTokenHash().StringLE(), "transfer", @@ -249,7 +249,7 @@ func TestSignMultisigTx(t *testing.T) { // Contract. e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "sign", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet1Path, "--address", address.Uint160ToString(h), "--in", txPath, "--out", txPath) tx, _ := e.CheckTxPersisted(t) diff --git a/cli/wallet/wallet_test.go b/cli/wallet/wallet_test.go index bfab35d38..27bb4ec98 100644 --- a/cli/wallet/wallet_test.go +++ b/cli/wallet/wallet_test.go @@ -470,17 +470,17 @@ func TestWalletClaimGas(t *testing.T) { t.Run("missing wallet path", func(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "claim", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--address", testcli.TestWalletAccount) }) t.Run("missing address", func(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "claim", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.TestWalletPath) }) t.Run("invalid address", func(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "claim", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.TestWalletPath, "--address", util.Uint160{}.StringLE()) }) @@ -493,14 +493,14 @@ func TestWalletClaimGas(t *testing.T) { t.Run("insufficient funds", func(t *testing.T) { e.In.WriteString("testpass\r") e.RunWithError(t, "neo-go", "wallet", "claim", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount) }) args := []string{ "neo-go", "wallet", "nep17", "multitransfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--from", testcli.ValidatorAddr, "--force", @@ -522,7 +522,7 @@ func TestWalletClaimGas(t *testing.T) { e.In.WriteString("testpass\r") e.Run(t, "neo-go", "wallet", "claim", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, "--force") @@ -570,7 +570,7 @@ func TestWalletImportDeployed(t *testing.T) { t.Run("unknown contract", func(t *testing.T) { e.In.WriteString("acc\rpass\rpass\r") e.RunWithError(t, "neo-go", "wallet", "import-deployed", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--contract", util.Uint160{}.StringLE(), "--wif", priv.WIF()) }) @@ -578,14 +578,14 @@ func TestWalletImportDeployed(t *testing.T) { badH := deployNNSContract(t, e) // wrong contract with no `verify` method e.In.WriteString("acc\rpass\rpass\r") e.RunWithError(t, "neo-go", "wallet", "import-deployed", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--contract", badH.StringLE(), "--wif", priv.WIF()) }) e.In.WriteString("acc\rpass\rpass\r") e.Run(t, "neo-go", "wallet", "import-deployed", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--wif", priv.WIF(), "--name", "my_acc", "--contract", h.StringLE()) @@ -599,7 +599,7 @@ func TestWalletImportDeployed(t *testing.T) { t.Run("re-importing", func(t *testing.T) { e.In.WriteString("acc\rpass\rpass\r") e.RunWithError(t, "neo-go", "wallet", "import-deployed", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--wif", priv.WIF(), "--name", "my_acc", "--contract", h.StringLE()) }) @@ -607,7 +607,7 @@ func TestWalletImportDeployed(t *testing.T) { t.Run("Sign", func(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--from", testcli.ValidatorAddr, "--force", "NEO:"+contractAddr+":10", @@ -619,7 +619,7 @@ func TestWalletImportDeployed(t *testing.T) { e.In.WriteString("pass\r") e.Run(t, "neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--from", contractAddr, "--force", "--to", privTo.Address(), "--token", "NEO", "--amount", "1") @@ -680,7 +680,7 @@ func TestOfflineSigning(t *testing.T) { t.Run("1/1 multisig", func(t *testing.T) { args := []string{"neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", walletPath, "--from", testcli.ValidatorAddr, "--to", w.Accounts[0].Address, @@ -699,15 +699,15 @@ func TestOfflineSigning(t *testing.T) { t.Run("sendtx", func(t *testing.T) { // And it can't be sent. e.RunWithError(t, "neo-go", "util", "sendtx", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], txPath) // Even with too many arguments. e.RunWithError(t, "neo-go", "util", "sendtx", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], txPath, txPath) // Or no arguments at all. e.RunWithError(t, "neo-go", "util", "sendtx", - "--rpc-endpoint", "http://"+e.RPC.Addr) + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0]) }) // But it can be signed with a proper wallet. e.In.WriteString("one\r") @@ -716,7 +716,7 @@ func TestOfflineSigning(t *testing.T) { "--in", txPath, "--out", txPath) // And then anyone can send (even via wallet sign). e.Run(t, "neo-go", "wallet", "sign", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", walletPath, "--address", testcli.ValidatorAddr, "--in", txPath) }) @@ -724,7 +724,7 @@ func TestOfflineSigning(t *testing.T) { t.Run("simple signature", func(t *testing.T) { simpleAddr := w.Accounts[0].Address args := []string{"neo-go", "wallet", "nep17", "transfer", - "--rpc-endpoint", "http://" + e.RPC.Addr, + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", walletPath, "--from", simpleAddr, "--to", testcli.ValidatorAddr, @@ -749,7 +749,7 @@ func TestOfflineSigning(t *testing.T) { e.RunWithError(t, "neo-go", "util", "sendtx", txPath) // But it requires no wallet at all. e.Run(t, "neo-go", "util", "sendtx", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], txPath) }) } diff --git a/config/protocol.mainnet.neofs.yml b/config/protocol.mainnet.neofs.yml index c0eaf0743..ae86c908f 100644 --- a/config/protocol.mainnet.neofs.yml +++ b/config/protocol.mainnet.neofs.yml @@ -47,34 +47,38 @@ ApplicationConfiguration: DataDirectoryPath: "./chains/mainnet.neofs" # BoltDBOptions: # FilePath: "./chains/mainnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 40333 + P2P: + Addresses: + - ":40333" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 100 + AttemptConnPeers: 20 + MinPeers: 5 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 100 - AttemptConnPeers: 20 - MinPeers: 5 Oracle: Enabled: false AllowedContentTypes: - application/json RPC: Enabled: true + Addresses: + - ":40332" MaxGasInvoke: 15 EnableCORSWorkaround: false - Port: 40332 TLSConfig: Enabled: false - Port: 40331 + Addresses: + - ":40331" CertFile: serv.crt KeyFile: serv.key Prometheus: Enabled: false - Port: 2112 + Addresses: + - ":2112" Pprof: Enabled: false - Port: 2113 + Addresses: + - ":2113" diff --git a/config/protocol.mainnet.yml b/config/protocol.mainnet.yml index fcd99245d..58934510a 100644 --- a/config/protocol.mainnet.yml +++ b/config/protocol.mainnet.yml @@ -59,17 +59,17 @@ ApplicationConfiguration: DataDirectoryPath: "./chains/mainnet" # BoltDBOptions: # FilePath: "./chains/mainnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 10333 + P2P: + Addresses: + - ":10333" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 100 + AttemptConnPeers: 20 + MinPeers: 10 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 100 - AttemptConnPeers: 20 - MinPeers: 10 Oracle: Enabled: false AllowedContentTypes: @@ -82,17 +82,21 @@ ApplicationConfiguration: RPC: Enabled: true + Addresses: + - ":10332" MaxGasInvoke: 15 EnableCORSWorkaround: false - Port: 10332 TLSConfig: Enabled: false - Port: 10331 + Addresses: + - ":10331" CertFile: serv.crt KeyFile: serv.key Prometheus: Enabled: true - Port: 2112 + Addresses: + - ":2112" Pprof: Enabled: false - Port: 2113 + Addresses: + - ":2113" diff --git a/config/protocol.privnet.docker.four.yml b/config/protocol.privnet.docker.four.yml index 2a59d1f59..d8d9818ee 100644 --- a/config/protocol.privnet.docker.four.yml +++ b/config/protocol.privnet.docker.four.yml @@ -38,17 +38,17 @@ ApplicationConfiguration: DataDirectoryPath: "/chains/four" # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 20336 + P2P: + Addresses: + - ":20336" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 10 + AttemptConnPeers: 5 + MinPeers: 3 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 10 - AttemptConnPeers: 5 - MinPeers: 3 Oracle: Enabled: false AllowedContentTypes: @@ -69,15 +69,18 @@ ApplicationConfiguration: Password: "pass" RPC: Enabled: true + Addresses: + - ":30336" MaxGasInvoke: 15 EnableCORSWorkaround: false - Port: 30336 Prometheus: Enabled: true - Port: 20004 + Addresses: + - ":20004" Pprof: Enabled: false - Port: 20014 + Addresses: + - ":20014" UnlockWallet: Path: "/wallet4.json" Password: "four" diff --git a/config/protocol.privnet.docker.one.yml b/config/protocol.privnet.docker.one.yml index eba250ec0..36d32f5b1 100644 --- a/config/protocol.privnet.docker.one.yml +++ b/config/protocol.privnet.docker.one.yml @@ -38,17 +38,17 @@ ApplicationConfiguration: DataDirectoryPath: "/chains/one" # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 20333 + P2P: + Addresses: + - ":20333" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 10 + AttemptConnPeers: 5 + MinPeers: 3 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 10 - AttemptConnPeers: 5 - MinPeers: 3 Oracle: Enabled: false AllowedContentTypes: @@ -69,12 +69,14 @@ ApplicationConfiguration: Password: "pass" RPC: Enabled: true + Addresses: + - ":30333" MaxGasInvoke: 15 EnableCORSWorkaround: false - Port: 30333 Prometheus: Enabled: true - Port: 20001 + Addresses: + - ":20001" Pprof: Enabled: false Port: 20011 diff --git a/config/protocol.privnet.docker.single.yml b/config/protocol.privnet.docker.single.yml index 4582b3cc9..7b3ff8883 100644 --- a/config/protocol.privnet.docker.single.yml +++ b/config/protocol.privnet.docker.single.yml @@ -32,17 +32,17 @@ ApplicationConfiguration: DataDirectoryPath: "/chains/single" # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 20333 + P2P: + Addresses: + - ":20333" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 10 + AttemptConnPeers: 5 + MinPeers: 0 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 10 - AttemptConnPeers: 5 - MinPeers: 0 Oracle: Enabled: false AllowedContentTypes: @@ -60,15 +60,18 @@ ApplicationConfiguration: Password: "pass" RPC: Enabled: true + Addresses: + - ":30333" EnableCORSWorkaround: false MaxGasInvoke: 15 - Port: 30333 Prometheus: Enabled: true - Port: 20001 + Addresses: + - ":20001" Pprof: Enabled: false - Port: 20011 + Addresses: + - ":20011" UnlockWallet: Path: "/wallet1.json" Password: "one" diff --git a/config/protocol.privnet.docker.three.yml b/config/protocol.privnet.docker.three.yml index 7455b6932..a8020fcaa 100644 --- a/config/protocol.privnet.docker.three.yml +++ b/config/protocol.privnet.docker.three.yml @@ -38,17 +38,17 @@ ApplicationConfiguration: DataDirectoryPath: "/chains/three" # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 20335 + P2P: + Addresses: + - ":20335" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 10 + AttemptConnPeers: 5 + MinPeers: 3 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 10 - AttemptConnPeers: 5 - MinPeers: 3 Oracle: Enabled: false AllowedContentTypes: @@ -69,15 +69,18 @@ ApplicationConfiguration: Password: "pass" RPC: Enabled: true + Addresses: + - ":30335" MaxGasInvoke: 15 EnableCORSWorkaround: false - Port: 30335 Prometheus: Enabled: true - Port: 20003 + Addresses: + - ":20003" Pprof: Enabled: false - Port: 20013 + Addresses: + - ":20013" UnlockWallet: Path: "/wallet3.json" Password: "three" diff --git a/config/protocol.privnet.docker.two.yml b/config/protocol.privnet.docker.two.yml index ffc2ffaf3..c49b475f2 100644 --- a/config/protocol.privnet.docker.two.yml +++ b/config/protocol.privnet.docker.two.yml @@ -38,17 +38,17 @@ ApplicationConfiguration: DataDirectoryPath: "/chains/two" # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 20334 + P2P: + Addresses: + - ":20334" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 10 + AttemptConnPeers: 5 + MinPeers: 3 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 10 - AttemptConnPeers: 5 - MinPeers: 3 Oracle: Enabled: false AllowedContentTypes: @@ -71,13 +71,16 @@ ApplicationConfiguration: Enabled: true MaxGasInvoke: 15 EnableCORSWorkaround: false - Port: 30334 + Addresses: + - ":30334" Prometheus: Enabled: true - Port: 20002 + Addresses: + - ":20002" Pprof: Enabled: false - Port: 20012 + Addresses: + - ":20012" UnlockWallet: Path: "/wallet2.json" Password: "two" diff --git a/config/protocol.privnet.yml b/config/protocol.privnet.yml index c4fe64fec..b9af738c5 100644 --- a/config/protocol.privnet.yml +++ b/config/protocol.privnet.yml @@ -38,17 +38,17 @@ ApplicationConfiguration: DataDirectoryPath: "./chains/privnet" # BoltDBOptions: # FilePath: "./chains/privnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 20332 + P2P: + Addresses: + - ":20332" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 10 + AttemptConnPeers: 5 + MinPeers: 3 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 10 - AttemptConnPeers: 5 - MinPeers: 3 P2PNotary: Enabled: false UnlockWallet: @@ -56,19 +56,23 @@ ApplicationConfiguration: Password: "pass" RPC: Enabled: true + Addresses: + - ":20331" MaxGasInvoke: 15 EnableCORSWorkaround: false - Port: 20331 SessionEnabled: true SessionExpirationTime: 180 # higher expiration time for manual requests and tests. TLSConfig: Enabled: false - Port: 20330 + Addresses: + - ":20330" CertFile: serv.crt KeyFile: serv.key Prometheus: Enabled: true - Port: 2112 + Addresses: + - ":2112" Pprof: Enabled: false - Port: 2113 + Addresses: + - ":2113" diff --git a/config/protocol.testnet.neofs.yml b/config/protocol.testnet.neofs.yml index 90a222897..e1573bf7b 100644 --- a/config/protocol.testnet.neofs.yml +++ b/config/protocol.testnet.neofs.yml @@ -47,32 +47,34 @@ ApplicationConfiguration: DataDirectoryPath: "./chains/testnet.neofs" # BoltDBOptions: # FilePath: "./chains/testnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 50333 + P2P: + Addresses: + - ":50333" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 100 + AttemptConnPeers: 20 + MinPeers: 5 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 100 - AttemptConnPeers: 20 - MinPeers: 5 Oracle: Enabled: false AllowedContentTypes: - application/json RPC: Enabled: true + Addresses: + - ":50332" EnableCORSWorkaround: false MaxGasInvoke: 100 - Port: 50332 StartWhenSynchronized: false TLSConfig: Enabled: false + Addresses: + - ":50331" CertFile: server.crt KeyFile: server.key - Port: 50331 P2PNotary: Enabled: false UnlockWallet: @@ -80,7 +82,9 @@ ApplicationConfiguration: Path: "/notary_wallet.json" Prometheus: Enabled: false - Port: 2112 + Addresses: + - ":2112" Pprof: Enabled: false - Port: 2113 + Addresses: + - ":2113" diff --git a/config/protocol.testnet.yml b/config/protocol.testnet.yml index 259bab32c..7134ffbfd 100644 --- a/config/protocol.testnet.yml +++ b/config/protocol.testnet.yml @@ -62,17 +62,17 @@ ApplicationConfiguration: DataDirectoryPath: "./chains/testnet" # BoltDBOptions: # FilePath: "./chains/testnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 20333 + P2P: + Addresses: + - ":20333" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 100 + AttemptConnPeers: 20 + MinPeers: 10 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 100 - AttemptConnPeers: 20 - MinPeers: 10 Oracle: Enabled: false AllowedContentTypes: @@ -84,17 +84,21 @@ ApplicationConfiguration: Password: "pass" RPC: Enabled: true + Addresses: + - ":20332" MaxGasInvoke: 15 EnableCORSWorkaround: false - Port: 20332 TLSConfig: Enabled: false - Port: 20331 + Addresses: + - ":20331" CertFile: serv.crt KeyFile: serv.key Prometheus: Enabled: true - Port: 2112 + Addresses: + - ":2112" Pprof: Enabled: false - Port: 2113 + Addresses: + - ":2113" diff --git a/config/protocol.unit_testnet.single.yml b/config/protocol.unit_testnet.single.yml index 0f53039c0..298cce8e7 100644 --- a/config/protocol.unit_testnet.single.yml +++ b/config/protocol.unit_testnet.single.yml @@ -33,17 +33,17 @@ ApplicationConfiguration: # DataDirectoryPath: "./chains/unit_testnet" # BoltDBOptions: # FilePath: "./chains/unit_testnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 0 + P2P: + Addresses: + - ":0" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MinPeers: 0 + MaxPeers: 10 + AttemptConnPeers: 5 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MinPeers: 0 - MaxPeers: 10 - AttemptConnPeers: 5 UnlockWallet: Path: "../testdata/wallet1_solo.json" Password: "one" @@ -53,14 +53,16 @@ ApplicationConfiguration: Path: "/notary_wallet.json" Password: "pass" RPC: - Address: localhost MaxGasInvoke: 15 Enabled: true + Addresses: + - "localhost:0" # let the system choose port dynamically EnableCORSWorkaround: false - Port: 0 # let the system choose port dynamically Prometheus: Enabled: false #since it's not useful for unit tests. - Port: 2112 + Addresses: + - ":2112" Pprof: Enabled: false #since it's not useful for unit tests. - Port: 2113 + Addresses: + - ":2113" diff --git a/config/protocol.unit_testnet.yml b/config/protocol.unit_testnet.yml index 6b2eff506..eac9858ea 100644 --- a/config/protocol.unit_testnet.yml +++ b/config/protocol.unit_testnet.yml @@ -42,33 +42,35 @@ ApplicationConfiguration: # DataDirectoryPath: "./chains/unit_testnet" # BoltDBOptions: # FilePath: "./chains/unit_testnet.bolt" - # Uncomment in order to set up custom address for node. - # Address: localhost - NodePort: 20333 + P2P: + Addresses: + - ":20333" # in form of "[host]:[port][:announcedPort]" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 50 + AttemptConnPeers: 5 + MinPeers: 0 Relay: true - DialTimeout: 3 - ProtoTickInterval: 2 - PingInterval: 30 - PingTimeout: 90 - MaxPeers: 50 - AttemptConnPeers: 5 - MinPeers: 0 P2PNotary: Enabled: false UnlockWallet: Path: "/notary_wallet.json" Password: "pass" RPC: - Address: localhost MaxGasInvoke: 15 Enabled: true + Addresses: + - "localhost:0" # let the system choose port dynamically EnableCORSWorkaround: false SessionEnabled: true SessionExpirationTime: 2 # enough for tests as they run locally. - Port: 0 # let the system choose port dynamically Prometheus: Enabled: false #since it's not useful for unit tests. - Port: 2112 + Addresses: + - ":2112" Pprof: Enabled: false #since it's not useful for unit tests. - Port: 2113 + Addresses: + - ":2113" diff --git a/docs/node-configuration.md b/docs/node-configuration.md index 9255fe76a..5778548fc 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -16,30 +16,77 @@ node-related settings described in the table below. | Section | Type | Default value | Description | | --- | --- | --- | --- | -| Address | `string` | `0.0.0.0` | Node address that P2P protocol handler binds to. | -| AnnouncedPort | `uint16` | Same as `NodePort` | Node port which should be used to announce node's port on P2P layer, it can differ from the `NodePort` the node is bound to (for example, if your node is behind NAT). | -| AttemptConnPeers | `int` | `20` | Number of connection to try to establish when the connection count drops below the `MinPeers` value.| -| BroadcastFactor | `int` | `0` | Multiplier that is used to determine the number of optimal gossip fan-out peer number for broadcasted messages (0-100). By default it's zero, node uses the most optimized value depending on the estimated network size (`2.5×log(size)`), so the node may have 20 peers and calculate that it needs to broadcast messages to just 10 of them. With BroadcastFactor set to 100 it will always send messages to all peers, any value in-between 0 and 100 is used for weighted calculation, for example if it's 30 then 13 neighbors will be used in the previous case. | +| Address | `string` | `0.0.0.0` | Node address that P2P protocol handler binds to. Warning: this field is deprecated, please, use `Addresses` instead. | +| AnnouncedPort | `uint16` | Same as `NodePort` | Node port which should be used to announce node's port on P2P layer, it can differ from the `NodePort` the node is bound to (for example, if your node is behind NAT). Warning: this field is deprecated, please, use `Addresses` instead. | +| AttemptConnPeers | `int` | `20` | Number of connection to try to establish when the connection count drops below the `MinPeers` value. Warning: this field is deprecated and moved to `P2P` section. | +| BroadcastFactor | `int` | `0` | Multiplier that is used to determine the number of optimal gossip fan-out peer number for broadcasted messages (0-100). By default it's zero, node uses the most optimized value depending on the estimated network size (`2.5×log(size)`), so the node may have 20 peers and calculate that it needs to broadcast messages to just 10 of them. With BroadcastFactor set to 100 it will always send messages to all peers, any value in-between 0 and 100 is used for weighted calculation, for example if it's 30 then 13 neighbors will be used in the previous case. Warning: this field is deprecated and moved to `P2P` section. | | DBConfiguration | [DB Configuration](#DB-Configuration) | | Describes configuration for database. See the [DB Configuration](#DB-Configuration) section for details. | -| DialTimeout | `int64` | `0` | Maximum duration a single dial may take in seconds. | -| ExtensiblePoolSize | `int` | `20` | Maximum amount of the extensible payloads from a single sender stored in a local pool. | +| DialTimeout | `int64` | `0` | Maximum duration a single dial may take in seconds. Warning: this field is deprecated and moved to `P2P` section. | +| ExtensiblePoolSize | `int` | `20` | Maximum amount of the extensible payloads from a single sender stored in a local pool. Warning: this field is deprecated and moved to `P2P` section. | | LogLevel | `string` | "info" | Minimal logged messages level (can be "debug", "info", "warn", "error", "dpanic", "panic" or "fatal"). | | LogPath | `string` | "", so only console logging | File path where to store node logs. | -| MaxPeers | `int` | `100` | Maximum numbers of peers that can be connected to the server. | -| MinPeers | `int` | `5` | Minimum number of peers for normal operation; when the node has less than this number of peers it tries to connect with some new ones. | -| NodePort | `uint16` | `0`, which is any free port | The actual node port it is bound to. | +| MaxPeers | `int` | `100` | Maximum numbers of peers that can be connected to the server. Warning: this field is deprecated and moved to `P2P` section. | +| MinPeers | `int` | `5` | Minimum number of peers for normal operation; when the node has less than this number of peers it tries to connect with some new ones. Warning: this field is deprecated and moved to `P2P` section. | +| NodePort | `uint16` | `0`, which is any free port | The actual node port it is bound to. Warning: this field is deprecated, please, use `Addresses` instead. | | Oracle | [Oracle Configuration](#Oracle-Configuration) | | Oracle module configuration. See the [Oracle Configuration](#Oracle-Configuration) section for details. | +| P2P | [P2P Configuration](#P2P-Configuration) | | Configuration values for P2P network interaction. See the [P2P Configuration](#P2P-Configuration) section for details. | | P2PNotary | [P2P Notary Configuration](#P2P-Notary-Configuration) | | P2P Notary module configuration. See the [P2P Notary Configuration](#P2P-Notary-Configuration) section for details. | -| PingInterval | `int64` | `30` | Interval in seconds used in pinging mechanism for syncing blocks. | -| PingTimeout | `int64` | `90` | Time to wait for pong (response for sent ping request). | +| PingInterval | `int64` | `30` | Interval in seconds used in pinging mechanism for syncing blocks. Warning: this field is deprecated and moved to `P2P` section. | +| PingTimeout | `int64` | `90` | Time to wait for pong (response for sent ping request). Warning: this field is deprecated and moved to `P2P` section. | | Pprof | [Metrics Services Configuration](#Metrics-Services-Configuration) | | Configuration for pprof service (profiling statistics gathering). See the [Metrics Services Configuration](#Metrics-Services-Configuration) section for details. | | Prometheus | [Metrics Services Configuration](#Metrics-Services-Configuration) | | Configuration for Prometheus (monitoring system). See the [Metrics Services Configuration](#Metrics-Services-Configuration) section for details | -| ProtoTickInterval | `int64` | `5` | Duration in seconds between protocol ticks with each connected peer. | +| ProtoTickInterval | `int64` | `5` | Duration in seconds between protocol ticks with each connected peer. Warning: this field is deprecated and moved to `P2P` section. | | Relay | `bool` | `true` | Determines whether the server is forwarding its inventory. | | RPC | [RPC Configuration](#RPC-Configuration) | | Describes [RPC subsystem](rpc.md) configuration. See the [RPC Configuration](#RPC-Configuration) for details. | | StateRoot | [State Root Configuration](#State-Root-Configuration) | | State root module configuration. See the [State Root Configuration](#State-Root-Configuration) section for details. | | UnlockWallet | [Unlock Wallet Configuration](#Unlock-Wallet-Configuration) | | Node wallet configuration used for consensus (dBFT) operation. See the [Unlock Wallet Configuration](#Unlock-Wallet-Configuration) section for details. | +### P2P Configuration + +`P2P` section contains configuration for peer-to-peer node communications and has +the following format: +``` +P2P: + Addresses: + - "0.0.0.0:0" # any free port on all available addresses (in form of "[host]:[port][:announcedPort]") + AttemptConnPeers: 20 + BroadcastFactor: 0 + DialTimeout: 0s + MaxPeers: 100 + MinPeers: 5 + PingInterval: 30s + PingTimeout: 90s + ProtoTickInterval: 5s + ExtensiblePoolSize: 20 +``` +where: +- `Addresses` (`[]string`) is the list of the node addresses that P2P protocol + handler binds to. Each address has the form of `[address]:[nodePort][:announcedPort]` + where `address` is the address itself, `nodePort` is the actual P2P port node listens at; + `announcedPort` is the node port which should be used to announce node's port on P2P layer, + it can differ from the `nodePort` the node is bound to if specified (for example, if your + node is behind NAT). +- `AttemptConnPeers` (`int`) is the number of connection to try to establish when the + connection count drops below the `MinPeers` value. +- `BroadcastFactor` (`int`) is the multiplier that is used to determine the number of + optimal gossip fan-out peer number for broadcasted messages (0-100). By default, it's + zero, node uses the most optimized value depending on the estimated network size + (`2.5×log(size)`), so the node may have 20 peers and calculate that it needs to broadcast + messages to just 10 of them. With BroadcastFactor set to 100 it will always send messages + to all peers, any value in-between 0 and 100 is used for weighted calculation, for example + if it's 30 then 13 neighbors will be used in the previous case. +- `DialTimeout` (`Duration`) is the maximum duration a single dial may take. +- `ExtensiblePoolSize` (`int`) is the maximum amount of the extensible payloads from a single + sender stored in a local pool. +- `MaxPeers` (`int`) is the maximum numbers of peers that can be connected to the server. +- `MinPeers` (`int`) is the minimum number of peers for normal operation; when the node has + less than this number of peers it tries to connect with some new ones. +- `PingInterval` (`Duration`) is the interval used in pinging mechanism for syncing + blocks. +- `PingTimeout` (`Duration`) is the time to wait for pong (response for sent ping request). +- `ProtoTickInterval` (`Duration`) is the duration between protocol ticks with each + connected peer. + ### DB Configuration `DBConfiguration` section describes configuration for node database and has @@ -118,17 +165,21 @@ Prometheus) and has the following structure: ``` Pprof: Enabled: false - Address: "" - Port: "30001" + Addresses: + - ":30001" Prometheus: Enabled: false - Address: "" - Port: "40001" + Addresses: + - ":40001" ``` where: - `Enabled` denotes whether the service is enabled. -- `Address` is a service address to be running at. -- `Port` is a service port to be bound to. +- `Address` is a service address to be running at. Warning: this field is deprecated, + please, use `Addresses` instead. +- `Port` is a service port to be bound to. Warning: this field is deprecated, please, + use `Addresses` instead. +- `Addresses` is a list of service addresses to be running at and listen to in + the form of "host:port". ### RPC Configuration @@ -137,29 +188,32 @@ the following structure: ``` RPC: Enabled: true - Address: "" + Addresses: + - ":10332" EnableCORSWorkaround: false MaxGasInvoke: 50 MaxIteratorResultItems: 100 MaxFindResultItems: 100 MaxNEP11Tokens: 100 MaxWebSocketClients: 64 - Port: 10332 SessionEnabled: false SessionExpirationTime: 15 SessionBackedByMPT: false SessionPoolSize: 20 StartWhenSynchronized: false TLSConfig: - Address: "" + Addresses: + - ":10331" CertFile: serv.crt Enabled: true - Port: 10331 KeyFile: serv.key ``` where: - `Enabled` denotes whether an RPC server should be started. -- `Address` is an RPC server address to be running at. +- `Address` is an RPC server address to be running at. Warning: this field is + deprecated, please, use `Addresses` instead. +- `Addresses` is a list of RPC server addresses to be running at and listen to in + the form of "host:port". - `EnableCORSWorkaround` turns on a set of origin-related behaviors that make RPC server wide open for connections from any origins. It enables OPTIONS request handling for pre-flight CORS and makes the server send @@ -182,7 +236,8 @@ where: number (64 by default). Attempts to establish additional connections will lead to websocket handshake failures. Use "-1" to disable websocket connections (0 will lead to using the default value). -- `Port` is an RPC server port it should be bound to. +- `Port` is an RPC server port it should be bound to. Warning: this field is + deprecated, please, use `Addresses` instead. - `SessionEnabled` denotes whether session-based iterator JSON-RPC API is enabled. If true, then all iterators got from `invoke*` calls will be stored as sessions on the server side available for further traverse. `traverseiterator` and diff --git a/internal/testcli/executor.go b/internal/testcli/executor.go index c8f731d18..568146306 100644 --- a/internal/testcli/executor.go +++ b/internal/testcli/executor.go @@ -146,7 +146,8 @@ func NewTestChain(t *testing.T, f func(*config.Config), run bool) (*core.Blockch go chain.Run() } - serverConfig := network.NewServerConfig(cfg) + serverConfig, err := network.NewServerConfig(cfg) + require.NoError(t, err) serverConfig.UserAgent = fmt.Sprintf(config.UserAgentFormat, "0.98.3-test") netSrv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), zap.NewNop()) require.NoError(t, err) @@ -348,7 +349,7 @@ func DeployContract(t *testing.T, e *Executor, inPath, configPath, wallet, addre "--out", nefName, "--manifest", manifestName) e.In.WriteString(pass + "\r") e.Run(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--wallet", wallet, "--address", address, "--force", "--in", nefName, "--manifest", manifestName) diff --git a/internal/testserdes/testing.go b/internal/testserdes/testing.go index 5bc7ad6d5..3cec4801e 100644 --- a/internal/testserdes/testing.go +++ b/internal/testserdes/testing.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) // MarshalUnmarshalJSON checks if the expected stays the same after @@ -18,6 +19,15 @@ func MarshalUnmarshalJSON(t *testing.T, expected, actual interface{}) { require.Equal(t, expected, actual) } +// MarshalUnmarshalYAML checks if the expected stays the same after +// marshal/unmarshal via YAML. +func MarshalUnmarshalYAML(t *testing.T, expected, actual interface{}) { + data, err := yaml.Marshal(expected) + require.NoError(t, err) + require.NoError(t, yaml.Unmarshal(data, actual)) + require.Equal(t, expected, actual) +} + // EncodeDecodeBinary checks if the expected stays the same after // serializing/deserializing via io.Serializable methods. func EncodeDecodeBinary(t *testing.T, expected, actual io.Serializable) { diff --git a/pkg/config/application_config.go b/pkg/config/application_config.go index 524d00ec4..8cf5a2756 100644 --- a/pkg/config/application_config.go +++ b/pkg/config/application_config.go @@ -1,35 +1,56 @@ package config import ( + "fmt" + "net" + "sort" + "strconv" + "strings" + "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" ) // ApplicationConfiguration config specific to the node. type ApplicationConfiguration struct { - Address string `yaml:"Address"` - AnnouncedNodePort uint16 `yaml:"AnnouncedPort"` - AttemptConnPeers int `yaml:"AttemptConnPeers"` + // Deprecated: please, use Addresses field of P2P section instead, this field will be removed in future versions. + Address *string `yaml:"Address,omitempty"` + // Deprecated: please, use Addresses field of P2P section instead, this field will be removed in future versions. + AnnouncedNodePort *uint16 `yaml:"AnnouncedPort,omitempty"` + // Deprecated: this option is moved to the P2P section. + AttemptConnPeers int `yaml:"AttemptConnPeers"` // BroadcastFactor is the factor (0-100) controlling gossip fan-out number optimization. - BroadcastFactor int `yaml:"BroadcastFactor"` - DBConfiguration dbconfig.DBConfiguration `yaml:"DBConfiguration"` - DialTimeout int64 `yaml:"DialTimeout"` - LogLevel string `yaml:"LogLevel"` - LogPath string `yaml:"LogPath"` - MaxPeers int `yaml:"MaxPeers"` - MinPeers int `yaml:"MinPeers"` - NodePort uint16 `yaml:"NodePort"` - PingInterval int64 `yaml:"PingInterval"` - PingTimeout int64 `yaml:"PingTimeout"` - Pprof BasicService `yaml:"Pprof"` - Prometheus BasicService `yaml:"Prometheus"` - ProtoTickInterval int64 `yaml:"ProtoTickInterval"` - Relay bool `yaml:"Relay"` - RPC RPC `yaml:"RPC"` - UnlockWallet Wallet `yaml:"UnlockWallet"` - Oracle OracleConfiguration `yaml:"Oracle"` - P2PNotary P2PNotary `yaml:"P2PNotary"` - StateRoot StateRoot `yaml:"StateRoot"` + // + // Deprecated: this option is moved to the P2P section. + BroadcastFactor int `yaml:"BroadcastFactor"` + DBConfiguration dbconfig.DBConfiguration `yaml:"DBConfiguration"` + // Deprecated: this option is moved to the P2P section. + DialTimeout int64 `yaml:"DialTimeout"` + LogLevel string `yaml:"LogLevel"` + LogPath string `yaml:"LogPath"` + // Deprecated: this option is moved to the P2P section. + MaxPeers int `yaml:"MaxPeers"` + // Deprecated: this option is moved to the P2P section. + MinPeers int `yaml:"MinPeers"` + // Deprecated: please, use Addresses field of P2P section instead, this field will be removed in future versions. + NodePort *uint16 `yaml:"NodePort,omitempty"` + P2P P2P `yaml:"P2P"` + // Deprecated: this option is moved to the P2P section. + PingInterval int64 `yaml:"PingInterval"` + // Deprecated: this option is moved to the P2P section. + PingTimeout int64 `yaml:"PingTimeout"` + Pprof BasicService `yaml:"Pprof"` + Prometheus BasicService `yaml:"Prometheus"` + // Deprecated: this option is moved to the P2P section. + ProtoTickInterval int64 `yaml:"ProtoTickInterval"` + Relay bool `yaml:"Relay"` + RPC RPC `yaml:"RPC"` + UnlockWallet Wallet `yaml:"UnlockWallet"` + Oracle OracleConfiguration `yaml:"Oracle"` + P2PNotary P2PNotary `yaml:"P2PNotary"` + StateRoot StateRoot `yaml:"StateRoot"` // ExtensiblePoolSize is the maximum amount of the extensible payloads from a single sender. + // + // Deprecated: this option is moved to the P2P section. ExtensiblePoolSize int `yaml:"ExtensiblePoolSize"` } @@ -37,22 +58,143 @@ type ApplicationConfiguration struct { // (Oracle, P2PNotary, Pprof, Prometheus, RPC, StateRoot and UnlockWallet sections) // and LogLevel field. func (a *ApplicationConfiguration) EqualsButServices(o *ApplicationConfiguration) bool { - if a.Address != o.Address || - a.AnnouncedNodePort != o.AnnouncedNodePort || - a.AttemptConnPeers != o.AttemptConnPeers || - a.BroadcastFactor != o.BroadcastFactor || + if len(a.P2P.Addresses) != len(o.P2P.Addresses) { + return false + } + aCp := make([]string, len(a.P2P.Addresses)) + oCp := make([]string, len(o.P2P.Addresses)) + copy(aCp, a.P2P.Addresses) + copy(oCp, o.P2P.Addresses) + sort.Strings(aCp) + sort.Strings(oCp) + for i := range aCp { + if aCp[i] != oCp[i] { + return false + } + } + if a.Address != o.Address || //nolint:staticcheck // SA1019: a.Address is deprecated + a.AnnouncedNodePort != o.AnnouncedNodePort || //nolint:staticcheck // SA1019: a.AnnouncedNodePort is deprecated + a.AttemptConnPeers != o.AttemptConnPeers || //nolint:staticcheck // SA1019: a.AttemptConnPeers is deprecated + a.P2P.AttemptConnPeers != o.P2P.AttemptConnPeers || + a.BroadcastFactor != o.BroadcastFactor || //nolint:staticcheck // SA1019: a.BroadcastFactor is deprecated + a.P2P.BroadcastFactor != o.P2P.BroadcastFactor || a.DBConfiguration != o.DBConfiguration || - a.DialTimeout != o.DialTimeout || - a.ExtensiblePoolSize != o.ExtensiblePoolSize || + a.DialTimeout != o.DialTimeout || //nolint:staticcheck // SA1019: a.DialTimeout is deprecated + a.P2P.DialTimeout != o.P2P.DialTimeout || + a.ExtensiblePoolSize != o.ExtensiblePoolSize || //nolint:staticcheck // SA1019: a.ExtensiblePoolSize is deprecated + a.P2P.ExtensiblePoolSize != o.P2P.ExtensiblePoolSize || a.LogPath != o.LogPath || - a.MaxPeers != o.MaxPeers || - a.MinPeers != o.MinPeers || - a.NodePort != o.NodePort || - a.PingInterval != o.PingInterval || - a.PingTimeout != o.PingTimeout || - a.ProtoTickInterval != o.ProtoTickInterval || + a.MaxPeers != o.MaxPeers || //nolint:staticcheck // SA1019: a.MaxPeers is deprecated + a.P2P.MaxPeers != o.P2P.MaxPeers || + a.MinPeers != o.MinPeers || //nolint:staticcheck // SA1019: a.MinPeers is deprecated + a.P2P.MinPeers != o.P2P.MinPeers || + a.NodePort != o.NodePort || //nolint:staticcheck // SA1019: a.NodePort is deprecated + a.PingInterval != o.PingInterval || //nolint:staticcheck // SA1019: a.PingInterval is deprecated + a.P2P.PingInterval != o.P2P.PingInterval || + a.PingTimeout != o.PingTimeout || //nolint:staticcheck // SA1019: a.PingTimeout is deprecated + a.P2P.PingTimeout != o.P2P.PingTimeout || + a.ProtoTickInterval != o.ProtoTickInterval || //nolint:staticcheck // SA1019: a.ProtoTickInterval is deprecated + a.P2P.ProtoTickInterval != o.P2P.ProtoTickInterval || a.Relay != o.Relay { return false } return true } + +// AnnounceableAddress is a pair of node address in the form of "[host]:[port]" +// with optional corresponding announced port to be used in version exchange. +type AnnounceableAddress struct { + Address string + AnnouncedPort uint16 +} + +// GetAddresses parses returns the list of AnnounceableAddress containing information +// gathered from both deprecated Address / NodePort / AnnouncedNodePort and newly +// created Addresses fields. +func (a *ApplicationConfiguration) GetAddresses() ([]AnnounceableAddress, error) { + addrs := make([]AnnounceableAddress, 0, len(a.P2P.Addresses)+1) + if a.Address != nil || a.NodePort != nil || a.AnnouncedNodePort != nil { //nolint:staticcheck // SA1019: a.Address is deprecated + var ( + host string + nodePort uint16 + ) + if a.Address != nil { //nolint:staticcheck // SA1019: a.Address is deprecated + host = *a.Address //nolint:staticcheck // SA1019: a.Address is deprecated + } + if a.NodePort != nil { //nolint:staticcheck // SA1019: a.NodePort is deprecated + nodePort = *a.NodePort //nolint:staticcheck // SA1019: a.NodePort is deprecated + } + addr := AnnounceableAddress{Address: net.JoinHostPort(host, strconv.Itoa(int(nodePort)))} + if a.AnnouncedNodePort != nil { //nolint:staticcheck // SA1019: a.AnnouncedNodePort is deprecated + addr.AnnouncedPort = *a.AnnouncedNodePort //nolint:staticcheck // SA1019: a.AnnouncedNodePort is deprecated + } + addrs = append(addrs, addr) + } + for i, addrStr := range a.P2P.Addresses { + if len(addrStr) == 0 { + return nil, fmt.Errorf("address #%d is empty", i) + } + lastCln := strings.LastIndex(addrStr, ":") + if lastCln == -1 { + addrs = append(addrs, AnnounceableAddress{ + Address: addrStr, // Plain IPv4 address without port. + }) + continue + } + lastPort, err := strconv.ParseUint(addrStr[lastCln+1:], 10, 16) + if err != nil { + addrs = append(addrs, AnnounceableAddress{ + Address: addrStr, // Still may be a valid IPv4 of the form "X.Y.Z.Q:" or plain IPv6 "A:B::", keep it. + }) + continue + } + penultimateCln := strings.LastIndex(addrStr[:lastCln], ":") + if penultimateCln == -1 { + addrs = append(addrs, AnnounceableAddress{ + Address: addrStr, // IPv4 address with port "X.Y.Z.Q:123" + }) + continue + } + isV6 := strings.Count(addrStr, ":") > 2 + hasBracket := strings.Contains(addrStr, "]") + if penultimateCln == lastCln-1 { + if isV6 && !hasBracket { + addrs = append(addrs, AnnounceableAddress{ + Address: addrStr, // Plain IPv6 of the form "A:B::123" + }) + } else { + addrs = append(addrs, AnnounceableAddress{ + Address: addrStr[:lastCln], // IPv4 with empty port and non-empty announced port "X.Y.Z.Q::123" or IPv6 with non-empty announced port "[A:B::]::123". + AnnouncedPort: uint16(lastPort), + }) + } + continue + } + _, err = strconv.ParseUint(addrStr[penultimateCln+1:lastCln], 10, 16) + if err != nil { + if isV6 { + addrs = append(addrs, AnnounceableAddress{ + Address: addrStr, // Still may be a valid plain IPv6 of the form "A::B:123" or IPv6 with single port [A:B::]:123, keep it. + }) + continue + } + return nil, fmt.Errorf("failed to parse port from %s: %w", addrStr, err) // Some garbage. + } + if isV6 && !hasBracket { + addrs = append(addrs, AnnounceableAddress{ + Address: addrStr, // Plain IPv6 of the form "A::1:1" + }) + } else { + addrs = append(addrs, AnnounceableAddress{ + Address: addrStr[:lastCln], // IPv4 with both ports or IPv6 with both ports specified. + AnnouncedPort: uint16(lastPort), + }) + } + } + if len(addrs) == 0 { + addrs = append(addrs, AnnounceableAddress{ + Address: ":0", + }) + } + return addrs, nil +} diff --git a/pkg/config/application_config_test.go b/pkg/config/application_config_test.go index 8b5428e8f..ed3b9e8b6 100644 --- a/pkg/config/application_config_test.go +++ b/pkg/config/application_config_test.go @@ -20,3 +20,168 @@ func TestApplicationConfigurationEquals(t *testing.T) { require.NoError(t, err) require.False(t, cfg1.ApplicationConfiguration.EqualsButServices(&cfg2.ApplicationConfiguration)) } + +func TestGetAddresses(t *testing.T) { + type testcase struct { + cfg *ApplicationConfiguration + expected []AnnounceableAddress + shouldFail bool + } + addr1 := "1.2.3.4" + addr2 := "5.6.7.8" + addr3 := "4.3.2.1" + v6Plain0 := "3731:54:65fe:2::a7" + v6Plain1 := "3731:54:65fe:2::1" + v6Plain2 := "3731:54:65fe::a:1" + v6Plain3 := "3731:54:65fe::1:1" + v6Plain4 := "3731:54:65fe:2::" + port1 := uint16(1) + port2 := uint16(2) + port3 := uint16(3) + cases := []testcase{ + // Compatibility with the old behaviour. + { + cfg: &ApplicationConfiguration{}, + expected: []AnnounceableAddress{{Address: ":0"}}, + }, + { + cfg: &ApplicationConfiguration{Address: &addr1}, + expected: []AnnounceableAddress{{Address: addr1 + ":0"}}, + }, + { + cfg: &ApplicationConfiguration{NodePort: &port1}, + expected: []AnnounceableAddress{{Address: ":1"}}, + }, + { + cfg: &ApplicationConfiguration{AnnouncedNodePort: &port1}, + expected: []AnnounceableAddress{{Address: ":0", AnnouncedPort: port1}}, + }, + { + cfg: &ApplicationConfiguration{Address: &addr1, NodePort: &port1}, + expected: []AnnounceableAddress{{Address: addr1 + ":1"}}, + }, + { + cfg: &ApplicationConfiguration{Address: &addr1, AnnouncedNodePort: &port1}, + expected: []AnnounceableAddress{{Address: addr1 + ":0", AnnouncedPort: port1}}, + }, + { + cfg: &ApplicationConfiguration{NodePort: &port1, AnnouncedNodePort: &port2}, + expected: []AnnounceableAddress{{Address: ":1", AnnouncedPort: port2}}, + }, + { + cfg: &ApplicationConfiguration{NodePort: &port1, AnnouncedNodePort: &port2}, + expected: []AnnounceableAddress{{Address: ":1", AnnouncedPort: port2}}, + }, + { + cfg: &ApplicationConfiguration{Address: &addr1, NodePort: &port1, AnnouncedNodePort: &port2}, + expected: []AnnounceableAddress{{Address: addr1 + ":1", AnnouncedPort: port2}}, + }, + // Compatibility with new multi-addresses. + { + cfg: &ApplicationConfiguration{ + Address: &addr1, NodePort: &port1, AnnouncedNodePort: &port2, + P2P: P2P{Addresses: []string{addr1, addr2 + ":3", addr3 + ":1:3"}}, + }, + expected: []AnnounceableAddress{ + {Address: addr1 + ":1", AnnouncedPort: port2}, + {Address: addr1}, + {Address: addr2 + ":3"}, + {Address: addr3 + ":1", AnnouncedPort: port3}, + }, + }, + // Multi-addresses checks. + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{addr1}}, + }, + expected: []AnnounceableAddress{ + {Address: addr1}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{":1"}}, + }, + expected: []AnnounceableAddress{ + {Address: ":1"}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{"::1"}}, + }, + expected: []AnnounceableAddress{ + {Address: ":", AnnouncedPort: port1}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{addr1 + ":1"}}, + }, + expected: []AnnounceableAddress{ + {Address: addr1 + ":1"}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{addr1 + "::1"}}, + }, + expected: []AnnounceableAddress{ + {Address: addr1 + ":", AnnouncedPort: port1}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{addr1 + ":1:2"}}, + }, + expected: []AnnounceableAddress{ + {Address: addr1 + ":1", AnnouncedPort: port2}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{addr1 + ":1", addr2 + "::2"}}, + }, + expected: []AnnounceableAddress{ + {Address: addr1 + ":1"}, + {Address: addr2 + ":", AnnouncedPort: port2}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{v6Plain0, v6Plain1, v6Plain2, v6Plain3, v6Plain4}}, + }, + expected: []AnnounceableAddress{ + {Address: v6Plain0}, + {Address: v6Plain1}, + {Address: v6Plain2}, + {Address: v6Plain3}, + {Address: v6Plain4}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{"[3731:54:65fe:2::]:123", "[3731:54:65fe:2::]:123:124"}}, + }, + expected: []AnnounceableAddress{ + {Address: "[3731:54:65fe:2::]:123"}, + {Address: "[3731:54:65fe:2::]:123", AnnouncedPort: 124}, + }, + }, + { + cfg: &ApplicationConfiguration{ + P2P: P2P{Addresses: []string{"127.0.0.1:QWER:123"}}, + }, + shouldFail: true, + }, + } + for i, c := range cases { + actual, err := c.cfg.GetAddresses() + if c.shouldFail { + require.Error(t, err, i) + } else { + require.NoError(t, err) + require.Equal(t, c.expected, actual, i) + } + } +} diff --git a/pkg/config/basic_service.go b/pkg/config/basic_service.go index 3e2137b96..50014b06b 100644 --- a/pkg/config/basic_service.go +++ b/pkg/config/basic_service.go @@ -1,8 +1,39 @@ package config -// BasicService is used for simple services like Pprof or Prometheus monitoring. +import ( + "net" + "strconv" +) + +// BasicService is used as a simple base for node services like Pprof, RPC or +// Prometheus monitoring. type BasicService struct { - Enabled bool `yaml:"Enabled"` - Address string `yaml:"Address"` - Port string `yaml:"Port"` + Enabled bool `yaml:"Enabled"` + // Deprecated: please, use Addresses section instead. This field will be removed later. + Address *string `yaml:"Address,omitempty"` + // Deprecated: please, use Addresses section instead. This field will be removed later. + Port *uint16 `yaml:"Port,omitempty"` + // Addresses holds the list of bind addresses in the form of "address:port". + Addresses []string `yaml:"Addresses"` +} + +// GetAddresses returns the set of unique (in terms of raw strings) pairs host:port +// for the given basic service. +func (s BasicService) GetAddresses() []string { + addrs := make([]string, len(s.Addresses), len(s.Addresses)+1) + copy(addrs, s.Addresses) + if s.Address != nil || s.Port != nil { //nolint:staticcheck // SA1019: s.Address is deprecated + var ( + addr string + port uint16 + ) + if s.Address != nil { //nolint:staticcheck // SA1019: s.Address is deprecated + addr = *s.Address //nolint:staticcheck // SA1019: s.Address is deprecated + } + if s.Port != nil { //nolint:staticcheck // SA1019: s.Port is deprecated + port = *s.Port //nolint:staticcheck // SA1019: s.Port is deprecated + } + addrs = append(addrs, net.JoinHostPort(addr, strconv.FormatUint(uint64(port), 10))) + } + return addrs } diff --git a/pkg/config/basic_service_test.go b/pkg/config/basic_service_test.go new file mode 100644 index 000000000..685ddd9da --- /dev/null +++ b/pkg/config/basic_service_test.go @@ -0,0 +1,29 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBasicService_GetAddresses(t *testing.T) { + addr := "1.2.3.4" + port := uint16(1234) + s := BasicService{ + Enabled: false, + Address: &addr, + Port: &port, + Addresses: []string{"1.2.3.4:1234", /* same as Address:Port */ + "3.4.5.6:1234", "2.3.4.5", ":1235", "2.3.4.5:1234", + "3.4.5.6:1234" /* already in list */}, + } + require.Equal(t, []string{ + "1.2.3.4:1234", + "3.4.5.6:1234", + "2.3.4.5", + ":1235", + "2.3.4.5:1234", + "3.4.5.6:1234", + "1.2.3.4:1234", + }, s.GetAddresses()) +} diff --git a/pkg/config/config.go b/pkg/config/config.go index b2dc05e27..345d71503 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -55,8 +55,10 @@ func LoadFile(configPath string) (Config, error) { config := Config{ ApplicationConfiguration: ApplicationConfiguration{ - PingInterval: 30, - PingTimeout: 90, + P2P: P2P{ + PingInterval: 30, + PingTimeout: 30, + }, RPC: RPC{ MaxIteratorResultItems: DefaultMaxIteratorResultItems, MaxFindResultItems: 100, diff --git a/pkg/config/p2p.go b/pkg/config/p2p.go new file mode 100644 index 000000000..32e32c11b --- /dev/null +++ b/pkg/config/p2p.go @@ -0,0 +1,19 @@ +package config + +import "time" + +// P2P holds P2P node settings. +type P2P struct { + // Addresses stores the node address list in the form of "[host]:[port][:announcedPort]". + Addresses []string `yaml:"Addresses"` + AttemptConnPeers int `yaml:"AttemptConnPeers"` + // BroadcastFactor is the factor (0-100) controlling gossip fan-out number optimization. + BroadcastFactor int `yaml:"BroadcastFactor"` + DialTimeout time.Duration `yaml:"DialTimeout"` + ExtensiblePoolSize int `yaml:"ExtensiblePoolSize"` + MaxPeers int `yaml:"MaxPeers"` + MinPeers int `yaml:"MinPeers"` + PingInterval time.Duration `yaml:"PingInterval"` + PingTimeout time.Duration `yaml:"PingTimeout"` + ProtoTickInterval time.Duration `yaml:"ProtoTickInterval"` +} diff --git a/pkg/config/rpc_config.go b/pkg/config/rpc_config.go index 4411a7fee..44df2aa38 100644 --- a/pkg/config/rpc_config.go +++ b/pkg/config/rpc_config.go @@ -7,9 +7,8 @@ import ( type ( // RPC is an RPC service configuration information. RPC struct { - Address string `yaml:"Address"` - Enabled bool `yaml:"Enabled"` - EnableCORSWorkaround bool `yaml:"EnableCORSWorkaround"` + BasicService `yaml:",inline"` + EnableCORSWorkaround bool `yaml:"EnableCORSWorkaround"` // MaxGasInvoke is the maximum amount of GAS which // can be spent during an RPC call. MaxGasInvoke fixedn.Fixed8 `yaml:"MaxGasInvoke"` @@ -17,7 +16,6 @@ type ( MaxFindResultItems int `yaml:"MaxFindResultItems"` MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"` MaxWebSocketClients int `yaml:"MaxWebSocketClients"` - Port uint16 `yaml:"Port"` SessionEnabled bool `yaml:"SessionEnabled"` SessionExpirationTime int `yaml:"SessionExpirationTime"` SessionBackedByMPT bool `yaml:"SessionBackedByMPT"` @@ -28,10 +26,8 @@ type ( // TLS describes SSL/TLS configuration. TLS struct { - Address string `yaml:"Address"` - CertFile string `yaml:"CertFile"` - Enabled bool `yaml:"Enabled"` - Port uint16 `yaml:"Port"` - KeyFile string `yaml:"KeyFile"` + BasicService `yaml:",inline"` + CertFile string `yaml:"CertFile"` + KeyFile string `yaml:"KeyFile"` } ) diff --git a/pkg/config/rpc_config_test.go b/pkg/config/rpc_config_test.go new file mode 100644 index 000000000..2d9a02dcf --- /dev/null +++ b/pkg/config/rpc_config_test.go @@ -0,0 +1,24 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +// TestRPC_UnmarshalBasicService is aimed to check that BasicService config of +// RPC service can be properly unmarshalled. This test may be removed after +// Address and Port config fields removal. +func TestRPC_UnmarshalBasicService(t *testing.T) { + data := ` +Enabled: true +Port: 10332 +MaxGasInvoke: 15 +` + cfg := &RPC{} + err := yaml.Unmarshal([]byte(data), &cfg) + require.NoError(t, err) + require.True(t, cfg.Enabled) + require.Equal(t, uint16(10332), *cfg.Port) +} diff --git a/pkg/network/discovery_test.go b/pkg/network/discovery_test.go index 2c1d83fbf..e9c76c21b 100644 --- a/pkg/network/discovery_test.go +++ b/pkg/network/discovery_test.go @@ -20,7 +20,8 @@ type fakeTransp struct { started atomic2.Bool closed atomic2.Bool dialCh chan string - addr string + host string + port string } type fakeAPeer struct { @@ -45,8 +46,14 @@ func (f *fakeAPeer) Version() *payload.Version { return f.version } -func newFakeTransp(s *Server) Transporter { - return &fakeTransp{} +func newFakeTransp(s *Server, addr string) Transporter { + tr := &fakeTransp{} + h, p, err := net.SplitHostPort(addr) + if err == nil { + tr.host = h + tr.port = p + } + return tr } func (ft *fakeTransp) Dial(addr string, timeout time.Duration) (AddressablePeer, error) { @@ -62,14 +69,15 @@ func (ft *fakeTransp) Accept() { if ft.started.Load() { panic("started twice") } - ft.addr = net.JoinHostPort("0.0.0.0", "42") + ft.host = "0.0.0.0" + ft.port = "42" ft.started.Store(true) } func (ft *fakeTransp) Proto() string { return "" } -func (ft *fakeTransp) Address() string { - return ft.addr +func (ft *fakeTransp) HostPort() (string, string) { + return ft.host, ft.port } func (ft *fakeTransp) Close() { if ft.closed.Load() { diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 0a4f3f311..959e07cb9 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -158,7 +158,7 @@ func (p *localPeer) HandleVersion(v *payload.Version) error { return nil } func (p *localPeer) SendVersion() error { - m, err := p.server.getVersionMsg() + m, err := p.server.getVersionMsg(nil) if err != nil { return err } @@ -208,6 +208,10 @@ func newTestServer(t *testing.T, serverConfig ServerConfig) *Server { } func newTestServerWithCustomCfg(t *testing.T, serverConfig ServerConfig, protocolCfg func(*config.ProtocolConfiguration)) *Server { + if len(serverConfig.Addresses) == 0 { + // Normally it will be done by ApplicationConfiguration.GetAddresses(). + serverConfig.Addresses = []config.AnnounceableAddress{{Address: ":0"}} + } s, err := newServerFromConstructors(serverConfig, fakechain.NewFakeChainWithCustomCfg(protocolCfg), new(fakechain.FakeStateSync), zaptest.NewLogger(t), newFakeTransp, newTestDiscovery) require.NoError(t, err) diff --git a/pkg/network/server.go b/pkg/network/server.go index 11737d61b..1e248fa37 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -97,7 +97,7 @@ type ( // A copy of the Ledger's config. config config.ProtocolConfiguration - transport Transporter + transports []Transporter discovery Discoverer chain Ledger bQueue *blockQueue @@ -154,13 +154,13 @@ func randomID() uint32 { // NewServer returns a new Server, initialized with the given configuration. func NewServer(config ServerConfig, chain Ledger, stSync StateSync, log *zap.Logger) (*Server, error) { - return newServerFromConstructors(config, chain, stSync, log, func(s *Server) Transporter { - return NewTCPTransport(s, net.JoinHostPort(s.ServerConfig.Address, strconv.Itoa(int(s.ServerConfig.Port))), s.log) + return newServerFromConstructors(config, chain, stSync, log, func(s *Server, addr string) Transporter { + return NewTCPTransport(s, addr, s.log) }, newDefaultDiscovery) } func newServerFromConstructors(config ServerConfig, chain Ledger, stSync StateSync, log *zap.Logger, - newTransport func(*Server) Transporter, + newTransport func(*Server, string) Transporter, newDiscovery func([]string, time.Duration, Transporter) Discoverer, ) (*Server, error) { if log == nil { @@ -238,11 +238,20 @@ func newServerFromConstructors(config ServerConfig, chain Ledger, stSync StateSy s.BroadcastFactor = defaultBroadcastFactor } - s.transport = newTransport(s) + if len(s.ServerConfig.Addresses) == 0 { + return nil, errors.New("no bind addresses configured") + } + transports := make([]Transporter, len(s.ServerConfig.Addresses)) + for i, addr := range s.ServerConfig.Addresses { + transports[i] = newTransport(s, addr.Address) + } + s.transports = transports s.discovery = newDiscovery( s.Seeds, s.DialTimeout, - s.transport, + // Here we need to pick up a single transporter, it will be used to + // dial, and it doesn't matter which one. + s.transports[0], ) return s, nil @@ -271,7 +280,9 @@ func (s *Server) Start(errChan chan error) { go s.relayBlocksLoop() go s.bQueue.run() go s.bSyncQueue.run() - go s.transport.Accept() + for _, tr := range s.transports { + go tr.Accept() + } setServerAndNodeVersions(s.UserAgent, strconv.FormatUint(uint64(s.id), 10)) s.run() } @@ -280,7 +291,9 @@ func (s *Server) Start(errChan chan error) { // once stopped the same intance of the Server can't be started again by calling Start. func (s *Server) Shutdown() { s.log.Info("shutting down server", zap.Int("peers", s.PeerCount())) - s.transport.Close() + for _, tr := range s.transports { + tr.Close() + } for _, p := range s.getPeers(nil) { p.Disconnect(errServerShutdown) } @@ -600,11 +613,12 @@ func (s *Server) HandshakedPeersCount() int { return count } -// getVersionMsg returns the current version message. -func (s *Server) getVersionMsg() (*Message, error) { - port, err := s.Port() +// getVersionMsg returns the current version message generated for the specified +// connection. +func (s *Server) getVersionMsg(localAddr net.Addr) (*Message, error) { + port, err := s.Port(localAddr) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to fetch server port: %w", err) } capabilities := []capability.Capability{ @@ -1654,26 +1668,42 @@ func (s *Server) broadcastTxLoop() { } } -// Port returns a server port that should be used in P2P version exchange. In -// case `AnnouncedPort` is set in the server.Config, the announced node port -// will be returned (e.g. consider the node running behind NAT). If `AnnouncedPort` -// isn't set, the port returned may still differs from that of server.Config. -func (s *Server) Port() (uint16, error) { - if s.AnnouncedPort != 0 { - return s.ServerConfig.AnnouncedPort, nil +// Port returns a server port that should be used in P2P version exchange with the +// peer connected on the given localAddr. In case if announced node port is set +// in the server.Config for the given bind address, the announced node port will +// be returned (e.g. consider the node running behind NAT). If `AnnouncedPort` +// isn't set, the port returned may still differ from that of server.Config. If +// no localAddr is given, then the first available port will be returned. +func (s *Server) Port(localAddr net.Addr) (uint16, error) { + var connIP string + if localAddr != nil { + connIP, _, _ = net.SplitHostPort(localAddr.String()) // Ignore error and provide info if possible. } - var port uint16 - _, portStr, err := net.SplitHostPort(s.transport.Address()) - if err != nil { - port = s.ServerConfig.Port - } else { - p, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return 0, err + var defaultPort *uint16 + for i, tr := range s.transports { + listenIP, listenPort := tr.HostPort() + if listenIP == "::" || listenIP == "" || localAddr == nil || connIP == "" || connIP == listenIP { + var res uint16 + if s.ServerConfig.Addresses[i].AnnouncedPort != 0 { + res = s.ServerConfig.Addresses[i].AnnouncedPort + } else { + p, err := strconv.ParseUint(listenPort, 10, 16) + if err != nil { + return 0, fmt.Errorf("failed to parse bind port from '%s': %w", listenPort, err) + } + res = uint16(p) + } + if localAddr == nil || // no local address is specified => take the first port available + (listenIP != "::" && listenIP != "") { // direct match is always preferable + return res, nil + } + defaultPort = &res } - port = uint16(p) } - return port, nil + if defaultPort != nil { + return *defaultPort, nil + } + return 0, fmt.Errorf("bind address for connection '%s' is not registered", localAddr.String()) } // optimalNumOfThreads returns the optimal number of processing threads to create diff --git a/pkg/network/server_config.go b/pkg/network/server_config.go index 0983dfda2..e1df8b386 100644 --- a/pkg/network/server_config.go +++ b/pkg/network/server_config.go @@ -1,6 +1,7 @@ package network import ( + "fmt" "time" "github.com/nspcc-dev/neo-go/pkg/config" @@ -28,14 +29,8 @@ type ( // The user agent of the server. UserAgent string - // Address. Example: "localhost". - Address string - - // AnnouncedPort is an announced node port for P2P version exchange. - AnnouncedPort uint16 - - // Port is the actual node port it is bound to. Example: 20332. - Port uint16 + // Addresses stores the list of bind addresses for the node. + Addresses []config.AnnounceableAddress // The network mode the server will operate on. // ModePrivNet docker private network. @@ -86,34 +81,72 @@ type ( // NewServerConfig creates a new ServerConfig struct // using the main applications config. -func NewServerConfig(cfg config.Config) ServerConfig { +func NewServerConfig(cfg config.Config) (ServerConfig, error) { appConfig := cfg.ApplicationConfiguration protoConfig := cfg.ProtocolConfiguration timePerBlock := protoConfig.TimePerBlock if timePerBlock == 0 && protoConfig.SecondsPerBlock > 0 { //nolint:staticcheck // SA1019: protoConfig.SecondsPerBlock is deprecated timePerBlock = time.Duration(protoConfig.SecondsPerBlock) * time.Second //nolint:staticcheck // SA1019: protoConfig.SecondsPerBlock is deprecated } - - return ServerConfig{ + dialTimeout := appConfig.P2P.DialTimeout + if dialTimeout == 0 && appConfig.DialTimeout > 0 { //nolint:staticcheck // SA1019: appConfig.DialTimeout is deprecated + dialTimeout = time.Duration(appConfig.DialTimeout) * time.Second //nolint:staticcheck // SA1019: appConfig.DialTimeout is deprecated + } + protoTickInterval := appConfig.P2P.ProtoTickInterval + if protoTickInterval == 0 && appConfig.ProtoTickInterval > 0 { //nolint:staticcheck // SA1019: appConfig.ProtoTickInterval is deprecated + protoTickInterval = time.Duration(appConfig.ProtoTickInterval) * time.Second //nolint:staticcheck // SA1019: appConfig.ProtoTickInterval is deprecated + } + pingInterval := appConfig.P2P.PingInterval + if pingInterval == 0 && appConfig.PingInterval > 0 { //nolint:staticcheck // SA1019: appConfig.PingInterval is deprecated + pingInterval = time.Duration(appConfig.PingInterval) * time.Second //nolint:staticcheck // SA1019: appConfig.PingInterval is deprecated + } + pingTimeout := appConfig.P2P.PingTimeout + if pingTimeout == 0 && appConfig.PingTimeout > 0 { //nolint:staticcheck // SA1019: appConfig.PingTimeout is deprecated + pingTimeout = time.Duration(appConfig.PingTimeout) * time.Second //nolint:staticcheck // SA1019: appConfig.PingTimeout is deprecated + } + maxPeers := appConfig.P2P.MaxPeers + if maxPeers == 0 && appConfig.MaxPeers > 0 { //nolint:staticcheck // SA1019: appConfig.MaxPeers is deprecated + maxPeers = appConfig.MaxPeers //nolint:staticcheck // SA1019: appConfig.MaxPeers is deprecated + } + attemptConnPeers := appConfig.P2P.AttemptConnPeers + if attemptConnPeers == 0 && appConfig.AttemptConnPeers > 0 { //nolint:staticcheck // SA1019: appConfig.AttemptConnPeers is deprecated + attemptConnPeers = appConfig.AttemptConnPeers //nolint:staticcheck // SA1019: appConfig.AttemptConnPeers is deprecated + } + minPeers := appConfig.P2P.MinPeers + if minPeers == 0 && appConfig.MinPeers > 0 { //nolint:staticcheck // SA1019: appConfig.MinPeers is deprecated + minPeers = appConfig.MinPeers //nolint:staticcheck // SA1019: appConfig.MinPeers is deprecated + } + extPoolSize := appConfig.P2P.ExtensiblePoolSize + if extPoolSize == 0 && appConfig.ExtensiblePoolSize > 0 { //nolint:staticcheck // SA1019: appConfig.ExtensiblePoolSize is deprecated + extPoolSize = appConfig.ExtensiblePoolSize //nolint:staticcheck // SA1019: appConfig.ExtensiblePoolSize is deprecated + } + broadcastFactor := appConfig.P2P.BroadcastFactor + if broadcastFactor > 0 && appConfig.BroadcastFactor > 0 { //nolint:staticcheck // SA1019: appConfig.BroadcastFactor is deprecated + broadcastFactor = appConfig.BroadcastFactor //nolint:staticcheck // SA1019: appConfig.BroadcastFactor is deprecated + } + addrs, err := appConfig.GetAddresses() + if err != nil { + return ServerConfig{}, fmt.Errorf("failed to parse addresses: %w", err) + } + c := ServerConfig{ UserAgent: cfg.GenerateUserAgent(), - Address: appConfig.Address, - AnnouncedPort: appConfig.AnnouncedNodePort, - Port: appConfig.NodePort, + Addresses: addrs, Net: protoConfig.Magic, Relay: appConfig.Relay, Seeds: protoConfig.SeedList, - DialTimeout: time.Duration(appConfig.DialTimeout) * time.Second, - ProtoTickInterval: time.Duration(appConfig.ProtoTickInterval) * time.Second, - PingInterval: time.Duration(appConfig.PingInterval) * time.Second, - PingTimeout: time.Duration(appConfig.PingTimeout) * time.Second, - MaxPeers: appConfig.MaxPeers, - AttemptConnPeers: appConfig.AttemptConnPeers, - MinPeers: appConfig.MinPeers, + DialTimeout: dialTimeout, + ProtoTickInterval: protoTickInterval, + PingInterval: pingInterval, + PingTimeout: pingTimeout, + MaxPeers: maxPeers, + AttemptConnPeers: attemptConnPeers, + MinPeers: minPeers, TimePerBlock: timePerBlock, OracleCfg: appConfig.Oracle, P2PNotaryCfg: appConfig.P2PNotary, StateRootCfg: appConfig.StateRoot, - ExtensiblePoolSize: appConfig.ExtensiblePoolSize, - BroadcastFactor: appConfig.BroadcastFactor, + ExtensiblePoolSize: extPoolSize, + BroadcastFactor: broadcastFactor, } + return c, nil } diff --git a/pkg/network/server_test.go b/pkg/network/server_test.go index 4ad30687d..175a32ca9 100644 --- a/pkg/network/server_test.go +++ b/pkg/network/server_test.go @@ -104,13 +104,13 @@ func TestServerStartAndShutdown(t *testing.T) { s.register <- p require.Eventually(t, func() bool { return 1 == s.PeerCount() }, time.Second, time.Millisecond*10) - assert.True(t, s.transport.(*fakeTransp).started.Load()) + assert.True(t, s.transports[0].(*fakeTransp).started.Load()) assert.Nil(t, s.txCallback) s.Shutdown() <-ch - require.True(t, s.transport.(*fakeTransp).closed.Load()) + require.True(t, s.transports[0].(*fakeTransp).closed.Load()) err, ok := p.droppedWith.Load().(error) require.True(t, ok) require.True(t, errors.Is(err, errServerShutdown)) @@ -191,7 +191,7 @@ func TestGetBlocksByIndex(t *testing.T) { } func testGetBlocksByIndex(t *testing.T, cmd CommandType) { - s := newTestServer(t, ServerConfig{Port: 0, UserAgent: "/test/"}) + s := newTestServer(t, ServerConfig{UserAgent: "/test/"}) start := s.chain.BlockHeight() if cmd == CMDGetHeaders { start = s.chain.HeaderHeight() @@ -217,7 +217,7 @@ func testGetBlocksByIndex(t *testing.T, cmd CommandType) { expectsCmd[i] = cmd expectedHeight[i] = []uint32{start + 1} } - go s.transport.Accept() + go s.transports[0].Accept() nonce := uint32(0) checkPingRespond := func(t *testing.T, peerIndex int, peerHeight uint32, hs ...uint32) { @@ -250,16 +250,15 @@ func testGetBlocksByIndex(t *testing.T, cmd CommandType) { func TestSendVersion(t *testing.T) { var ( - s = newTestServer(t, ServerConfig{Port: 0, UserAgent: "/test/"}) + s = newTestServer(t, ServerConfig{UserAgent: "/test/"}) p = newLocalPeer(t, s) ) // we need to set listener at least to handle dynamic port correctly - s.transport.Accept() + s.transports[0].Accept() p.messageHandler = func(t *testing.T, msg *Message) { - // listener is already set, so Address() gives us proper address with port - _, p, err := net.SplitHostPort(s.transport.Address()) - assert.NoError(t, err) - port, err := strconv.ParseUint(p, 10, 16) + // listener is already set, so Addresses(nil) gives us proper address with port + _, prt := s.transports[0].HostPort() + port, err := strconv.ParseUint(prt, 10, 16) assert.NoError(t, err) assert.Equal(t, CMDVersion, msg.Command) assert.IsType(t, msg.Payload, &payload.Version{}) @@ -390,7 +389,7 @@ func (s *Server) testHandleMessage(t *testing.T, p Peer, cmd CommandType, pl pay func startTestServer(t *testing.T, protocolCfg ...func(*config.ProtocolConfiguration)) *Server { var s *Server - srvCfg := ServerConfig{Port: 0, UserAgent: "/test/"} + srvCfg := ServerConfig{UserAgent: "/test/"} if protocolCfg != nil { s = newTestServerWithCustomCfg(t, srvCfg, protocolCfg[0]) } else { @@ -849,7 +848,7 @@ func TestHandleMPTData(t *testing.T) { t.Run("good", func(t *testing.T) { expected := [][]byte{{1, 2, 3}, {2, 3, 4}} - s := newTestServer(t, ServerConfig{Port: 0, UserAgent: "/test/"}) + s := newTestServer(t, ServerConfig{UserAgent: "/test/"}) s.config.P2PStateExchangeExtensions = true s.stateSync = &fakechain.FakeStateSync{ AddMPTNodesFunc: func(nodes [][]byte) error { @@ -1044,7 +1043,7 @@ func TestVerifyNotaryRequest(t *testing.T) { bc := fakechain.NewFakeChain() bc.MaxVerificationGAS = 10 bc.NotaryContractScriptHash = util.Uint160{1, 2, 3} - s, err := newServerFromConstructors(ServerConfig{}, bc, new(fakechain.FakeStateSync), zaptest.NewLogger(t), newFakeTransp, newTestDiscovery) + s, err := newServerFromConstructors(ServerConfig{Addresses: []config.AnnounceableAddress{{Address: ":0"}}}, bc, new(fakechain.FakeStateSync), zaptest.NewLogger(t), newFakeTransp, newTestDiscovery) require.NoError(t, err) newNotaryRequest := func() *payload.P2PNotaryRequest { return &payload.P2PNotaryRequest{ @@ -1123,3 +1122,33 @@ func TestTryInitStateSync(t *testing.T) { s.tryInitStateSync() }) } + +func TestServer_Port(t *testing.T) { + s := newTestServer(t, ServerConfig{ + Addresses: []config.AnnounceableAddress{ + {Address: "1.2.3.4:10"}, // some random address + {Address: ":1"}, // listen all IPs + {Address: "127.0.0.1:2"}, // address without announced port + {Address: "123.123.0.123:3", AnnouncedPort: 123}}, // address with announced port + }) + + // Default addr => first port available + actual, err := s.Port(nil) + require.NoError(t, err) + require.Equal(t, uint16(10), actual) + + // Specified address with direct match => port of matched address + actual, err = s.Port(&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 123}) + require.NoError(t, err) + require.Equal(t, uint16(2), actual) + + // No address match => 0.0.0.0's port + actual, err = s.Port(&net.TCPAddr{IP: net.IPv4(5, 6, 7, 8), Port: 123}) + require.NoError(t, err) + require.Equal(t, uint16(1), actual) + + // Specified address with match on announceable address => announced port + actual, err = s.Port(&net.TCPAddr{IP: net.IPv4(123, 123, 0, 123), Port: 123}) + require.NoError(t, err) + require.Equal(t, uint16(123), actual) +} diff --git a/pkg/network/tcp_peer.go b/pkg/network/tcp_peer.go index e297f669a..6de3abe74 100644 --- a/pkg/network/tcp_peer.go +++ b/pkg/network/tcp_peer.go @@ -307,7 +307,7 @@ func (p *TCPPeer) IsFullNode() bool { // SendVersion checks for the handshake state and sends a message to the peer. func (p *TCPPeer) SendVersion() error { - msg, err := p.server.getVersionMsg() + msg, err := p.server.getVersionMsg(p.conn.LocalAddr()) if err != nil { return err } diff --git a/pkg/network/tcp_peer_test.go b/pkg/network/tcp_peer_test.go index 056bbf9b0..7e03f84bb 100644 --- a/pkg/network/tcp_peer_test.go +++ b/pkg/network/tcp_peer_test.go @@ -19,7 +19,9 @@ func TestPeerHandshake(t *testing.T) { server, client := net.Pipe() tcpS := NewTCPPeer(server, "", newTestServer(t, ServerConfig{})) + tcpS.server.transports[0].Accept() // properly initialize the address list tcpC := NewTCPPeer(client, "", newTestServer(t, ServerConfig{})) + tcpC.server.transports[0].Accept() // Something should read things written into the pipe. go connReadStub(tcpS.conn) diff --git a/pkg/network/tcp_transport.go b/pkg/network/tcp_transport.go index b1b6662d0..026ca2bc9 100644 --- a/pkg/network/tcp_transport.go +++ b/pkg/network/tcp_transport.go @@ -15,17 +15,32 @@ type TCPTransport struct { server *Server listener net.Listener bindAddr string + hostPort hostPort lock sync.RWMutex quit bool } +type hostPort struct { + Host string + Port string +} + // NewTCPTransport returns a new TCPTransport that will listen for // new incoming peer connections. func NewTCPTransport(s *Server, bindAddr string, log *zap.Logger) *TCPTransport { + host, port, err := net.SplitHostPort(bindAddr) + if err != nil { + // Only host can be provided, it's OK. + host = bindAddr + } return &TCPTransport{ log: log, server: s, bindAddr: bindAddr, + hostPort: hostPort{ + Host: host, + Port: port, + }, } } @@ -55,6 +70,8 @@ func (t *TCPTransport) Accept() { return } t.listener = l + t.bindAddr = l.Addr().String() + t.hostPort.Host, t.hostPort.Port, _ = net.SplitHostPort(t.bindAddr) // no error expected as l.Addr() is a valid address. t.lock.Unlock() for { @@ -66,7 +83,7 @@ func (t *TCPTransport) Accept() { if errors.Is(err, net.ErrClosed) && quit { break } - t.log.Warn("TCP accept error", zap.Error(err)) + t.log.Warn("TCP accept error", zap.String("address", l.Addr().String()), zap.Error(err)) continue } p := NewTCPPeer(conn, "", t.server) @@ -89,12 +106,10 @@ func (t *TCPTransport) Proto() string { return "tcp" } -// Address implements the Transporter interface. -func (t *TCPTransport) Address() string { +// HostPort implements the Transporter interface. +func (t *TCPTransport) HostPort() (string, string) { t.lock.RLock() defer t.lock.RUnlock() - if t.listener != nil { - return t.listener.Addr().String() - } - return "" + + return t.hostPort.Host, t.hostPort.Port } diff --git a/pkg/network/transport.go b/pkg/network/transport.go index 71228647b..96e5425d9 100644 --- a/pkg/network/transport.go +++ b/pkg/network/transport.go @@ -8,6 +8,6 @@ type Transporter interface { Dial(addr string, timeout time.Duration) (AddressablePeer, error) Accept() Proto() string - Address() string + HostPort() (string, string) Close() } diff --git a/pkg/services/metrics/metrics.go b/pkg/services/metrics/metrics.go index b7af0c3a9..e814e1eff 100644 --- a/pkg/services/metrics/metrics.go +++ b/pkg/services/metrics/metrics.go @@ -3,31 +3,62 @@ package metrics import ( "context" "errors" + "fmt" + "net" "net/http" "github.com/nspcc-dev/neo-go/pkg/config" + "go.uber.org/atomic" "go.uber.org/zap" ) // Service serves metrics. type Service struct { - *http.Server + http []*http.Server config config.BasicService log *zap.Logger serviceType string + started *atomic.Bool +} + +// NewService configures logger and returns new service instance. +func NewService(name string, httpServers []*http.Server, cfg config.BasicService, log *zap.Logger) *Service { + return &Service{ + http: httpServers, + config: cfg, + serviceType: name, + log: log.With(zap.String("service", name)), + started: atomic.NewBool(false), + } } // Start runs http service with the exposed endpoint on the configured port. -func (ms *Service) Start() { +func (ms *Service) Start() error { if ms.config.Enabled { - ms.log.Info("service is running", zap.String("endpoint", ms.Addr)) - err := ms.ListenAndServe() - if err != nil && !errors.Is(err, http.ErrServerClosed) { - ms.log.Warn("service couldn't start on configured port") + if !ms.started.CAS(false, true) { + ms.log.Info("service already started") + return nil + } + for _, srv := range ms.http { + ms.log.Info("starting service", zap.String("endpoint", srv.Addr)) + + ln, err := net.Listen("tcp", srv.Addr) + if err != nil { + return fmt.Errorf("failed to listen on %s: %w", srv.Addr, err) + } + srv.Addr = ln.Addr().String() // set Addr to the actual address + + go func(s *http.Server) { + err = s.Serve(ln) + if !errors.Is(err, http.ErrServerClosed) { + ms.log.Error("failed to start service", zap.String("endpoint", s.Addr), zap.Error(err)) + } + }(srv) } } else { ms.log.Info("service hasn't started since it's disabled") } + return nil } // ShutDown stops the service. @@ -35,9 +66,14 @@ func (ms *Service) ShutDown() { if !ms.config.Enabled { return } - ms.log.Info("shutting down service", zap.String("endpoint", ms.Addr)) - err := ms.Shutdown(context.Background()) - if err != nil { - ms.log.Error("can't shut service down", zap.Error(err)) + if !ms.started.CAS(true, false) { + return + } + for _, srv := range ms.http { + ms.log.Info("shutting down service", zap.String("endpoint", srv.Addr)) + err := srv.Shutdown(context.Background()) + if err != nil { + ms.log.Error("can't shut service down", zap.String("endpoint", srv.Addr), zap.Error(err)) + } } } diff --git a/pkg/services/metrics/pprof.go b/pkg/services/metrics/pprof.go index 6c3bbb202..9dc1f7d63 100644 --- a/pkg/services/metrics/pprof.go +++ b/pkg/services/metrics/pprof.go @@ -24,13 +24,13 @@ func NewPprofService(cfg config.BasicService, log *zap.Logger) *Service { handler.HandleFunc("/debug/pprof/symbol", pprof.Symbol) handler.HandleFunc("/debug/pprof/trace", pprof.Trace) - return &Service{ - Server: &http.Server{ - Addr: cfg.Address + ":" + cfg.Port, + addrs := cfg.GetAddresses() + srvs := make([]*http.Server, len(addrs)) + for i, addr := range addrs { + srvs[i] = &http.Server{ + Addr: addr, Handler: handler, - }, - config: cfg, - serviceType: "Pprof", - log: log.With(zap.String("service", "Pprof")), + } } + return NewService("Pprof", srvs, cfg, log) } diff --git a/pkg/services/metrics/prometheus.go b/pkg/services/metrics/prometheus.go index b00efc8b3..854fd37f4 100644 --- a/pkg/services/metrics/prometheus.go +++ b/pkg/services/metrics/prometheus.go @@ -17,13 +17,13 @@ func NewPrometheusService(cfg config.BasicService, log *zap.Logger) *Service { return nil } - return &Service{ - Server: &http.Server{ - Addr: cfg.Address + ":" + cfg.Port, - Handler: promhttp.Handler(), - }, - config: cfg, - serviceType: "Prometheus", - log: log.With(zap.String("service", "Prometheus")), + addrs := cfg.GetAddresses() + srvs := make([]*http.Server, len(addrs)) + for i, addr := range addrs { + srvs[i] = &http.Server{ + Addr: addr, + Handler: promhttp.Handler(), // share metrics between multiple prometheus handlers + } } + return NewService("Prometheus", srvs, cfg, log) } diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index a3550a08c..e80eacc2c 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -1923,9 +1923,10 @@ func TestClient_Iterator_SessionConfigVariations(t *testing.T) { cfg.ApplicationConfiguration.RPC.SessionEnabled = true cfg.ApplicationConfiguration.RPC.SessionBackedByMPT = true }) - serverConfig := network.NewServerConfig(cfg) + serverConfig, err := network.NewServerConfig(cfg) + require.NoError(t, err) serverConfig.UserAgent = fmt.Sprintf(config.UserAgentFormat, "0.98.6-test") - serverConfig.Port = 0 + serverConfig.Addresses = []config.AnnounceableAddress{{Address: ":0"}} server, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), logger) require.NoError(t, err) errCh := make(chan error, 2) diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 1a1a37ea3..9bc132cb1 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -117,7 +117,9 @@ type ( // Server represents the JSON-RPC 2.0 server. Server struct { - *http.Server + http []*http.Server + https []*http.Server + chain Ledger config config.RPC // wsReadLimit represents web-socket message limit for a receiving side. @@ -128,7 +130,6 @@ type ( coreServer *network.Server oracle *atomic.Value log *zap.Logger - https *http.Server shutdown chan struct{} started *atomic.Bool errChan chan error @@ -254,14 +255,22 @@ var invalidBlockHeightError = func(index int, height int) *neorpc.Error { // New creates a new Server struct. func New(chain Ledger, conf config.RPC, coreServer *network.Server, orc OracleHandler, log *zap.Logger, errChan chan error) Server { - httpServer := &http.Server{ - Addr: conf.Address + ":" + strconv.FormatUint(uint64(conf.Port), 10), + addrs := conf.GetAddresses() + httpServers := make([]*http.Server, len(addrs)) + for i, addr := range addrs { + httpServers[i] = &http.Server{ + Addr: addr, + } } - var tlsServer *http.Server + var tlsServers []*http.Server if cfg := conf.TLSConfig; cfg.Enabled { - tlsServer = &http.Server{ - Addr: net.JoinHostPort(cfg.Address, strconv.FormatUint(uint64(cfg.Port), 10)), + addrs := cfg.GetAddresses() + tlsServers = make([]*http.Server, len(addrs)) + for i, addr := range addrs { + tlsServers[i] = &http.Server{ + Addr: addr, + } } } @@ -289,7 +298,9 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server, wsOriginChecker = func(_ *http.Request) bool { return true } } return Server{ - Server: httpServer, + http: httpServers, + https: tlsServers, + chain: chain, config: conf, wsReadLimit: int64(protoCfg.MaxBlockSize*4)/3 + 1024, // Enough for Base64-encoded content of `submitblock` and `submitp2pnotaryrequest`. @@ -299,7 +310,6 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server, coreServer: coreServer, log: log, oracle: oracleWrapped, - https: tlsServer, shutdown: make(chan struct{}), started: atomic.NewBool(false), errChan: errChan, @@ -333,40 +343,48 @@ func (s *Server) Start() { s.log.Info("RPC server already started") return } - s.Handler = http.HandlerFunc(s.handleHTTPRequest) - s.log.Info("starting rpc-server", zap.String("endpoint", s.Addr)) + for _, srv := range s.http { + srv.Handler = http.HandlerFunc(s.handleHTTPRequest) + s.log.Info("starting rpc-server", zap.String("endpoint", srv.Addr)) + + ln, err := net.Listen("tcp", srv.Addr) + if err != nil { + s.errChan <- fmt.Errorf("failed to listen on %s: %w", srv.Addr, err) + return + } + srv.Addr = ln.Addr().String() // set Addr to the actual address + go func(server *http.Server) { + err = server.Serve(ln) + if !errors.Is(err, http.ErrServerClosed) { + s.log.Error("failed to start RPC server", zap.Error(err)) + s.errChan <- err + } + }(srv) + } go s.handleSubEvents() if cfg := s.config.TLSConfig; cfg.Enabled { - s.https.Handler = http.HandlerFunc(s.handleHTTPRequest) - s.log.Info("starting rpc-server (https)", zap.String("endpoint", s.https.Addr)) - go func() { - ln, err := net.Listen("tcp", s.https.Addr) + for _, srv := range s.https { + srv.Handler = http.HandlerFunc(s.handleHTTPRequest) + s.log.Info("starting rpc-server (https)", zap.String("endpoint", srv.Addr)) + + ln, err := net.Listen("tcp", srv.Addr) if err != nil { s.errChan <- err return } - s.https.Addr = ln.Addr().String() - err = s.https.ServeTLS(ln, cfg.CertFile, cfg.KeyFile) - if !errors.Is(err, http.ErrServerClosed) { - s.log.Error("failed to start TLS RPC server", zap.Error(err)) - s.errChan <- err - } - }() - } - ln, err := net.Listen("tcp", s.Addr) - if err != nil { - s.errChan <- err - return - } - s.Addr = ln.Addr().String() // set Addr to the actual address - go func() { - err = s.Serve(ln) - if !errors.Is(err, http.ErrServerClosed) { - s.log.Error("failed to start RPC server", zap.Error(err)) - s.errChan <- err + srv.Addr = ln.Addr().String() + + go func(srv *http.Server) { + err = srv.ServeTLS(ln, cfg.CertFile, cfg.KeyFile) + if !errors.Is(err, http.ErrServerClosed) { + s.log.Error("failed to start TLS RPC server", + zap.String("endpoint", srv.Addr), zap.Error(err)) + s.errChan <- err + } + }(srv) } - }() + } } // Shutdown stops the RPC server if it's running. It can only be called once, @@ -381,17 +399,23 @@ func (s *Server) Shutdown() { close(s.shutdown) if s.config.TLSConfig.Enabled { - s.log.Info("shutting down RPC server (https)", zap.String("endpoint", s.https.Addr)) - err := s.https.Shutdown(context.Background()) - if err != nil { - s.log.Warn("error during RPC (https) server shutdown", zap.Error(err)) + for _, srv := range s.https { + s.log.Info("shutting down RPC server (https)", zap.String("endpoint", srv.Addr)) + err := srv.Shutdown(context.Background()) + if err != nil { + s.log.Warn("error during RPC (https) server shutdown", + zap.String("endpoint", srv.Addr), zap.Error(err)) + } } } - s.log.Info("shutting down RPC server", zap.String("endpoint", s.Addr)) - err := s.Server.Shutdown(context.Background()) - if err != nil { - s.log.Warn("error during RPC (http) server shutdown", zap.Error(err)) + for _, srv := range s.http { + s.log.Info("shutting down RPC server", zap.String("endpoint", srv.Addr)) + err := srv.Shutdown(context.Background()) + if err != nil { + s.log.Warn("error during RPC (http) server shutdown", + zap.String("endpoint", srv.Addr), zap.Error(err)) + } } // Perform sessions finalisation. @@ -693,7 +717,7 @@ func (s *Server) getBlockHash(reqParams params.Params) (interface{}, *neorpc.Err } func (s *Server) getVersion(_ params.Params) (interface{}, *neorpc.Error) { - port, err := s.coreServer.Port() + port, err := s.coreServer.Port(nil) // any port will suite if err != nil { return nil, neorpc.NewInternalServerError(fmt.Sprintf("cannot fetch tcp port: %s", err)) } @@ -2790,3 +2814,13 @@ func escapeForLog(in string) string { return c }, in) } + +// Addresses returns the list of addresses RPC server is listening to in the form of +// address:port. +func (s *Server) Addresses() []string { + res := make([]string, len(s.http)) + for i, srv := range s.http { + res[i] = srv.Addr + } + return res +} diff --git a/pkg/services/rpcsrv/server_helper_test.go b/pkg/services/rpcsrv/server_helper_test.go index 6ebd192c3..72be966d8 100644 --- a/pkg/services/rpcsrv/server_helper_test.go +++ b/pkg/services/rpcsrv/server_helper_test.go @@ -116,9 +116,10 @@ func initClearServerWithServices(t testing.TB, needOracle bool, needNotary bool, } func wrapUnitTestChain(t testing.TB, chain *core.Blockchain, orc *oracle.Oracle, cfg config.Config, logger *zap.Logger) (*core.Blockchain, *Server, *httptest.Server) { - serverConfig := network.NewServerConfig(cfg) + serverConfig, err := network.NewServerConfig(cfg) + require.NoError(t, err) serverConfig.UserAgent = fmt.Sprintf(config.UserAgentFormat, "0.98.6-test") - serverConfig.Port = 0 + serverConfig.Addresses = []config.AnnounceableAddress{{Address: ":0"}} server, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), logger) require.NoError(t, err) errCh := make(chan error, 2) diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index 4aa4f0743..19b22b244 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -3278,7 +3278,8 @@ func TestEscapeForLog(t *testing.T) { func BenchmarkHandleIn(b *testing.B) { chain, orc, cfg, logger := getUnitTestChain(b, false, false, false) - serverConfig := network.NewServerConfig(cfg) + serverConfig, err := network.NewServerConfig(cfg) + require.NoError(b, err) serverConfig.UserAgent = fmt.Sprintf(config.UserAgentFormat, "0.98.6-test") serverConfig.LogLevel = zapcore.FatalLevel server, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), logger)