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 }