379 lines
12 KiB
Go
379 lines
12 KiB
Go
// Copyright 2017-2021 DERO Project. All rights reserved.
|
|
// Use of this source code in any form is governed by RESEARCH license.
|
|
// license can be found in the LICENSE file.
|
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
|
//
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package globals
|
|
|
|
import "io"
|
|
import "os"
|
|
import "fmt"
|
|
import "time"
|
|
import "math"
|
|
import "net/url"
|
|
import "strings"
|
|
import "strconv"
|
|
import "math/big"
|
|
import "path/filepath"
|
|
import "runtime/debug"
|
|
import "golang.org/x/net/proxy"
|
|
|
|
import "go.uber.org/zap"
|
|
import "go.uber.org/zap/zapcore"
|
|
import "github.com/go-logr/logr"
|
|
import "github.com/go-logr/zapr"
|
|
import "github.com/robfig/cron/v3"
|
|
|
|
import "github.com/deroproject/derohe/config"
|
|
import "github.com/deroproject/derohe/rpc"
|
|
|
|
// all the the global variables used by the program are stored here
|
|
// since the entire logic is designed around a state machine driven by external events
|
|
// once the core starts nothing changes until there is a network state change
|
|
|
|
var Subsystem_Active uint32 // atomic counter to show how many subsystems are active
|
|
var Exit_In_Progress bool
|
|
|
|
// on init this variable is updated to setup global config in 1 go
|
|
var Config config.CHAIN_CONFIG = config.Mainnet // default is mainnnet
|
|
|
|
// global logger all components will use it with context
|
|
var Logger logr.Logger = logr.Discard() // default discard all logs
|
|
|
|
var ClockOffset time.Duration // actual clock offset that is used by the daemon
|
|
var ClockOffsetNTP time.Duration // clockoffset in reference to ntp servers
|
|
var ClockOffsetP2P time.Duration // clockoffset in reference to p2p averging
|
|
var TimeIsInSync bool // whether time is in sync, if yes we do not use any clock offset but still we keep calculating them
|
|
var TimeIsInSyncNTP bool
|
|
|
|
// get current time with clock offset applied
|
|
func Time() time.Time {
|
|
if TimeIsInSync {
|
|
return time.Now()
|
|
}
|
|
if TimeIsInSyncNTP {
|
|
return time.Now().Add(ClockOffsetNTP)
|
|
}
|
|
return time.Now()
|
|
//return time.Now().Add(ClockOffsetP2P) // this is the last effort
|
|
}
|
|
|
|
// skip p2p offset
|
|
func TimeSkipP2P() time.Time {
|
|
if TimeIsInSync {
|
|
return time.Now()
|
|
}
|
|
if TimeIsInSyncNTP {
|
|
return time.Now().Add(ClockOffsetNTP)
|
|
}
|
|
return time.Now()
|
|
}
|
|
|
|
func GetOffset() time.Duration {
|
|
return time.Now().Sub(Time())
|
|
}
|
|
func GetOffsetNTP() time.Duration {
|
|
return ClockOffsetNTP
|
|
}
|
|
func GetOffsetP2P() time.Duration {
|
|
return ClockOffsetP2P
|
|
}
|
|
|
|
var Cron = cron.New(cron.WithChain(
|
|
cron.Recover(Logger), // or use cron.DefaultLogger
|
|
))
|
|
|
|
var Dialer proxy.Dialer = proxy.Direct // for proxy and direct connections
|
|
// all outgoing connections , including DNS requests must be made using this
|
|
|
|
// all program arguments are available here
|
|
var Arguments = map[string]interface{}{}
|
|
|
|
func InitNetwork() {
|
|
Config = config.Mainnet // default is mainnnet
|
|
if Arguments["--testnet"].(bool) == true { // setup testnet if requested
|
|
Config = config.Testnet
|
|
}
|
|
|
|
}
|
|
|
|
// these 2 global variables control all log levels
|
|
var Log_Level_Console = zap.NewAtomicLevelAt(zapcore.Level(0)) // default info level
|
|
var Log_Level_File = zap.NewAtomicLevelAt(zapcore.Level(-1)) // default debug level
|
|
|
|
// remove caller information from console
|
|
type removeCallerCore struct {
|
|
zapcore.Core
|
|
}
|
|
|
|
func (c *removeCallerCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
|
if c.Core.Check(entry, nil) == nil {
|
|
return ce
|
|
}
|
|
return ce.AddCore(entry, c)
|
|
}
|
|
func (c *removeCallerCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
|
entry.Caller = zapcore.EntryCaller{}
|
|
return c.Core.Write(entry, fields)
|
|
}
|
|
func (c *removeCallerCore) With(fields []zap.Field) zapcore.Core {
|
|
return &removeCallerCore{c.Core.With(fields)}
|
|
}
|
|
|
|
func InitializeLog(console, logfile io.Writer) {
|
|
|
|
if Arguments["--debug"] != nil && Arguments["--debug"].(bool) == true { // setup debug mode if requested
|
|
Log_Level_Console = zap.NewAtomicLevelAt(zapcore.Level(-1))
|
|
}
|
|
|
|
if Arguments["--clog-level"] != nil { // setup log level if requested
|
|
var log_level int8
|
|
fmt.Sscan(Arguments["--clog-level"].(string), &log_level)
|
|
if log_level < 0 {
|
|
log_level = 0
|
|
}
|
|
if log_level > 127 {
|
|
log_level = 127
|
|
}
|
|
Log_Level_Console = zap.NewAtomicLevelAt(zapcore.Level(0 - log_level))
|
|
}
|
|
|
|
if Arguments["--flog-level"] != nil { // setup log level if requested
|
|
var log_level int8
|
|
fmt.Sscan(Arguments["--flog-level"].(string), &log_level)
|
|
if log_level < 0 {
|
|
log_level = 0
|
|
}
|
|
if log_level > 127 {
|
|
log_level = 127
|
|
}
|
|
Log_Level_File = zap.NewAtomicLevelAt(zapcore.Level(0 - log_level))
|
|
}
|
|
|
|
zf := zap.NewDevelopmentEncoderConfig()
|
|
zc := zap.NewDevelopmentEncoderConfig()
|
|
zc.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
|
zc.EncodeTime = zapcore.TimeEncoderOfLayout("02/01 15:04:05")
|
|
|
|
file_encoder := zapcore.NewJSONEncoder(zf)
|
|
console_encoder := zapcore.NewConsoleEncoder(zc)
|
|
|
|
core_console := zapcore.NewCore(console_encoder, zapcore.AddSync(console), Log_Level_Console)
|
|
removecore := &removeCallerCore{core_console}
|
|
core := zapcore.NewTee(
|
|
removecore,
|
|
zapcore.NewCore(file_encoder, zapcore.AddSync(logfile), Log_Level_File),
|
|
)
|
|
|
|
zcore := zap.New(core, zap.AddCaller()) // add caller info to every record which is then trimmed from console
|
|
|
|
Logger = zapr.NewLogger(zcore) // sets up global logger
|
|
//Logger = zapr.NewLoggerWithOptions(zcore,zapr.LogInfoLevel("V")) // if you need verbosity levels
|
|
|
|
// remember -1 is debug, 0 is info
|
|
|
|
}
|
|
func Initialize() {
|
|
var err error
|
|
|
|
Arguments["--testnet"] = true // force testnet every where
|
|
|
|
InitNetwork()
|
|
|
|
// choose socks based proxy if user requested so
|
|
if Arguments["--socks-proxy"] != nil {
|
|
Logger.V(1).Info("Setting up proxy using ", "address", Arguments["--socks-proxy"].(string))
|
|
//uri, err := url.Parse("socks5://127.0.0.1:9000") // "socks5://demo:demo@192.168.99.100:1080"
|
|
uri, err := url.Parse("socks5://" + Arguments["--socks-proxy"].(string)) // "socks5://demo:demo@192.168.99.100:1080"
|
|
if err != nil {
|
|
Logger.Error(err, "Error parsing socks proxy:")
|
|
os.Exit(-1)
|
|
}
|
|
|
|
Dialer, err = proxy.FromURL(uri, proxy.Direct)
|
|
if err != nil {
|
|
Logger.Error(err, "Error creating socks proxy", "address", Arguments["--socks-proxy"].(string))
|
|
}
|
|
}
|
|
|
|
// lets create data directories
|
|
err = os.MkdirAll(GetDataDirectory(), 0750)
|
|
if err != nil {
|
|
fmt.Printf("Error creating/accessing directory %s , err %s\n", GetDataDirectory(), err)
|
|
}
|
|
|
|
}
|
|
|
|
// used to recover in case of panics
|
|
func Recover(level int) (err error) {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("Recovered r:%+v stack %s", r, fmt.Sprintf("%s", string(debug.Stack())))
|
|
Logger.V(level).Error(nil, "Recovered ", "error", r, "stack", fmt.Sprintf("%s", string(debug.Stack())))
|
|
}
|
|
return
|
|
}
|
|
|
|
// tells whether we are in mainnet mode
|
|
// if we are not mainnet, we are a testnet,
|
|
// we will only have a single mainnet ,( but we may have one or more testnets )
|
|
func IsMainnet() bool {
|
|
return Config.Name == "mainnet"
|
|
}
|
|
|
|
// tells whether we are in simulator mode ( both mainnet and testnet coud be simulated)
|
|
func IsSimulator() bool {
|
|
if Arguments["--simulator"] != nil && Arguments["--simulator"].(bool) == true {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// return different directories for different networks ( mainly mainnet, testnet, simulation )
|
|
// this function is specifically for daemon
|
|
func GetDataDirectory() string {
|
|
data_directory, err := os.Getwd()
|
|
if err != nil {
|
|
fmt.Printf("Error obtaining current directory, using temp dir err %s\n", err)
|
|
data_directory = os.TempDir()
|
|
}
|
|
|
|
// if user provided an option, override default
|
|
if Arguments["--data-dir"] != nil {
|
|
data_directory = Arguments["--data-dir"].(string)
|
|
}
|
|
|
|
simulator := ""
|
|
if IsSimulator() {
|
|
simulator = "_simulator" // add _simulator
|
|
}
|
|
|
|
if IsMainnet() {
|
|
return filepath.Join(data_directory, "mainnet"+simulator)
|
|
}
|
|
|
|
return filepath.Join(data_directory, "testnet"+simulator)
|
|
}
|
|
|
|
// never do any division operation on money due to floating point issues
|
|
// newbies, see type the next in python interpretor "3.33-3.13"
|
|
//
|
|
func FormatMoney(amount uint64) string {
|
|
return FormatMoneyPrecision(amount, 5) // default is 5 precision after floating point
|
|
}
|
|
|
|
// 0
|
|
func FormatMoney0(amount uint64) string {
|
|
return FormatMoneyPrecision(amount, 0)
|
|
}
|
|
|
|
//5 precision
|
|
func FormatMoney5(amount uint64) string {
|
|
return FormatMoneyPrecision(amount, 5)
|
|
}
|
|
|
|
//8 precision
|
|
func FormatMoney8(amount uint64) string {
|
|
return FormatMoneyPrecision(amount, 8)
|
|
}
|
|
|
|
// 12 precision
|
|
func FormatMoney12(amount uint64) string {
|
|
return FormatMoneyPrecision(amount, 12) // default is 8 precision after floating point
|
|
}
|
|
|
|
// format money with specific precision
|
|
func FormatMoneyPrecision(amount uint64, precision int) string {
|
|
hard_coded_decimals := new(big.Float).SetInt64(100000)
|
|
float_amount, _, _ := big.ParseFloat(fmt.Sprintf("%d", amount), 10, 0, big.ToZero)
|
|
result := new(big.Float)
|
|
result.Quo(float_amount, hard_coded_decimals)
|
|
return result.Text('f', precision) // 5 is display precision after floating point
|
|
}
|
|
|
|
// this will parse and validate an address, in reference to the current main/test mode
|
|
func ParseValidateAddress(str string) (addr *rpc.Address, err error) {
|
|
addr, err = rpc.NewAddress(strings.TrimSpace(str))
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// check whether the domain is valid
|
|
if !addr.IsDERONetwork() {
|
|
err = fmt.Errorf("Invalid DERO address")
|
|
return
|
|
}
|
|
|
|
if IsMainnet() != addr.IsMainnet() {
|
|
if IsMainnet() {
|
|
err = fmt.Errorf("Address belongs to DERO testnet and is invalid on current network")
|
|
} else {
|
|
err = fmt.Errorf("Address belongs to DERO mainnet and is invalid on current network")
|
|
}
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// this will covert an amount in string form to atomic units
|
|
func ParseAmount(str string) (amount uint64, err error) {
|
|
float_amount, base, err := big.ParseFloat(strings.TrimSpace(str), 10, 0, big.ToZero)
|
|
|
|
if err != nil {
|
|
err = fmt.Errorf("Amount could not be parsed err: %s", err)
|
|
return
|
|
}
|
|
if base != 10 {
|
|
err = fmt.Errorf("Amount should be in base 10 (0123456789)")
|
|
return
|
|
}
|
|
if float_amount.Cmp(new(big.Float).Abs(float_amount)) != 0 { // number and abs(num) not equal means number is neg
|
|
err = fmt.Errorf("Amount cannot be negative")
|
|
return
|
|
}
|
|
|
|
// multiply by 5 zeroes
|
|
hard_coded_decimals := new(big.Float).SetInt64(100000)
|
|
float_amount.Mul(float_amount, hard_coded_decimals)
|
|
|
|
/*if !float_amount.IsInt() {
|
|
err = fmt.Errorf("Amount is invalid %s ", float_amount.Text('f',0))
|
|
return
|
|
}*/
|
|
|
|
// convert amount to uint64
|
|
//amount, _ = float_amount.Uint64() // sanity checks again
|
|
amount, err = strconv.ParseUint(float_amount.Text('f', 0), 10, 64)
|
|
if err != nil {
|
|
err = fmt.Errorf("Amount is invalid %s ", float_amount.Text('f', 0))
|
|
return
|
|
}
|
|
//if amount == 0 {
|
|
// err = fmt.Errorf("0 cannot be transferred")
|
|
// return
|
|
//}
|
|
|
|
if amount == math.MaxUint64 {
|
|
err = fmt.Errorf("Amount is invalid")
|
|
return
|
|
}
|
|
|
|
return // return the number
|
|
}
|
|
|
|
// gets a stack trace of all
|
|
func StackTrace(all bool) string {
|
|
return string(debug.Stack())
|
|
}
|