From 42181349ae376e86fc0839e653e508a2586ea8df Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Tue, 25 Jun 2019 17:01:01 -0400 Subject: [PATCH 1/6] add bash and zsh completion support (#82) as suggested in https://github.com/algorand/go-algorand/issues/80 --- cmd/goal/commands.go | 3 +++ cmd/goal/completion.go | 59 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 cmd/goal/completion.go diff --git a/cmd/goal/commands.go b/cmd/goal/commands.go index 171ce24d99..3fb2a57d81 100644 --- a/cmd/goal/commands.go +++ b/cmd/goal/commands.go @@ -76,6 +76,9 @@ func init() { // ledger.go rootCmd.AddCommand(ledgerCmd) + // completion.go + rootCmd.AddCommand(completionCmd) + // Config defaultDataDirValue := []string{""} rootCmd.PersistentFlags().StringArrayVarP(&dataDirs, "datadir", "d", defaultDataDirValue, "Data directory for the node") diff --git a/cmd/goal/completion.go b/cmd/goal/completion.go new file mode 100644 index 0000000000..eb670062ff --- /dev/null +++ b/cmd/goal/completion.go @@ -0,0 +1,59 @@ +// Copyright (C) 2019 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package main + +import ( + "os" + + "github.com/spf13/cobra" +) + +func init() { + completionCmd.AddCommand(bashCompletionCmd) + completionCmd.AddCommand(zshCompletionCmd) +} + +var completionCmd = &cobra.Command{ + Use: "completion", + Short: "Shell completion helper", + Long: "Shell completion helper", + Args: validateNoPosArgsFn, + Run: func(cmd *cobra.Command, args []string) { + // If no arguments passed, we should fallback to help + cmd.HelpFunc()(cmd, args) + }, +} + +var bashCompletionCmd = &cobra.Command{ + Use: "bash", + Short: "Generate bash completion commands", + Long: "Generate bash completion commands", + Args: validateNoPosArgsFn, + Run: func(cmd *cobra.Command, _ []string) { + rootCmd.GenBashCompletion(os.Stdout) + }, +} + +var zshCompletionCmd = &cobra.Command{ + Use: "zsh", + Short: "Generate zsh completion commands", + Long: "Generate zsh completion commands", + Args: validateNoPosArgsFn, + Run: func(cmd *cobra.Command, _ []string) { + rootCmd.GenZshCompletion(os.Stdout) + }, +} From 1eb313efb445a541e4a2192426a546861c7a3ea3 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Tue, 25 Jun 2019 18:41:40 -0400 Subject: [PATCH 2/6] goal ledger supply: rounds are not denominated in microalgos (#78) --- cmd/goal/ledger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/goal/ledger.go b/cmd/goal/ledger.go index 2b3e443ac6..e36b6b8490 100644 --- a/cmd/goal/ledger.go +++ b/cmd/goal/ledger.go @@ -49,6 +49,6 @@ var supplyCmd = &cobra.Command{ reportErrorf(errorRequestFail, err) } - fmt.Printf("Round: %v microAlgos\nTotal Money: %v microAlgos\nOnline Money: %v microAlgos\n", response.Round, response.TotalMoney, response.OnlineMoney) + fmt.Printf("Round: %v\nTotal Money: %v microAlgos\nOnline Money: %v microAlgos\n", response.Round, response.TotalMoney, response.OnlineMoney) }, } From 3dc6422a669d41a0b69e914f007b0c446722f4b4 Mon Sep 17 00:00:00 2001 From: Tsachi Herman Date: Tue, 25 Jun 2019 18:44:04 -0400 Subject: [PATCH 3/6] Improve remoteHost logging during ConnectPeerFail event. (#85) --- network/wsNetwork.go | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 05c9ee159a..8c84f85f58 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -801,11 +801,24 @@ func (wn *WebsocketNetwork) updateURLHost(originalRootURL string, originIP net.I // ServerHTTP handles the gossip network functions over websockets func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *http.Request) { + remoteHost, _, err := net.SplitHostPort(request.RemoteAddr) + if err != nil { + // this error should not happen. The go framework is responsible for populating the RemoteAddr using the incoming TCP connection + // information. + wn.log.Errorf("could not parse request.RemoteAddr=%v, %s", request.RemoteAddr, err) + response.WriteHeader(http.StatusServiceUnavailable) + return + } + originIP := wn.getForwardedConnectionAddress(request.Header) + if originIP != nil { + remoteHost = originIP.String() + } + if wn.numIncomingPeers() >= wn.config.IncomingConnectionsLimit { networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "incoming_connection_limit"}) wn.log.EventWithDetails(telemetryspec.Network, telemetryspec.ConnectPeerFailEvent, telemetryspec.ConnectPeerFailEventDetails{ - Address: justHost(request.RemoteAddr), + Address: remoteHost, HostName: request.Header.Get(TelemetryIDHeader), Incoming: true, InstanceName: request.Header.Get(InstanceNameHeader), @@ -814,21 +827,12 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt response.WriteHeader(http.StatusServiceUnavailable) return } - remoteHost, _, err := net.SplitHostPort(request.RemoteAddr) - if err != nil { - wn.log.Errorf("could not parse request.RemoteAddr=%v, %s", request.RemoteAddr, err) - response.WriteHeader(http.StatusServiceUnavailable) - return - } - originIP := wn.getForwardedConnectionAddress(request.Header) - if originIP != nil { - remoteHost = originIP.String() - } + if wn.connectedForIP(remoteHost) >= wn.config.MaxConnectionsPerIP { - networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "incoming_connection_limit"}) + networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "incoming_connection_per_ip_limit"}) wn.log.EventWithDetails(telemetryspec.Network, telemetryspec.ConnectPeerFailEvent, telemetryspec.ConnectPeerFailEventDetails{ - Address: justHost(request.RemoteAddr), + Address: remoteHost, HostName: request.Header.Get(TelemetryIDHeader), Incoming: true, InstanceName: request.Header.Get(InstanceNameHeader), @@ -878,7 +882,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt wn.log.With("event", "ConnectedIn").With("remote", otherPublicAddr).With("local", localAddr).Infof("Accepted incoming connection from peer %s", otherPublicAddr) wn.log.EventWithDetails(telemetryspec.Network, telemetryspec.ConnectPeerEvent, telemetryspec.PeerEventDetails{ - Address: justHost(request.RemoteAddr), + Address: remoteHost, HostName: otherTelemetryGUID, Incoming: true, InstanceName: otherInstanceName, @@ -1554,9 +1558,9 @@ func (wn *WebsocketNetwork) removePeer(peer *wsPeer, reason disconnectReason) { // definitely don't change this to do the logging while holding the lock. localAddr, _ := wn.Address() wn.log.With("event", "Disconnected").With("remote", peer.rootURL).With("local", localAddr).Infof("Peer %v disconnected", peer.rootURL) - peerAddr := "" + peerAddr := peer.OriginAddress() // we might be able to get addr out of conn, or it might be closed - if peer.conn != nil { + if peerAddr == "" && peer.conn != nil { paddr := peer.conn.RemoteAddr() if paddr != nil { peerAddr = justHost(paddr.String()) From 97c85b1e39d402105f9d86a8e4da0cd78702a168 Mon Sep 17 00:00:00 2001 From: Tsachi Herman Date: Tue, 25 Jun 2019 18:51:09 -0400 Subject: [PATCH 4/6] Add ForceRelayMessages flag & unit test. (#89) --- config/config.go | 5 ++- installer/config.json.example | 3 +- network/wsNetwork.go | 3 +- network/wsNetwork_test.go | 83 +++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 7a69814820..3ff54e93e0 100644 --- a/config/config.go +++ b/config/config.go @@ -669,7 +669,7 @@ type Local struct { // the max size the sync server would return TxSyncServeResponseSize int - // IsIndexerActive indicates wheather to activate the indexer for fast retrieval of transactions + // IsIndexerActive indicates whether to activate the indexer for fast retrieval of transactions // Note -- Indexer cannot operate on non Archival nodes IsIndexerActive bool @@ -678,6 +678,9 @@ type Local struct { // proxy vendor provides another header field. In the case of CloudFlare proxy, the "CF-Connecting-IP" header // field can be used. UseXForwardedForAddressField string + + // ForceRelayMessages indicates whether the network library relay messages even in the case that no NetAddress was specified. + ForceRelayMessages bool } // Filenames of config files within the configdir (e.g. ~/.algorand) diff --git a/installer/config.json.example b/installer/config.json.example index c1124aa248..c32703f3f9 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -36,5 +36,6 @@ "TxPoolSize": 50000, "TxSyncIntervalSeconds": 60, "TxSyncServeResponseSize": 1000000, - "TxSyncTimeoutSeconds": 30 + "TxSyncTimeoutSeconds": 30, + "ForceRelayMessages": false } diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 8c84f85f58..e2ad64e288 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -521,6 +521,7 @@ func (wn *WebsocketNetwork) setup() { wn.server.IdleTimeout = httpServerIdleTimeout wn.server.MaxHeaderBytes = httpServerMaxHeaderBytes wn.ctx, wn.ctxCancel = context.WithCancel(context.Background()) + wn.relayMessages = wn.config.NetAddress != "" || wn.config.ForceRelayMessages // roughly estimate the number of messages that could be sent over the lifespan of a single round. wn.outgoingMessagesBufferSize = int(config.Consensus[protocol.ConsensusCurrentVersion].NumProposers*2 + config.Consensus[protocol.ConsensusCurrentVersion].SoftCommitteeSize + @@ -1532,8 +1533,6 @@ func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebook Phon outerPhonebook := &MultiPhonebook{phonebooks: []Phonebook{phonebook}} wn = &WebsocketNetwork{log: log, config: config, phonebook: outerPhonebook, GenesisID: genesisID, NetworkID: networkID} - // TODO - add config parameter to allow non-relays to enable relaying. - wn.relayMessages = config.NetAddress != "" wn.setup() return wn, nil } diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index b9dd648fd1..3544171399 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1464,3 +1464,86 @@ func TestSlowPeerDisconnection(t *testing.T) { time.Sleep(time.Millisecond * 5) } } + +func TestForceMessageRelaying(t *testing.T) { + log := logging.TestingLog(t) + log.SetLevel(logging.Level(defaultConfig.BaseLoggerDebugLevel)) + wn := &WebsocketNetwork{ + log: log, + config: defaultConfig, + phonebook: emptyPhonebookSingleton, + GenesisID: "go-test-network-genesis", + NetworkID: config.Devtestnet, + } + wn.setup() + wn.eventualReadyDelay = time.Second + + netA := wn + netA.config.GossipFanout = 1 + + defer func() { t.Log("stopping A"); netA.Stop(); t.Log("A done") }() + + counter := newMessageCounter(t, 5) + counterDone := counter.done + netA.RegisterHandlers([]TaggedMessageHandler{TaggedMessageHandler{Tag: debugTag, MessageHandler: counter}}) + netA.Start() + addrA, postListen := netA.Address() + require.Truef(t, postListen, "Listening network failed to start") + + noAddressConfig := defaultConfig + noAddressConfig.NetAddress = "" + netB := makeTestWebsocketNodeWithConfig(t, noAddressConfig) + netB.config.GossipFanout = 1 + netB.phonebook = &oneEntryPhonebook{addrA} + netB.Start() + defer func() { t.Log("stopping B"); netB.Stop(); t.Log("B done") }() + + noAddressConfig.ForceRelayMessages = true + netC := makeTestWebsocketNodeWithConfig(t, noAddressConfig) + netC.config.GossipFanout = 1 + netC.phonebook = &oneEntryPhonebook{addrA} + netC.Start() + defer func() { t.Log("stopping C"); netB.Stop(); t.Log("C done") }() + + readyTimeout := time.NewTimer(2 * time.Second) + waitReady(t, netA, readyTimeout.C) + waitReady(t, netB, readyTimeout.C) + waitReady(t, netC, readyTimeout.C) + + // send 5 messages from both netB and netC to netA + for i := 0; i < 5; i++ { + err := netB.Relay(context.Background(), debugTag, []byte{1, 2, 3}, true, nil) + require.NoError(t, err) + err = netC.Relay(context.Background(), debugTag, []byte{1, 2, 3}, true, nil) + require.NoError(t, err) + } + + select { + case <-counterDone: + case <-time.After(2 * time.Second): + if counter.count < 5 { + require.Failf(t, "One or more messages failed to reach destination network", "%d > %d", 5, counter.count) + } else if counter.count > 5 { + require.Failf(t, "One or more messages that were expected to be dropped, reached destination network", "%d < %d", 5, counter.count) + } + } + netA.ClearHandlers() + counter = newMessageCounter(t, 10) + counterDone = counter.done + netA.RegisterHandlers([]TaggedMessageHandler{TaggedMessageHandler{Tag: debugTag, MessageHandler: counter}}) + + // hack the relayMessages on the netB so that it would start sending messages. + netB.relayMessages = true + // send additional 10 messages from netB + for i := 0; i < 10; i++ { + err := netB.Relay(context.Background(), debugTag, []byte{1, 2, 3}, true, nil) + require.NoError(t, err) + } + + select { + case <-counterDone: + case <-time.After(2 * time.Second): + require.Failf(t, "One or more messages failed to reach destination network", "%d > %d", 10, counter.count) + } + +} From d722a8c3b6dcb240080a3e2e3109c282d2c92b19 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 26 Jun 2019 11:00:07 -0400 Subject: [PATCH 5/6] Accept API Token in Authorization Header (#90) Accept API tokens provided in a bearer token format. --- daemon/algod/api/server/lib/middlewares/auth.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/daemon/algod/api/server/lib/middlewares/auth.go b/daemon/algod/api/server/lib/middlewares/auth.go index 0ba7e20eab..28b1770838 100644 --- a/daemon/algod/api/server/lib/middlewares/auth.go +++ b/daemon/algod/api/server/lib/middlewares/auth.go @@ -74,6 +74,13 @@ func Auth(log logging.Logger, apiToken string) func(http.Handler) http.Handler { // Handle debug route // Grab the apiToken from the HTTP header providedToken := []byte(r.Header.Get(TokenHeader)) + if len(providedToken) == 0 { + // Accept tokens provided in a bearer token format. + authentication := strings.SplitN(r.Header.Get("Authorization"), " ", 2) + if len(authentication) == 2 && strings.EqualFold("Bearer", authentication[0]) { + providedToken = []byte(authentication[1]) + } + } if route.GetName() == debugRouteName { // For debug routes, we place the apiToken in the path itself providedToken = []byte(mux.Vars(r)["apiToken"]) From 7b151730b65f5316eab515058ad3e83dea414581 Mon Sep 17 00:00:00 2001 From: Benjamin Chan Date: Wed, 26 Jun 2019 12:10:41 -0400 Subject: [PATCH 6/6] Add missing -race flag to build-race make target (#86) We were compiling without golang race detector when we intended the opposite for e2e tests. In addition, this change: - tries to kill algod processes before e2e tests panic, to stop littering machines on failure - fixes a kmd data race due to value receivers on kmd server - excludes kmd binary from -race compilation for e2e tests. Notably, computing Scrypt.Key in memory/cpu-constrained environments took the order of minutes when the race detector is enabled, essentially breaking kmd if compiled with -race, and causing test failures. --- Makefile | 7 ++++++- daemon/kmd/server/server.go | 16 +++++++--------- test/framework/fixtures/libgoalFixture.go | 8 ++++++++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 141a378eb6..d8fbe60842 100644 --- a/Makefile +++ b/Makefile @@ -157,12 +157,17 @@ buildsrc: $(SRCPATH)/crypto/lib/libsodium.a node_exporter NONGO_BIN deps $(ALGOD cd $(SRCPATH) && \ go vet $(UNIT_TEST_SOURCES) $(E2E_TEST_SOURCES) +SOURCES_RACE := github.com/algorand/go-algorand/cmd/kmd + ## Build binaries with the race detector enabled in them. ## This allows us to run e2e tests with race detection. +## We overwrite bin-race/kmd with a non -race version due to +## the incredible performance impact of -race on Scrypt. build-race: build @mkdir -p $(GOPATH)/bin-race cd $(SRCPATH) && \ - GOBIN=$(GOPATH)/bin-race go install $(GOTAGS) -ldflags="$(GOLDFLAGS)" $(SOURCES) + GOBIN=$(GOPATH)/bin-race go install $(GOTAGS) -race -ldflags="$(GOLDFLAGS)" $(SOURCES) && \ + GOBIN=$(GOPATH)/bin-race go install $(GOTAGS) -ldflags="$(GOLDFLAGS)" $(SOURCES_RACE) NONGO_BIN_FILES=$(GOPATH)/bin/find-nodes.sh $(GOPATH)/bin/update.sh $(GOPATH)/bin/updatekey.json $(GOPATH)/bin/COPYING diff --git a/daemon/kmd/server/server.go b/daemon/kmd/server/server.go index cd4d9b9662..e3298fea1e 100644 --- a/daemon/kmd/server/server.go +++ b/daemon/kmd/server/server.go @@ -101,15 +101,13 @@ func ValidateConfig(cfg WalletServerConfig) error { // MakeWalletServer takes a WalletServerConfig, and returns a validated, // configured WalletServer. -func MakeWalletServer(config WalletServerConfig) (WalletServer, error) { - var ws WalletServer - +func MakeWalletServer(config WalletServerConfig) (*WalletServer, error) { err := ValidateConfig(config) if err != nil { - return ws, err + return nil, err } - ws = WalletServer{ + ws := &WalletServer{ WalletServerConfig: config, netPath: filepath.Join(config.DataDir, NetFilename), pidPath: filepath.Join(config.DataDir, PIDFilename), @@ -144,7 +142,7 @@ func (ws *WalletServer) releaseFileLock() error { } // Write out a file containing the address kmd is listening on -func (ws WalletServer) writeStateFiles(netAddr string) (err error) { +func (ws *WalletServer) writeStateFiles(netAddr string) (err error) { // netPath file contains path to sock file err = ioutil.WriteFile(ws.netPath, []byte(netAddr), 0640) if err != nil { @@ -156,7 +154,7 @@ func (ws WalletServer) writeStateFiles(netAddr string) (err error) { } // Delete the state files generated by writeStateFiles -func (ws WalletServer) deleteStateFiles() { +func (ws *WalletServer) deleteStateFiles() { os.Remove(ws.pidPath) os.Remove(ws.netPath) } @@ -164,7 +162,7 @@ func (ws WalletServer) deleteStateFiles() { // makeWatchdogCallback generates a callback function that either 1. does // nothing if ws.Timeout is nil, or 2. kicks a watchdog timer that will kill // kmd when it expires. -func (ws WalletServer) makeWatchdogCallback(kill chan os.Signal) func() { +func (ws *WalletServer) makeWatchdogCallback(kill chan os.Signal) func() { // If Timeout is nil, then we will not kill kmd after a timeout if ws.Timeout == nil { return func() {} @@ -193,7 +191,7 @@ func (ws WalletServer) makeWatchdogCallback(kill chan os.Signal) func() { // returns an error if it was unable to start the server. It reads from the // `kill` channel in order to shut down the server gracefully, and returns a // `died` channel that will be written after the server exits. -func (ws WalletServer) Start(kill chan os.Signal) (died chan error, sock string, err error) { +func (ws *WalletServer) Start(kill chan os.Signal) (died chan error, sock string, err error) { // Ensure we're the only instance of kmd running in this data directory err = ws.acquireFileLock() if err != nil { diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 0b29c7e922..200216f0dc 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -278,6 +278,14 @@ func (f *LibGoalFixture) ShutdownImpl(preserveData bool) { } } +// intercept baseFixture.failOnError so we can clean up any algods that are still alive +func (f *LibGoalFixture) failOnError(err error, message string) { + if err != nil { + f.network.Stop(f.binDir) + f.baseFixture.failOnError(err, message) + } +} + // PrimaryDataDir returns the data directory for the PrimaryNode for the network func (f *LibGoalFixture) PrimaryDataDir() string { return f.network.PrimaryDataDir()