github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/mobile/mysterium/entrypoint.go (about)

     1  /*
     2   * Copyright (C) 2018 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package mysterium
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"math/big"
    26  	"net/http"
    27  	"path/filepath"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/mysteriumnetwork/node/cmd"
    32  	"github.com/mysteriumnetwork/node/config"
    33  	"github.com/mysteriumnetwork/node/consumer/entertainment"
    34  	"github.com/mysteriumnetwork/node/consumer/migration"
    35  	"github.com/mysteriumnetwork/node/core/connection"
    36  	"github.com/mysteriumnetwork/node/core/connection/connectionstate"
    37  	"github.com/mysteriumnetwork/node/core/discovery/proposal"
    38  	"github.com/mysteriumnetwork/node/core/ip"
    39  	"github.com/mysteriumnetwork/node/core/location"
    40  	"github.com/mysteriumnetwork/node/core/node"
    41  	"github.com/mysteriumnetwork/node/core/quality"
    42  	"github.com/mysteriumnetwork/node/core/service"
    43  	"github.com/mysteriumnetwork/node/core/service/servicestate"
    44  	"github.com/mysteriumnetwork/node/core/state"
    45  	"github.com/mysteriumnetwork/node/eventbus"
    46  	"github.com/mysteriumnetwork/node/feedback"
    47  	"github.com/mysteriumnetwork/node/identity"
    48  	"github.com/mysteriumnetwork/node/identity/registry"
    49  	"github.com/mysteriumnetwork/node/identity/selector"
    50  	"github.com/mysteriumnetwork/node/logconfig"
    51  	"github.com/mysteriumnetwork/node/metadata"
    52  	natprobe "github.com/mysteriumnetwork/node/nat/behavior"
    53  	"github.com/mysteriumnetwork/node/pilvytis"
    54  	"github.com/mysteriumnetwork/node/requests"
    55  	"github.com/mysteriumnetwork/node/router"
    56  	"github.com/mysteriumnetwork/node/services/wireguard"
    57  	wireguard_connection "github.com/mysteriumnetwork/node/services/wireguard/connection"
    58  	"github.com/mysteriumnetwork/node/session/pingpong"
    59  	"github.com/mysteriumnetwork/node/session/pingpong/event"
    60  	pingpongEvent "github.com/mysteriumnetwork/node/session/pingpong/event"
    61  	paymentClient "github.com/mysteriumnetwork/payments/client"
    62  	"github.com/mysteriumnetwork/payments/crypto"
    63  	"github.com/mysteriumnetwork/payments/units"
    64  
    65  	"github.com/rs/zerolog"
    66  )
    67  
    68  // MobileNode represents node object tuned for mobile device.
    69  type MobileNode struct {
    70  	shutdown                  func() error
    71  	node                      *cmd.Node
    72  	stateKeeper               *state.Keeper
    73  	connectionManager         connection.MultiManager
    74  	locationResolver          *location.Cache
    75  	natProber                 natprobe.NATProber
    76  	identitySelector          selector.Handler
    77  	signerFactory             identity.SignerFactory
    78  	identityMover             *identity.Mover
    79  	ipResolver                ip.Resolver
    80  	eventBus                  eventbus.EventBus
    81  	connectionRegistry        *connection.Registry
    82  	proposalsManager          *proposalsManager
    83  	feedbackReporter          *feedback.Reporter
    84  	transactor                *registry.Transactor
    85  	affiliator                *registry.Affiliator
    86  	identityRegistry          registry.IdentityRegistry
    87  	identityChannelCalculator *paymentClient.MultiChainAddressProvider
    88  	consumerBalanceTracker    *pingpong.ConsumerBalanceTracker
    89  	pilvytis                  *pilvytis.API
    90  	pilvytisOrderIssuer       *pilvytis.OrderIssuer
    91  	chainID                   int64
    92  	startTime                 time.Time
    93  	sessionStorage            SessionStorage
    94  	entertainmentEstimator    *entertainment.Estimator
    95  	residentCountry           *identity.ResidentCountry
    96  	filterPresetStorage       *proposal.FilterPresetStorage
    97  	hermesMigrator            *migration.HermesMigrator
    98  	servicesManager           *service.Manager
    99  	earningsProvider          earningsProvider
   100  }
   101  
   102  type earningsProvider interface {
   103  	GetEarningsDetailed(chainID int64, id identity.Identity) *pingpongEvent.EarningsDetailed
   104  }
   105  
   106  // MobileNodeOptions contains common mobile node options.
   107  type MobileNodeOptions struct {
   108  	Network                        string
   109  	KeepConnectedOnFail            bool
   110  	DiscoveryAddress               string
   111  	BrokerAddresses                []string
   112  	EtherClientRPCL1               []string
   113  	EtherClientRPCL2               []string
   114  	FeedbackURL                    string
   115  	QualityOracleURL               string
   116  	IPDetectorURL                  string
   117  	LocationDetectorURL            string
   118  	TransactorEndpointAddress      string
   119  	AffiliatorEndpointAddress      string
   120  	HermesEndpointAddress          string
   121  	Chain1ID                       int64
   122  	Chain2ID                       int64
   123  	ActiveChainID                  int64
   124  	PilvytisAddress                string
   125  	MystSCAddress                  string
   126  	RegistrySCAddress              string
   127  	HermesSCAddress                string
   128  	ChannelImplementationSCAddress string
   129  	CacheTTLSeconds                int
   130  	ObserverAddress                string
   131  	IsProvider                     bool
   132  	TequilapiSecured               bool
   133  	UIFeaturesEnabled              string
   134  }
   135  
   136  // ConsumerPaymentConfig defines consumer side payment configuration
   137  type ConsumerPaymentConfig struct {
   138  	PriceGiBMax  string
   139  	PriceHourMax string
   140  }
   141  
   142  // DefaultNodeOptions returns default options.
   143  func DefaultNodeOptions() *MobileNodeOptions {
   144  	return DefaultNodeOptionsByNetwork(string(config.Mainnet))
   145  }
   146  
   147  // DefaultNodeOptionsByNetwork returns default options by network.
   148  func DefaultNodeOptionsByNetwork(network string) *MobileNodeOptions {
   149  	options := &MobileNodeOptions{
   150  		KeepConnectedOnFail: true,
   151  		FeedbackURL:         "https://feedback.mysterium.network",
   152  		QualityOracleURL:    "https://quality.mysterium.network/api/v3",
   153  		IPDetectorURL:       "https://location.mysterium.network/api/v1/location",
   154  		LocationDetectorURL: "https://location.mysterium.network/api/v1/location",
   155  		CacheTTLSeconds:     5,
   156  	}
   157  
   158  	bcNetwork, err := config.ParseBlockchainNetwork(network)
   159  	if err != nil {
   160  		bcNetwork = config.Mainnet
   161  	}
   162  
   163  	switch bcNetwork {
   164  	case config.Mainnet:
   165  		options.Network = string(config.Mainnet)
   166  		options = setDefinitionOptions(options, metadata.MainnetDefinition)
   167  	case config.Testnet:
   168  		options.Network = string(config.Testnet)
   169  		options = setDefinitionOptions(options, metadata.TestnetDefinition)
   170  	case config.Localnet:
   171  		options.Network = string(config.Localnet)
   172  		options = setDefinitionOptions(options, metadata.LocalnetDefinition)
   173  	}
   174  
   175  	return options
   176  }
   177  
   178  func setDefinitionOptions(options *MobileNodeOptions, definition metadata.NetworkDefinition) *MobileNodeOptions {
   179  	options.DiscoveryAddress = definition.DiscoveryAddress
   180  	options.BrokerAddresses = definition.BrokerAddresses
   181  	options.EtherClientRPCL1 = definition.Chain1.EtherClientRPC
   182  	options.EtherClientRPCL2 = definition.Chain2.EtherClientRPC
   183  	options.TransactorEndpointAddress = definition.TransactorAddress
   184  	options.AffiliatorEndpointAddress = definition.AffiliatorAddress
   185  	options.ActiveChainID = definition.DefaultChainID
   186  	options.Chain1ID = definition.Chain1.ChainID
   187  	options.Chain2ID = definition.Chain2.ChainID
   188  	options.PilvytisAddress = definition.PilvytisAddress
   189  	options.MystSCAddress = definition.Chain2.MystAddress
   190  	options.RegistrySCAddress = definition.Chain2.RegistryAddress
   191  	options.HermesSCAddress = definition.Chain2.HermesID
   192  	options.ChannelImplementationSCAddress = definition.Chain2.ChannelImplAddress
   193  	options.ObserverAddress = definition.ObserverAddress
   194  	return options
   195  }
   196  
   197  // NewNode function creates new Node.
   198  func NewNode(appPath string, options *MobileNodeOptions) (*MobileNode, error) {
   199  	var di cmd.Dependencies
   200  
   201  	if appPath == "" {
   202  		return nil, errors.New("node app path is required")
   203  	}
   204  
   205  	dataDir := filepath.Join(appPath, ".mysterium")
   206  	config.Current.SetDefault(config.FlagDataDir.Name, dataDir)
   207  	currentDir := appPath
   208  	if err := loadUserConfig(dataDir); err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	config.Current.SetDefault(config.FlagChainID.Name, options.ActiveChainID)
   213  	config.Current.SetDefault(config.FlagKeepConnectedOnFail.Name, options.KeepConnectedOnFail)
   214  	config.Current.SetDefault(config.FlagAutoReconnect.Name, "true")
   215  	config.Current.SetDefault(config.FlagDefaultCurrency.Name, metadata.DefaultNetwork.DefaultCurrency)
   216  	config.Current.SetDefault(config.FlagSTUNservers.Name, []string{"stun.l.google.com:19302", "stun1.l.google.com:19302", "stun2.l.google.com:19302"})
   217  	config.Current.SetDefault(config.FlagUDPListenPorts.Name, "10000:60000")
   218  	config.Current.SetDefault(config.FlagStatsReportInterval.Name, time.Second)
   219  	config.Current.SetDefault(config.FlagUIFeatures.Name, options.UIFeaturesEnabled)
   220  	config.Current.SetDefault(config.FlagActiveServices.Name, "scraping")
   221  
   222  	if options.IsProvider {
   223  		config.Current.SetDefault(config.FlagUserspace.Name, "true")
   224  		config.Current.SetDefault(config.FlagAgreedTermsConditions.Name, "true")
   225  		config.Current.SetDefault(config.FlagDiscoveryPingInterval.Name, "3m")
   226  		config.Current.SetDefault(config.FlagDiscoveryFetchInterval.Name, "3m")
   227  		config.Current.SetDefault(config.FlagAccessPolicyFetchInterval.Name, "10m")
   228  		config.Current.SetDefault(config.FlagAccessPolicyFetchingEnabled.Name, "false")
   229  		config.Current.SetDefault(config.FlagPaymentsZeroStakeUnsettledAmount.Name, "5.0")
   230  		config.Current.SetDefault(config.FlagShaperBandwidth.Name, "6250")
   231  
   232  		config.Current.SetDefault(config.FlagPaymentsProviderInvoiceFrequency.Name, config.FlagPaymentsProviderInvoiceFrequency.Value)
   233  		config.Current.SetDefault(config.FlagPaymentsLimitProviderInvoiceFrequency.Name, config.FlagPaymentsLimitProviderInvoiceFrequency.Value)
   234  		config.Current.SetDefault(config.FlagPaymentsUnpaidInvoiceValue.Name, config.FlagPaymentsUnpaidInvoiceValue.Value)
   235  		config.Current.SetDefault(config.FlagPaymentsLimitUnpaidInvoiceValue.Name, config.FlagPaymentsLimitUnpaidInvoiceValue.Value)
   236  		config.Current.SetDefault(config.FlagChain1KnownHermeses.Name, config.FlagChain1KnownHermeses.Value)
   237  		config.Current.SetDefault(config.FlagChain2KnownHermeses.Name, config.FlagChain2KnownHermeses.Value)
   238  		config.Current.SetDefault(config.FlagDNSListenPort.Name, config.FlagDNSListenPort.Value)
   239  
   240  		config.Current.SetDefault(config.FlagUIFeatures.Name, "android_mobile_node,android_sso_deeplink,disableUpdateNotifications")
   241  	}
   242  
   243  	bcNetwork, err := config.ParseBlockchainNetwork(options.Network)
   244  	if err != nil {
   245  		return nil, fmt.Errorf("invalid bc network: %w", err)
   246  	}
   247  	config.Current.SetDefaultsByNetwork(bcNetwork)
   248  
   249  	network := node.OptionsNetwork{
   250  		Network:          bcNetwork,
   251  		DiscoveryAddress: options.DiscoveryAddress,
   252  		BrokerAddresses:  options.BrokerAddresses,
   253  		EtherClientRPCL1: options.EtherClientRPCL1,
   254  		EtherClientRPCL2: options.EtherClientRPCL2,
   255  		ChainID:          options.ActiveChainID,
   256  		DNSMap: map[string][]string{
   257  			"location.mysterium.network": {"51.158.129.204"},
   258  			"quality.mysterium.network":  {"51.158.129.204"},
   259  			"feedback.mysterium.network": {"116.203.17.150"},
   260  			"api.ipify.org": {
   261  				"54.204.14.42", "54.225.153.147", "54.235.83.248", "54.243.161.145",
   262  				"23.21.109.69", "23.21.126.66",
   263  				"50.19.252.36",
   264  				"174.129.214.20",
   265  			},
   266  		},
   267  	}
   268  	if options.IsProvider {
   269  		network.DNSMap["badupnp.benjojo.co.uk"] = []string{"104.22.70.70", "104.22.71.70", "172.67.25.154"}
   270  	}
   271  	logOptions := logconfig.LogOptions{
   272  		LogLevel: zerolog.DebugLevel,
   273  		LogHTTP:  false,
   274  		Filepath: filepath.Join(dataDir, "mysterium-node"),
   275  	}
   276  
   277  	nodeOptions := node.Options{
   278  		Mobile:     true,
   279  		LogOptions: logOptions,
   280  		Directories: node.OptionsDirectory{
   281  			Data:     dataDir,
   282  			Storage:  filepath.Join(dataDir, "db"),
   283  			Keystore: filepath.Join(dataDir, "keystore"),
   284  			Runtime:  currentDir,
   285  		},
   286  
   287  		SwarmDialerDNSHeadstart: time.Millisecond * 1500,
   288  		Keystore: node.OptionsKeystore{
   289  			UseLightweight: true,
   290  		},
   291  		FeedbackURL:    options.FeedbackURL,
   292  		OptionsNetwork: network,
   293  		Quality: node.OptionsQuality{
   294  			Type:    node.QualityTypeMORQA,
   295  			Address: options.QualityOracleURL,
   296  		},
   297  		Discovery: node.OptionsDiscovery{
   298  			Types:        []node.DiscoveryType{node.DiscoveryTypeAPI},
   299  			Address:      network.DiscoveryAddress,
   300  			FetchEnabled: false,
   301  			DHT: node.OptionsDHT{
   302  				Address:        "0.0.0.0",
   303  				Port:           0,
   304  				Protocol:       "tcp",
   305  				BootstrapPeers: []string{},
   306  			},
   307  		},
   308  		Location: node.OptionsLocation{
   309  			IPDetectorURL: options.IPDetectorURL,
   310  			Type:          node.LocationTypeOracle,
   311  			Address:       options.LocationDetectorURL,
   312  		},
   313  		Transactor: node.OptionsTransactor{
   314  			TransactorEndpointAddress:       options.TransactorEndpointAddress,
   315  			ProviderMaxRegistrationAttempts: 10,
   316  			TransactorFeesValidTime:         time.Second * 30,
   317  		},
   318  		Affiliator: node.OptionsAffiliator{AffiliatorEndpointAddress: options.AffiliatorEndpointAddress},
   319  		Payments: node.OptionsPayments{
   320  			MaxAllowedPaymentPercentile:    1500,
   321  			BCTimeout:                      time.Second * 30,
   322  			SettlementTimeout:              time.Hour * 2,
   323  			HermesStatusRecheckInterval:    time.Hour * 2,
   324  			BalanceFastPollInterval:        time.Second * 30,
   325  			BalanceFastPollTimeout:         time.Minute * 3,
   326  			BalanceLongPollInterval:        time.Hour * 1,
   327  			RegistryTransactorPollInterval: time.Second * 20,
   328  			RegistryTransactorPollTimeout:  time.Minute * 20,
   329  		},
   330  		Chains: node.OptionsChains{
   331  			Chain1: metadata.ChainDefinition{
   332  				RegistryAddress:    options.RegistrySCAddress,
   333  				HermesID:           options.HermesSCAddress,
   334  				ChannelImplAddress: options.ChannelImplementationSCAddress,
   335  				MystAddress:        options.MystSCAddress,
   336  				ChainID:            options.Chain1ID,
   337  			},
   338  			Chain2: metadata.ChainDefinition{
   339  				RegistryAddress:    options.RegistrySCAddress,
   340  				HermesID:           options.HermesSCAddress,
   341  				ChannelImplAddress: options.ChannelImplementationSCAddress,
   342  				MystAddress:        options.MystSCAddress,
   343  				ChainID:            options.Chain2ID,
   344  			},
   345  		},
   346  
   347  		Consumer:        true,
   348  		PilvytisAddress: options.PilvytisAddress,
   349  		ObserverAddress: options.ObserverAddress,
   350  		SSE: node.OptionsSSE{
   351  			Enabled: true,
   352  		},
   353  	}
   354  	if options.IsProvider {
   355  		nodeOptions.Discovery.FetchEnabled = true
   356  		nodeOptions.Discovery.PingInterval = config.GetDuration(config.FlagDiscoveryPingInterval)
   357  		nodeOptions.Discovery.FetchInterval = config.GetDuration(config.FlagDiscoveryFetchInterval)
   358  		nodeOptions.Payments = node.OptionsPayments{
   359  			MaxAllowedPaymentPercentile:    3000,
   360  			BCTimeout:                      time.Second * 30,
   361  			SettlementTimeout:              time.Minute * 3,
   362  			SettlementRecheckInterval:      time.Second * 30,
   363  			HermesPromiseSettlingThreshold: 0.01,
   364  			MaxFeeSettlingThreshold:        0.05,
   365  			ConsumerDataLeewayMegabytes:    0x14,
   366  			MinAutoSettleAmount:            5,
   367  			MaxUnSettledAmount:             20,
   368  			HermesStatusRecheckInterval:    time.Hour * 2,
   369  			BalanceFastPollInterval:        time.Second * 30 * 2,
   370  			BalanceFastPollTimeout:         time.Minute * 3 * 3,
   371  			BalanceLongPollInterval:        time.Hour * 1,
   372  			RegistryTransactorPollInterval: time.Second * 20,
   373  			RegistryTransactorPollTimeout:  time.Minute * 20,
   374  			ProviderInvoiceFrequency:       config.GetDuration(config.FlagPaymentsProviderInvoiceFrequency),
   375  			ProviderLimitInvoiceFrequency:  config.GetDuration(config.FlagPaymentsLimitProviderInvoiceFrequency),
   376  			MaxUnpaidInvoiceValue:          config.GetBigInt(config.FlagPaymentsUnpaidInvoiceValue),
   377  			LimitUnpaidInvoiceValue:        config.GetBigInt(config.FlagPaymentsLimitUnpaidInvoiceValue),
   378  		}
   379  		nodeOptions.Payments.LimitUnpaidInvoiceValue = config.GetBigInt(config.FlagPaymentsLimitUnpaidInvoiceValue)
   380  		nodeOptions.Chains.Chain1.KnownHermeses = config.GetStringSlice(config.FlagChain1KnownHermeses)
   381  		nodeOptions.Chains.Chain1.KnownHermeses = config.GetStringSlice(config.FlagChain2KnownHermeses)
   382  		nodeOptions.Consumer = false
   383  		nodeOptions.TequilapiEnabled = options.TequilapiSecured
   384  		nodeOptions.TequilapiPort = 4050
   385  		nodeOptions.TequilapiAddress = "127.0.0.1"
   386  		nodeOptions.TequilapiSecured = options.TequilapiSecured
   387  		nodeOptions.UI = node.OptionsUI{
   388  			UIEnabled:     true,
   389  			UIBindAddress: "127.0.0.1",
   390  			UIPort:        4449,
   391  		}
   392  	}
   393  
   394  	err = di.Bootstrap(nodeOptions)
   395  	if err != nil {
   396  		return nil, fmt.Errorf("could not bootstrap dependencies: %w", err)
   397  	}
   398  
   399  	mobileNode := &MobileNode{
   400  		shutdown:                  di.Shutdown,
   401  		node:                      di.Node,
   402  		stateKeeper:               di.StateKeeper,
   403  		connectionManager:         di.MultiConnectionManager,
   404  		locationResolver:          di.LocationResolver,
   405  		natProber:                 di.NATProber,
   406  		identitySelector:          di.IdentitySelector,
   407  		signerFactory:             di.SignerFactory,
   408  		ipResolver:                di.IPResolver,
   409  		eventBus:                  di.EventBus,
   410  		connectionRegistry:        di.ConnectionRegistry,
   411  		feedbackReporter:          di.Reporter,
   412  		affiliator:                di.Affiliator,
   413  		transactor:                di.Transactor,
   414  		identityRegistry:          di.IdentityRegistry,
   415  		consumerBalanceTracker:    di.ConsumerBalanceTracker,
   416  		identityChannelCalculator: di.AddressProvider,
   417  		proposalsManager: newProposalsManager(
   418  			di.ProposalRepository,
   419  			di.FilterPresetStorage,
   420  			di.NATProber,
   421  			time.Duration(options.CacheTTLSeconds)*time.Second,
   422  		),
   423  		pilvytis:            di.PilvytisAPI,
   424  		pilvytisOrderIssuer: di.PilvytisOrderIssuer,
   425  		startTime:           time.Now(),
   426  		chainID:             nodeOptions.OptionsNetwork.ChainID,
   427  		sessionStorage:      di.SessionStorage,
   428  		identityMover:       di.IdentityMover,
   429  		entertainmentEstimator: entertainment.NewEstimator(
   430  			config.FlagPaymentPriceGiB.Value,
   431  			config.FlagPaymentPriceHour.Value,
   432  		),
   433  		residentCountry:     di.ResidentCountry,
   434  		filterPresetStorage: di.FilterPresetStorage,
   435  		hermesMigrator:      di.HermesMigrator,
   436  		earningsProvider:    di.HermesChannelRepository,
   437  	}
   438  
   439  	if options.IsProvider {
   440  		mobileNode.servicesManager = di.ServicesManager
   441  	}
   442  
   443  	return mobileNode, nil
   444  }
   445  
   446  // GetDefaultCurrency returns the current default currency set.
   447  func (mb *MobileNode) GetDefaultCurrency() string {
   448  	return config.Current.GetString(config.FlagDefaultCurrency.Name)
   449  }
   450  
   451  // ProposalChangeCallback represents proposal callback.
   452  type ProposalChangeCallback interface {
   453  	OnChange(proposal []byte)
   454  }
   455  
   456  // GetLocationResponse represents location response.
   457  type GetLocationResponse struct {
   458  	IP      string
   459  	Country string
   460  }
   461  
   462  // GetLocation return current location including country and IP.
   463  func (mb *MobileNode) GetLocation() (*GetLocationResponse, error) {
   464  	tr := http.DefaultTransport.(*http.Transport)
   465  	tr.DisableKeepAlives = true
   466  	c := requests.NewHTTPClientWithTransport(tr, 30*time.Second)
   467  	resolver := location.NewOracleResolver(c, DefaultNodeOptions().LocationDetectorURL)
   468  	loc, err := resolver.DetectLocation()
   469  	// TODO this is temporary workaround to show correct location on Android.
   470  	// This needs to be fixed on the di level to make sure we are using correct resolver in transport and in visual part.
   471  	// loc, err := mb.locationResolver.DetectLocation()
   472  	if err != nil {
   473  		return nil, fmt.Errorf("could not get location: %w", err)
   474  	}
   475  
   476  	return &GetLocationResponse{
   477  		IP:      loc.IP,
   478  		Country: loc.Country,
   479  	}, nil
   480  }
   481  
   482  // SetUserConfig sets a user values in the configuration file
   483  func (mb *MobileNode) SetUserConfig(key, value string) error {
   484  	return setUserConfig(key, value)
   485  }
   486  
   487  // GetStatusResponse represents status response.
   488  type GetStatusResponse struct {
   489  	State    string
   490  	Proposal proposalDTO
   491  }
   492  
   493  // GetStatus returns current connection state and provider info if connected to VPN.
   494  func (mb *MobileNode) GetStatus() ([]byte, error) {
   495  	status := mb.connectionManager.Status(0)
   496  
   497  	resp := &GetStatusResponse{
   498  		State: string(status.State),
   499  		Proposal: proposalDTO{
   500  			ProviderID:   status.Proposal.ProviderID,
   501  			ServiceType:  status.Proposal.ServiceType,
   502  			Country:      status.Proposal.Location.Country,
   503  			IPType:       status.Proposal.Location.IPType,
   504  			QualityLevel: proposalQualityLevel(status.Proposal.Quality.Quality),
   505  			Price: proposalPrice{
   506  				Currency: mb.GetDefaultCurrency(),
   507  			},
   508  		},
   509  	}
   510  
   511  	if status.Proposal.Price.PricePerGiB != nil && status.Proposal.Price.PricePerHour != nil {
   512  		resp.Proposal.Price.PerGiB, _ = big.NewFloat(0).SetInt(status.Proposal.Price.PricePerGiB).Float64()
   513  		resp.Proposal.Price.PerHour, _ = big.NewFloat(0).SetInt(status.Proposal.Price.PricePerHour).Float64()
   514  	}
   515  
   516  	return json.Marshal(resp)
   517  }
   518  
   519  // GetNATType return current NAT type after a probe.
   520  func (mb *MobileNode) GetNATType() (string, error) {
   521  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   522  	defer cancel()
   523  
   524  	natType, err := mb.natProber.Probe(ctx)
   525  	return string(natType), err
   526  }
   527  
   528  // StatisticsChangeCallback represents statistics callback.
   529  type StatisticsChangeCallback interface {
   530  	OnChange(duration int64, bytesReceived int64, bytesSent int64, tokensSpent float64)
   531  }
   532  
   533  // RegisterStatisticsChangeCallback registers callback which is called on active connection
   534  // statistics change.
   535  func (mb *MobileNode) RegisterStatisticsChangeCallback(cb StatisticsChangeCallback) {
   536  	_ = mb.eventBus.SubscribeAsync(connectionstate.AppTopicConnectionStatistics, func(e connectionstate.AppEventConnectionStatistics) {
   537  		var tokensSpent float64
   538  		agreementTotal := mb.stateKeeper.GetConnection("").Invoice.AgreementTotal
   539  		if agreementTotal != nil {
   540  			tokensSpent = units.BigIntWeiToFloatEth(agreementTotal)
   541  		}
   542  
   543  		cb.OnChange(int64(e.SessionInfo.Duration().Seconds()), int64(e.Stats.BytesReceived), int64(e.Stats.BytesSent), tokensSpent)
   544  	})
   545  }
   546  
   547  // ConnectionStatusChangeCallback represents status callback.
   548  type ConnectionStatusChangeCallback interface {
   549  	OnChange(status string)
   550  }
   551  
   552  // ServiceStatusChangeCallback represents status callback.
   553  type ServiceStatusChangeCallback interface {
   554  	OnChange(service string, status string)
   555  }
   556  
   557  // RegisterConnectionStatusChangeCallback registers callback which is called on active connection
   558  // status change.
   559  func (mb *MobileNode) RegisterConnectionStatusChangeCallback(cb ConnectionStatusChangeCallback) {
   560  	_ = mb.eventBus.SubscribeAsync(connectionstate.AppTopicConnectionState, func(e connectionstate.AppEventConnectionState) {
   561  		cb.OnChange(string(e.State))
   562  	})
   563  }
   564  
   565  // RegisterServiceStatusChangeCallback registers callback which is called on active connection
   566  // status change.
   567  func (mb *MobileNode) RegisterServiceStatusChangeCallback(cb ServiceStatusChangeCallback) {
   568  	_ = mb.eventBus.SubscribeAsync(servicestate.AppTopicServiceStatus, func(e servicestate.AppEventServiceStatus) {
   569  		cb.OnChange(e.Type, e.Status)
   570  	})
   571  }
   572  
   573  // BalanceChangeCallback represents balance change callback.
   574  type BalanceChangeCallback interface {
   575  	OnChange(identityAddress string, balance float64)
   576  }
   577  
   578  // RegisterBalanceChangeCallback registers callback which is called on identity balance change.
   579  func (mb *MobileNode) RegisterBalanceChangeCallback(cb BalanceChangeCallback) {
   580  	_ = mb.eventBus.SubscribeAsync(event.AppTopicBalanceChanged, func(e event.AppEventBalanceChanged) {
   581  		balance := crypto.BigMystToFloat(e.Current)
   582  		cb.OnChange(e.Identity.Address, balance)
   583  	})
   584  }
   585  
   586  // ConnectRequest represents connect request.
   587  /*
   588   * DNSOption:
   589   *	- "auto" (default) tries the following with fallbacks: provider's DNS -> client's system DNS -> public DNS
   590   *  - "provider" uses DNS servers from provider's system configuration
   591   *  - "system" uses DNS servers from client's system configuration
   592   */
   593  type ConnectRequest struct {
   594  	Providers               string // comma separated list of providers that will be used for the connection.
   595  	IdentityAddress         string
   596  	ServiceType             string
   597  	CountryCode             string
   598  	IPType                  string
   599  	SortBy                  string
   600  	DNSOption               string
   601  	IncludeMonitoringFailed bool
   602  }
   603  
   604  func (cr *ConnectRequest) dnsOption() (connection.DNSOption, error) {
   605  	if len(cr.DNSOption) > 0 {
   606  		return connection.NewDNSOption(cr.DNSOption)
   607  	}
   608  
   609  	return connection.DNSOptionAuto, nil
   610  }
   611  
   612  // ConnectResponse represents connect response with optional error code and message.
   613  type ConnectResponse struct {
   614  	ErrorCode    string
   615  	ErrorMessage string
   616  }
   617  
   618  const (
   619  	connectErrInvalidProposal     = "InvalidProposal"
   620  	connectErrInsufficientBalance = "InsufficientBalance"
   621  	connectErrUnknown             = "Unknown"
   622  )
   623  
   624  // Connect connects to given provider.
   625  func (mb *MobileNode) Connect(req *ConnectRequest) *ConnectResponse {
   626  	var providers []string
   627  	if len(req.Providers) > 0 {
   628  		providers = strings.Split(req.Providers, ",")
   629  	}
   630  
   631  	f := &proposal.Filter{
   632  		ServiceType:             req.ServiceType,
   633  		LocationCountry:         req.CountryCode,
   634  		ProviderIDs:             providers,
   635  		IPType:                  req.IPType,
   636  		IncludeMonitoringFailed: req.IncludeMonitoringFailed,
   637  		ExcludeUnsupported:      true,
   638  	}
   639  
   640  	proposalLookup := connection.FilteredProposals(f, req.SortBy, mb.proposalsManager.repository)
   641  
   642  	qualityEvent := quality.ConnectionEvent{
   643  		ServiceType: req.ServiceType,
   644  		ConsumerID:  req.IdentityAddress,
   645  	}
   646  
   647  	if len(req.Providers) == 1 {
   648  		qualityEvent.ProviderID = providers[0]
   649  	}
   650  
   651  	dnsOption, err := req.dnsOption()
   652  	if err != nil {
   653  		return &ConnectResponse{
   654  			ErrorCode:    connectErrUnknown,
   655  			ErrorMessage: err.Error(),
   656  		}
   657  	}
   658  	connectOptions := connection.ConnectParams{
   659  		DNS: dnsOption,
   660  	}
   661  
   662  	hermes, err := mb.identityChannelCalculator.GetActiveHermes(mb.chainID)
   663  	if err != nil {
   664  		return &ConnectResponse{
   665  			ErrorCode:    connectErrUnknown,
   666  			ErrorMessage: err.Error(),
   667  		}
   668  	}
   669  
   670  	if err := mb.connectionManager.Connect(identity.FromAddress(req.IdentityAddress), hermes, proposalLookup, connectOptions); err != nil {
   671  		qualityEvent.Stage = quality.StageConnectionUnknownError
   672  		qualityEvent.Error = err.Error()
   673  		mb.eventBus.Publish(quality.AppTopicConnectionEvents, qualityEvent)
   674  
   675  		if errors.Is(err, connection.ErrInsufficientBalance) {
   676  			return &ConnectResponse{
   677  				ErrorCode: connectErrInsufficientBalance,
   678  			}
   679  		}
   680  
   681  		return &ConnectResponse{
   682  			ErrorCode:    connectErrUnknown,
   683  			ErrorMessage: err.Error(),
   684  		}
   685  	}
   686  
   687  	qualityEvent.Stage = quality.StageConnectionOK
   688  	mb.eventBus.Publish(quality.AppTopicConnectionEvents, qualityEvent)
   689  
   690  	return &ConnectResponse{}
   691  }
   692  
   693  // Reconnect is deprecated, we are doing reconnect now in the connection manager.
   694  // TODO remove this from mobile app and here too.
   695  func (mb *MobileNode) Reconnect(req *ConnectRequest) *ConnectResponse {
   696  	return &ConnectResponse{}
   697  }
   698  
   699  // Disconnect disconnects or cancels current connection.
   700  func (mb *MobileNode) Disconnect() error {
   701  	if err := mb.connectionManager.Disconnect(0); err != nil {
   702  		return fmt.Errorf("could not disconnect: %w", err)
   703  	}
   704  
   705  	return nil
   706  }
   707  
   708  // GetBalanceRequest represents balance request.
   709  type GetBalanceRequest struct {
   710  	IdentityAddress string
   711  }
   712  
   713  // GetBalanceResponse represents balance response.
   714  type GetBalanceResponse struct {
   715  	Balance float64
   716  }
   717  
   718  // GetBalance returns current balance.
   719  func (mb *MobileNode) GetBalance(req *GetBalanceRequest) (*GetBalanceResponse, error) {
   720  	balance := mb.consumerBalanceTracker.GetBalance(mb.chainID, identity.FromAddress(req.IdentityAddress))
   721  	b := crypto.BigMystToFloat(balance)
   722  
   723  	return &GetBalanceResponse{Balance: b}, nil
   724  }
   725  
   726  // GetUnsettledEarnings returns unsettled earnings.
   727  func (mb *MobileNode) GetUnsettledEarnings(req *GetBalanceRequest) (*GetBalanceResponse, error) {
   728  	earnings := mb.earningsProvider.GetEarningsDetailed(mb.chainID, identity.FromAddress(req.IdentityAddress))
   729  	u := crypto.BigMystToFloat(earnings.Total.UnsettledBalance)
   730  
   731  	return &GetBalanceResponse{Balance: u}, nil
   732  }
   733  
   734  // ForceBalanceUpdate force updates balance and returns the updated balance.
   735  func (mb *MobileNode) ForceBalanceUpdate(req *GetBalanceRequest) *GetBalanceResponse {
   736  	return &GetBalanceResponse{
   737  		Balance: crypto.BigMystToFloat(mb.consumerBalanceTracker.ForceBalanceUpdateCached(mb.chainID, identity.FromAddress(req.IdentityAddress))),
   738  	}
   739  }
   740  
   741  // SendFeedbackRequest represents user feedback request.
   742  type SendFeedbackRequest struct {
   743  	Email       string
   744  	Description string
   745  }
   746  
   747  // SendFeedback sends user feedback via feedback reported.
   748  func (mb *MobileNode) SendFeedback(req *SendFeedbackRequest) error {
   749  	report := feedback.BugReport{
   750  		Email:       req.Email,
   751  		Description: req.Description,
   752  	}
   753  
   754  	_, apierr, err := mb.feedbackReporter.NewIssue(report)
   755  	if err != nil {
   756  		return fmt.Errorf("could not create user report: %w", err)
   757  	} else if apierr != nil {
   758  		return fmt.Errorf("could not create user report: %w", apierr)
   759  	}
   760  
   761  	return nil
   762  }
   763  
   764  // Shutdown function stops running mobile node.
   765  func (mb *MobileNode) Shutdown() error {
   766  	return mb.shutdown()
   767  }
   768  
   769  // WaitUntilDies function returns when node stops.
   770  func (mb *MobileNode) WaitUntilDies() error {
   771  	return mb.node.Wait()
   772  }
   773  
   774  // OverrideWireguardConnection overrides default wireguard connection implementation to more mobile adapted one.
   775  func (mb *MobileNode) OverrideWireguardConnection(wgTunnelSetup WireguardTunnelSetup) {
   776  	wireguard.Bootstrap()
   777  
   778  	factory := func() (connection.Connection, error) {
   779  		opts := wireGuardOptions{
   780  			statsUpdateInterval: 1 * time.Second,
   781  			handshakeTimeout:    1 * time.Minute,
   782  		}
   783  
   784  		return NewWireGuardConnection(
   785  			opts,
   786  			newWireguardDevice(wgTunnelSetup),
   787  			mb.ipResolver,
   788  			wireguard_connection.NewHandshakeWaiter(),
   789  		)
   790  	}
   791  	mb.connectionRegistry.Register(wireguard.ServiceType, factory)
   792  
   793  	router.SetProtectFunc(wgTunnelSetup.Protect)
   794  }
   795  
   796  // HealthCheckData represents node health check info.
   797  type HealthCheckData struct {
   798  	Uptime    string     `json:"uptime"`
   799  	Version   string     `json:"version"`
   800  	BuildInfo *BuildInfo `json:"build_info"`
   801  }
   802  
   803  // BuildInfo represents node build info.
   804  type BuildInfo struct {
   805  	Commit      string `json:"commit"`
   806  	Branch      string `json:"branch"`
   807  	BuildNumber string `json:"build_number"`
   808  }
   809  
   810  // HealthCheck returns node health check data.
   811  func (mb *MobileNode) HealthCheck() *HealthCheckData {
   812  	return &HealthCheckData{
   813  		Uptime:  time.Since(mb.startTime).String(),
   814  		Version: metadata.VersionAsString(),
   815  		BuildInfo: &BuildInfo{
   816  			Commit:      metadata.BuildCommit,
   817  			Branch:      metadata.BuildBranch,
   818  			BuildNumber: metadata.BuildNumber,
   819  		},
   820  	}
   821  }