mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-03 07:26:45 +00:00
New NTP Client (#277)
* WIP * Added check for time out of sync * merged upstream/master * added tests * Increased configuration options for NTPclient and test coverage * removed unneeded config save at end of ntp update * Added test for empty response to confirm it will loop * formatting correction * converted to pointer to allow for default allowance settings to be checked * added readme for NTP server * corrected some formatting * updated configtest negativedifference value * gofmt config_test.go for correct import order * corrected typo value in test * bugfix for windows newline and changes based on PR feedback * added minus sign to output * fixed negative number input * Fixed spelling mistakes and removed redundant test * reverted back to a positive number in the config instead of negative for allowednegativedifference * restructured code for cleaner output
This commit is contained in:
@@ -211,6 +211,23 @@ comm method and add in your contact list if available.
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
## Configure Network Time Server
|
||||
|
||||
+ To configure and enable a NTP server you need to set the "enabled" field to one of 3 values -1 is disabled 0 is enabled and alert at start up 1 is enabled and warn at start up
|
||||
servers are configured by the pool array and attempted first to last allowedDifference and allowedNegativeDifference are how far ahead and behind is acceptable for the time to be out in nanoseconds
|
||||
|
||||
```js
|
||||
"ntpclient": {
|
||||
"enabled": 0,
|
||||
"pool": [
|
||||
"pool.ntp.org:123"
|
||||
],
|
||||
"allowedDifference": 0,
|
||||
"allowedNegativeDifference": 0
|
||||
},
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
## Contribution
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -32,6 +35,8 @@ const (
|
||||
configPairsLastUpdatedWarningThreshold = 30 // 30 days
|
||||
configDefaultHTTPTimeout = time.Second * 15
|
||||
configMaxAuthFailres = 3
|
||||
defaultNTPAllowedDifference = 50000000
|
||||
defaultNTPAllowedNegativeDifference = 50000000
|
||||
)
|
||||
|
||||
// Constants here hold some messages
|
||||
@@ -104,6 +109,7 @@ type Config struct {
|
||||
GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"`
|
||||
Logging log.Logging `json:"logging"`
|
||||
Profiler ProfilerConfig `json:"profiler"`
|
||||
NTPClient NTPClientConfig `json:"ntpclient"`
|
||||
Currency CurrencyConfig `json:"currencyConfig"`
|
||||
Communications CommunicationsConfig `json:"communications"`
|
||||
Portfolio portfolio.Base `json:"portfolioAddresses"`
|
||||
@@ -122,6 +128,13 @@ type ProfilerConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type NTPClientConfig struct {
|
||||
Level int `json:"enabled"`
|
||||
Pool []string `json:"pool"`
|
||||
AllowedDifference *time.Duration `json:"allowedDifference"`
|
||||
AllowedNegativeDifference *time.Duration `json:"allowedNegativeDifference"`
|
||||
}
|
||||
|
||||
// ExchangeConfig holds all the information needed for each enabled Exchange.
|
||||
type ExchangeConfig struct {
|
||||
Name string `json:"name"`
|
||||
@@ -1086,6 +1099,62 @@ func (c *Config) CheckLoggerConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckNTPConfig() checks for missing or incorrectly configured NTPClient and recreates with known safe defaults
|
||||
func (c *Config) CheckNTPConfig() {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if c.NTPClient.AllowedDifference == nil || *c.NTPClient.AllowedDifference == 0 {
|
||||
c.NTPClient.AllowedDifference = new(time.Duration)
|
||||
*c.NTPClient.AllowedDifference = defaultNTPAllowedDifference
|
||||
}
|
||||
|
||||
if c.NTPClient.AllowedNegativeDifference == nil || *c.NTPClient.AllowedNegativeDifference <= 0 {
|
||||
c.NTPClient.AllowedNegativeDifference = new(time.Duration)
|
||||
*c.NTPClient.AllowedNegativeDifference = defaultNTPAllowedNegativeDifference
|
||||
}
|
||||
|
||||
if len(c.NTPClient.Pool) < 1 {
|
||||
log.Warn("NTPClient enabled with no servers configured enabling default pool")
|
||||
c.NTPClient.Pool = []string{"pool.ntp.org:123"}
|
||||
}
|
||||
}
|
||||
|
||||
// DisableNTPCheck() allows the user to change how they are prompted for timesync alerts
|
||||
func (c *Config) DisableNTPCheck(input io.Reader) (string, error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
reader := bufio.NewReader(input)
|
||||
log.Warn("Your system time is out of sync this may cause issues with trading")
|
||||
log.Warn("How would you like to show future notifications? (a)lert / (w)arn / (d)isable \n")
|
||||
|
||||
var answered = false
|
||||
for ok := true; ok; ok = (!answered) {
|
||||
answer, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
answer = strings.TrimRight(answer, "\r\n")
|
||||
switch answer {
|
||||
case "a":
|
||||
c.NTPClient.Level = 0
|
||||
answered = true
|
||||
return "Time sync has been set to alert", nil
|
||||
case "w":
|
||||
c.NTPClient.Level = 1
|
||||
answered = true
|
||||
return "Time sync has been set to warn only", nil
|
||||
case "d":
|
||||
c.NTPClient.Level = -1
|
||||
answered = true
|
||||
return "Future notications for out time sync have been disabled", nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("something went wrong NTPCheck should never make it this far")
|
||||
}
|
||||
|
||||
// GetFilePath returns the desired config file or the default config file name
|
||||
// based on if the application is being run under test or normal mode.
|
||||
func GetFilePath(file string) (string, error) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/currency"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
"github.com/thrasher-/gocryptotrader/ntpclient"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -967,3 +969,61 @@ func TestCheckLoggerConfig(t *testing.T) {
|
||||
t.Errorf("Failed to create logger with user settings: reason: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableNTPCheck(t *testing.T) {
|
||||
c := GetConfig()
|
||||
err := c.LoadConfig(ConfigTestFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
warn, err := c.DisableNTPCheck(strings.NewReader("w\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("test failed to create ntpclient failed reason: %v", err)
|
||||
}
|
||||
|
||||
if warn != "Time sync has been set to warn only" {
|
||||
t.Errorf("failed expected %v got %v", "Time sync has been set to warn only", warn)
|
||||
}
|
||||
alert, _ := c.DisableNTPCheck(strings.NewReader("a\n"))
|
||||
if alert != "Time sync has been set to alert" {
|
||||
t.Errorf("failed expected %v got %v", "Time sync has been set to alert", alert)
|
||||
}
|
||||
|
||||
disable, _ := c.DisableNTPCheck(strings.NewReader("d\n"))
|
||||
if disable != "Future notications for out time sync have been disabled" {
|
||||
t.Errorf("failed expected %v got %v", "Future notications for out time sync have been disabled", disable)
|
||||
}
|
||||
|
||||
_, err = c.DisableNTPCheck(strings.NewReader(" "))
|
||||
if err.Error() != "EOF" {
|
||||
t.Errorf("failed expected EOF got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckNTPConfig(t *testing.T) {
|
||||
c := GetConfig()
|
||||
|
||||
c.NTPClient.Level = 0
|
||||
c.NTPClient.Pool = nil
|
||||
c.NTPClient.AllowedNegativeDifference = nil
|
||||
c.NTPClient.AllowedDifference = nil
|
||||
|
||||
c.CheckNTPConfig()
|
||||
_, err := ntpclient.NTPClient(c.NTPClient.Pool)
|
||||
if err != nil {
|
||||
t.Fatalf("test failed to create ntpclient failed reason: %v", err)
|
||||
}
|
||||
|
||||
if c.NTPClient.Pool[0] != "pool.ntp.org:123" {
|
||||
t.Error("ntpclient with no valid pool should default to pool.ntp.org ")
|
||||
}
|
||||
|
||||
if c.NTPClient.AllowedDifference == nil {
|
||||
t.Error("ntpclient with nil alloweddifference should default to sane value")
|
||||
}
|
||||
|
||||
if c.NTPClient.AllowedNegativeDifference == nil {
|
||||
t.Error("ntpclient with nil allowednegativedifference should default to sane value")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
"profiler": {
|
||||
"enabled": false
|
||||
},
|
||||
"ntpclient": {
|
||||
"enabled": 0,
|
||||
"pool": [
|
||||
"pool.ntp.org:123"
|
||||
],
|
||||
"allowedDifference": 50000000,
|
||||
"allowedNegativeDifference": 50000000
|
||||
},
|
||||
"currencyConfig": {
|
||||
"forexProviders": [
|
||||
{
|
||||
|
||||
26
main.go
26
main.go
@@ -9,6 +9,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/communications"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/thrasher-/gocryptotrader/currency/coinmarketcap"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
"github.com/thrasher-/gocryptotrader/ntpclient"
|
||||
"github.com/thrasher-/gocryptotrader/portfolio"
|
||||
)
|
||||
|
||||
@@ -103,6 +105,30 @@ func main() {
|
||||
log.Errorf("Failed to setup logger reason: %s", err)
|
||||
}
|
||||
|
||||
if bot.config.NTPClient.Level != -1 {
|
||||
bot.config.CheckNTPConfig()
|
||||
NTPTime, errNTP := ntpclient.NTPClient(bot.config.NTPClient.Pool)
|
||||
currentTime := time.Now()
|
||||
if errNTP != nil {
|
||||
log.Warnf("NTPClient failed to create: %v", errNTP)
|
||||
} else {
|
||||
NTPcurrentTimeDifference := NTPTime.Sub(currentTime)
|
||||
configNTPTime := *bot.config.NTPClient.AllowedDifference
|
||||
configNTPNegativeTime := (*bot.config.NTPClient.AllowedNegativeDifference - (*bot.config.NTPClient.AllowedNegativeDifference * 2))
|
||||
if NTPcurrentTimeDifference > configNTPTime || NTPcurrentTimeDifference < configNTPNegativeTime {
|
||||
log.Warnf("Time out of sync (NTP): %v | (time.Now()): %v | (Difference): %v | (Allowed): +%v / %v", NTPTime, currentTime, NTPcurrentTimeDifference, configNTPTime, configNTPNegativeTime)
|
||||
if bot.config.NTPClient.Level == 0 {
|
||||
disable, errNTP := bot.config.DisableNTPCheck(os.Stdin)
|
||||
if errNTP != nil {
|
||||
log.Errorf("failed to disable ntp time check reason: %v", err)
|
||||
} else {
|
||||
log.Info(disable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AdjustGoMaxProcs()
|
||||
log.Debugf("Bot '%s' started.\n", bot.config.Name)
|
||||
log.Debugf("Bot dry run mode: %v.\n", common.IsEnabled(bot.dryRun))
|
||||
|
||||
59
ntpclient/ntpclient.go
Normal file
59
ntpclient/ntpclient.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package ntpclient
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
type ntppacket struct {
|
||||
Settings uint8 // leap yr indicator, ver number, and mode
|
||||
Stratum uint8 // stratum of local clock
|
||||
Poll int8 // poll exponent
|
||||
Precision int8 // precision exponent
|
||||
RootDelay uint32 // root delay
|
||||
RootDispersion uint32 // root dispersion
|
||||
ReferenceID uint32 // reference id
|
||||
RefTimeSec uint32 // reference timestamp sec
|
||||
RefTimeFrac uint32 // reference timestamp fractional
|
||||
OrigTimeSec uint32 // origin time secs
|
||||
OrigTimeFrac uint32 // origin time fractional
|
||||
RxTimeSec uint32 // receive time secs
|
||||
RxTimeFrac uint32 // receive time frac
|
||||
TxTimeSec uint32 // transmit time secs
|
||||
TxTimeFrac uint32 // transmit time frac
|
||||
}
|
||||
|
||||
// NTPClient create's a new NTPClient and returns local based on ntp servers provided timestamp
|
||||
func NTPClient(pool []string) (time.Time, error) {
|
||||
for i := range pool {
|
||||
con, err := net.Dial("udp", pool[i])
|
||||
if err != nil {
|
||||
log.Warnf("Unable to connect to hosts %v attempting next", pool[i])
|
||||
continue
|
||||
}
|
||||
|
||||
defer con.Close()
|
||||
|
||||
con.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
|
||||
req := &ntppacket{Settings: 0x1B}
|
||||
if err := binary.Write(con, binary.BigEndian, req); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rsp := &ntppacket{}
|
||||
if err := binary.Read(con, binary.BigEndian, rsp); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
secs := float64(rsp.TxTimeSec) - 2208988800
|
||||
nanos := (int64(rsp.TxTimeFrac) * 1e9) >> 32
|
||||
|
||||
return time.Unix(int64(secs), nanos), nil
|
||||
}
|
||||
return time.Unix(0, 0), errors.New("no valid time servers")
|
||||
}
|
||||
25
ntpclient/ntpclient_test.go
Normal file
25
ntpclient/ntpclient_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package ntpclient
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNTPClient(t *testing.T) {
|
||||
pool := []string{"pool.ntp.org:123", "0.pool.ntp.org:123"}
|
||||
_, err := NTPClient(pool)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get time %v", err)
|
||||
}
|
||||
|
||||
invalidpool := []string{"pool.thisisinvalid.org"}
|
||||
_, err = NTPClient(invalidpool)
|
||||
if err == nil {
|
||||
t.Errorf("failed to get time %v", err)
|
||||
}
|
||||
|
||||
firstInvalid := []string{"pool.thisisinvalid.org", "pool.ntp.org:123", "0.pool.ntp.org:123"}
|
||||
_, err = NTPClient(firstInvalid)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get time %v", err)
|
||||
}
|
||||
}
|
||||
9
testdata/configtest.json
vendored
9
testdata/configtest.json
vendored
@@ -12,6 +12,15 @@
|
||||
"profiler": {
|
||||
"enabled": false
|
||||
},
|
||||
"ntpclient": {
|
||||
"enabled": 0,
|
||||
"pool": [
|
||||
"0.pool.ntp.org:123",
|
||||
"pool.ntp.org:123"
|
||||
],
|
||||
"allowedDifference": 50000000,
|
||||
"allowedNegativeDifference": 50000000
|
||||
},
|
||||
"currencyConfig": {
|
||||
"forexProviders": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user