github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/session/pingpong/hermes_channel_repository.go (about) 1 /* 2 * Copyright (C) 2020 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 pingpong 19 20 import ( 21 "encoding/hex" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "math/big" 26 "strings" 27 "sync" 28 29 "github.com/ethereum/go-ethereum/common" 30 ethcrypto "github.com/ethereum/go-ethereum/crypto" 31 "github.com/rs/zerolog/log" 32 33 "github.com/mysteriumnetwork/node/config" 34 nodeEvent "github.com/mysteriumnetwork/node/core/node/event" 35 "github.com/mysteriumnetwork/node/eventbus" 36 "github.com/mysteriumnetwork/node/identity" 37 pingEvent "github.com/mysteriumnetwork/node/session/pingpong/event" 38 "github.com/mysteriumnetwork/payments/client" 39 "github.com/mysteriumnetwork/payments/crypto" 40 ) 41 42 type promiseProvider interface { 43 Get(chainID int64, channelID string) (HermesPromise, error) 44 List(filter HermesPromiseFilter) ([]HermesPromise, error) 45 Store(promise HermesPromise) error 46 } 47 48 type channelProvider interface { 49 GetProviderChannel(chainID int64, hermesAddress common.Address, addressToCheck common.Address, pending bool) (client.ProviderChannel, error) 50 } 51 52 type hermesCaller interface { 53 GetProviderData(chainID int64, id string) (HermesUserInfo, error) 54 RefreshLatestProviderPromise(chainID int64, id string, hashlock, recoveryData []byte, signer identity.Signer) (crypto.Promise, error) 55 RevealR(r string, provider string, agreementID *big.Int) error 56 } 57 58 type beneficiaryProvider interface { 59 GetBeneficiary(identity common.Address) (common.Address, error) 60 } 61 62 // HermesChannelRepository is fetches HermesChannel models from blockchain. 63 type HermesChannelRepository struct { 64 promiseProvider promiseProvider 65 channelProvider channelProvider 66 publisher eventbus.Publisher 67 channels map[int64][]HermesChannel 68 addressProvider addressProvider 69 hermesCaller hermesCaller 70 encryption encryption 71 bprovider beneficiaryProvider 72 lock sync.RWMutex 73 signer identity.SignerFactory 74 } 75 76 // NewHermesChannelRepository returns a new instance of HermesChannelRepository. 77 func NewHermesChannelRepository(promiseProvider promiseProvider, channelProvider channelProvider, publisher eventbus.Publisher, bprovider beneficiaryProvider, hermesCaller hermesCaller, addressProvider addressProvider, signer identity.SignerFactory, encryption encryption) *HermesChannelRepository { 78 return &HermesChannelRepository{ 79 promiseProvider: promiseProvider, 80 channelProvider: channelProvider, 81 publisher: publisher, 82 bprovider: bprovider, 83 hermesCaller: hermesCaller, 84 addressProvider: addressProvider, 85 channels: make(map[int64][]HermesChannel, 0), 86 signer: signer, 87 encryption: encryption, 88 } 89 } 90 91 // Fetch force identity's channel update and returns updated channel. 92 func (hcr *HermesChannelRepository) Fetch(chainID int64, id identity.Identity, hermesID common.Address) (HermesChannel, error) { 93 hcr.lock.Lock() 94 defer hcr.lock.Unlock() 95 96 channelID, err := crypto.GenerateProviderChannelID(id.Address, hermesID.Hex()) 97 if err != nil { 98 return HermesChannel{}, fmt.Errorf("could not generate provider channel address: %w", err) 99 } 100 101 promise, err := hcr.promiseProvider.Get(chainID, channelID) 102 if err != nil && !errors.Is(err, ErrNotFound) { 103 return HermesChannel{}, fmt.Errorf("could not get hermes promise for provider %v, hermes %v: %w", id, hermesID.Hex(), err) 104 } 105 106 channel, err := hcr.fetchChannel(chainID, promise.ChannelID, id, hermesID, promise) 107 if err != nil { 108 return HermesChannel{}, err 109 } 110 111 return channel, nil 112 } 113 114 // ErrUnknownChain is returned when an operation cannot be completed because 115 // the given chain is unknown or isn't configured. 116 var ErrUnknownChain = errors.New("unknown chain") 117 118 // Get retrieves identity's channel with given hermes. 119 func (hcr *HermesChannelRepository) Get(chainID int64, id identity.Identity, hermesID common.Address) (HermesChannel, bool) { 120 hcr.lock.RLock() 121 defer hcr.lock.RUnlock() 122 123 v, ok := hcr.channels[chainID] 124 if !ok { 125 return HermesChannel{}, false 126 } 127 128 for _, channel := range v { 129 if channel.Identity == id && channel.HermesID == hermesID { 130 131 // return a copy!!! 132 return channel.Copy(), true 133 } 134 } 135 136 return HermesChannel{}, false 137 } 138 139 // List retrieves identity's channels with all known hermeses. 140 func (hcr *HermesChannelRepository) List(chainID int64) []HermesChannel { 141 hcr.lock.RLock() 142 defer hcr.lock.RUnlock() 143 144 v, ok := hcr.channels[chainID] 145 if !ok { 146 return nil 147 } 148 149 // make a copy of array, so it could be used in other goroutines 150 channelsCopy := make([]HermesChannel, 0) 151 for _, val := range v { 152 channelsCopy = append(channelsCopy, val.Copy()) 153 } 154 return channelsCopy 155 } 156 157 // GetEarnings returns all channels earnings for given identity combined from all hermeses possible 158 func (hcr *HermesChannelRepository) GetEarnings(chainID int64, id identity.Identity) pingEvent.Earnings { 159 hcr.lock.RLock() 160 defer hcr.lock.RUnlock() 161 162 return hcr.sumChannels(chainID, id) 163 } 164 165 // GetEarningsDetailed returns earnings in a detailed format grouping them by hermes ID but also providing totals. 166 func (hcr *HermesChannelRepository) GetEarningsDetailed(chainID int64, id identity.Identity) *pingEvent.EarningsDetailed { 167 hcr.lock.RLock() 168 defer hcr.lock.RUnlock() 169 170 return hcr.sumChannelsDetailed(chainID, id) 171 } 172 173 func (hcr *HermesChannelRepository) sumChannelsDetailed(chainID int64, id identity.Identity) *pingEvent.EarningsDetailed { 174 result := &pingEvent.EarningsDetailed{ 175 Total: pingEvent.Earnings{ 176 LifetimeBalance: new(big.Int), 177 UnsettledBalance: new(big.Int), 178 }, 179 PerHermes: make(map[common.Address]pingEvent.Earnings), 180 } 181 182 v, ok := hcr.channels[chainID] 183 if !ok { 184 return result 185 } 186 187 add := func(current pingEvent.Earnings, channel HermesChannel) pingEvent.Earnings { 188 life := new(big.Int).Add(current.LifetimeBalance, channel.LifetimeBalance()) 189 unset := new(big.Int).Add(current.UnsettledBalance, channel.UnsettledBalance()) 190 191 // Save total globally per all hermeses 192 current.LifetimeBalance = life 193 current.UnsettledBalance = unset 194 195 return current 196 } 197 198 for _, channel := range v { 199 if channel.Identity != id { 200 continue 201 } 202 result.Total = add(result.Total, channel) 203 204 // Save total for a single hermes 205 got, ok := result.PerHermes[channel.HermesID] 206 if !ok { 207 got = pingEvent.Earnings{ 208 LifetimeBalance: new(big.Int), 209 UnsettledBalance: new(big.Int), 210 } 211 } 212 213 result.PerHermes[channel.HermesID] = add(got, channel) 214 } 215 216 return result 217 } 218 219 func (hcr *HermesChannelRepository) sumChannels(chainID int64, id identity.Identity) pingEvent.Earnings { 220 var lifetimeBalance = new(big.Int) 221 var unsettledBalance = new(big.Int) 222 v, ok := hcr.channels[chainID] 223 if !ok { 224 return pingEvent.Earnings{ 225 LifetimeBalance: new(big.Int), 226 UnsettledBalance: new(big.Int), 227 } 228 } 229 230 for _, channel := range v { 231 if channel.Identity == id { 232 lifetimeBalance = new(big.Int).Add(lifetimeBalance, channel.LifetimeBalance()) 233 unsettledBalance = new(big.Int).Add(unsettledBalance, channel.UnsettledBalance()) 234 } 235 } 236 237 return pingEvent.Earnings{ 238 LifetimeBalance: lifetimeBalance, 239 UnsettledBalance: unsettledBalance, 240 } 241 } 242 243 // Subscribe subscribes to the appropriate events. 244 func (hcr *HermesChannelRepository) Subscribe(bus eventbus.Subscriber) error { 245 err := bus.SubscribeAsync(nodeEvent.AppTopicNode, hcr.handleNodeStart) 246 if err != nil { 247 return fmt.Errorf("could not subscribe to node status event: %w", err) 248 } 249 err = bus.SubscribeAsync(pingEvent.AppTopicHermesPromise, hcr.handleHermesPromiseReceived) 250 if err != nil { 251 return fmt.Errorf("could not subscribe to AppTopicHermesPromise event: %w", err) 252 } 253 err = bus.SubscribeAsync(identity.AppTopicIdentityUnlock, hcr.handleIdentityUnlock) 254 if err != nil { 255 return fmt.Errorf("could not subscribe to AppTopicIdentityUnlock event: %w", err) 256 } 257 return nil 258 } 259 260 func (hcr *HermesChannelRepository) handleHermesPromiseReceived(payload pingEvent.AppEventHermesPromise) { 261 channelID, err := crypto.GenerateProviderChannelID(payload.ProviderID.Address, payload.HermesID.Hex()) 262 if err != nil { 263 log.Err(err).Msg("could not generate provider channel id") 264 return 265 } 266 267 promise, err := hcr.promiseProvider.Get(payload.Promise.ChainID, channelID) 268 if err != nil { 269 log.Err(err).Msgf("could not get hermes promise for provider %v, hermes %v", payload.ProviderID, payload.HermesID.Hex()) 270 return 271 } 272 273 // use parameter "protectChannels" to protect channels on update 274 err = hcr.updateChannelWithLatestPromise(payload.Promise.ChainID, promise.ChannelID, payload.ProviderID, payload.HermesID, promise, true) 275 if err != nil { 276 log.Err(err).Msg("could not update channel state with latest hermes promise") 277 } 278 } 279 280 func (hcr *HermesChannelRepository) handleNodeStart(payload nodeEvent.Payload) { 281 if payload.Status != nodeEvent.StatusStarted { 282 return 283 } 284 hcr.fetchKnownChannels(config.GetInt64(config.FlagChainID)) 285 } 286 287 func (hcr *HermesChannelRepository) handleIdentityUnlock(payload identity.AppEventIdentityUnlock) { 288 hermes, err := hcr.addressProvider.GetActiveHermes(payload.ChainID) 289 if err != nil { 290 log.Err(err).Msg("failed to get active Hermes") 291 return 292 } 293 hermesChannel, exists := hcr.Get(payload.ChainID, payload.ID, hermes) 294 if exists { 295 return 296 } 297 298 unsettledBalance := hermesChannel.UnsettledBalance() 299 if unsettledBalance.Cmp(big.NewInt(0)) != 0 { 300 return 301 } 302 303 data, err := hcr.hermesCaller.GetProviderData(payload.ChainID, payload.ID.Address) 304 if err != nil { 305 log.Err(err).Msg("failed to get provider data") 306 return 307 } 308 309 //skip refresh if promise has been revealed or we know r 310 if data.LatestPromise.Hashlock != "" { 311 hermesPromise, err := hcr.promiseProvider.Get(payload.ChainID, data.ChannelID) 312 if err == nil && strings.EqualFold(data.LatestPromise.Hashlock, fmt.Sprintf("0x%s", common.Bytes2Hex(hermesPromise.Promise.Hashlock))) { 313 err = hcr.revealR(hermesPromise) 314 if err == nil { 315 return 316 } 317 log.Error().Err(err).Msgf("failed to reveal R on identity unlock") 318 } 319 } 320 321 if data.LatestPromise.Amount != nil && data.LatestPromise.Amount.Cmp(big.NewInt(0)) != 0 { 322 R, err := crypto.GenerateR() 323 if err != nil { 324 log.Err(err).Msg("failed to generate R") 325 return 326 } 327 hashlock := ethcrypto.Keccak256(R) 328 details := rRecoveryDetails{ 329 R: hex.EncodeToString(R), 330 AgreementID: big.NewInt(0), 331 } 332 333 bytes, err := json.Marshal(details) 334 if err != nil { 335 log.Err(err).Msgf("could not marshal R recovery details") 336 return 337 } 338 339 encrypted, err := hcr.encryption.Encrypt(payload.ID.ToCommonAddress(), bytes) 340 if err != nil { 341 log.Err(err).Msgf("could not encrypt R") 342 return 343 } 344 signer := hcr.signer(payload.ID) 345 promise, err := hcr.hermesCaller.RefreshLatestProviderPromise(config.GetInt64(config.FlagChainID), payload.ID.Address, hashlock, encrypted, signer) 346 if err != nil { 347 log.Err(err).Msgf("failed to refresh promise") 348 return 349 } 350 hermesPromise := HermesPromise{ 351 R: hex.EncodeToString(R), 352 ChannelID: data.ChannelID, 353 Identity: identity.FromAddress(data.Identity), 354 HermesID: hermes, 355 Promise: promise, 356 Revealed: false, 357 } 358 359 err = hcr.promiseProvider.Store(hermesPromise) 360 if err != nil { 361 log.Err(err).Msg("could not store hermes promise") 362 return 363 } 364 hcr.publisher.Publish(pingEvent.AppTopicHermesPromise, pingEvent.AppEventHermesPromise{ 365 Promise: promise, 366 HermesID: hermes, 367 ProviderID: identity.FromAddress(data.Identity), 368 }) 369 370 err = hcr.revealR(hermesPromise) 371 if err != nil { 372 log.Err(err).Msgf("failed to reveal R after promise refresh") 373 } 374 log.Debug().Bool("saved", err == nil).Msg("refreshed promise") 375 } 376 } 377 378 func (hcr *HermesChannelRepository) revealR(hermesPromise HermesPromise) error { 379 if hermesPromise.Revealed { 380 return nil 381 } 382 383 err := hcr.hermesCaller.RevealR(hermesPromise.R, hermesPromise.Identity.Address, hermesPromise.AgreementID) 384 if err != nil { 385 return fmt.Errorf("could not reveal R: %w", err) 386 } 387 388 hermesPromise.Revealed = true 389 err = hcr.promiseProvider.Store(hermesPromise) 390 if err != nil && !errors.Is(err, ErrAttemptToOverwrite) { 391 return fmt.Errorf("could not store hermes promise: %w", err) 392 } 393 394 return nil 395 } 396 397 func (hcr *HermesChannelRepository) fetchKnownChannels(chainID int64) { 398 hcr.lock.Lock() 399 defer hcr.lock.Unlock() 400 401 promises, err := hcr.promiseProvider.List(HermesPromiseFilter{ 402 ChainID: chainID, 403 }) 404 if err != nil { 405 log.Error().Err(err).Msg("could not load initial earnings state") 406 return 407 } 408 409 for _, promise := range promises { 410 gen, err := crypto.GenerateProviderChannelID(promise.Identity.Address, promise.HermesID.Hex()) 411 if err != nil { 412 log.Err(err).Msg("could not generate a provider channel address") 413 continue 414 } 415 if strings.ToLower(gen) != strings.ToLower(promise.ChannelID) { 416 log.Debug().Fields(map[string]interface{}{ 417 "identity": promise.Identity.Address, 418 "expected_channelID": gen, 419 "got_channelID": promise.ChannelID, 420 "hermes": promise.HermesID.Hex(), 421 }).Msg("promise channel ID did not match provider channel ID, skipping") 422 continue 423 } 424 425 if _, err := hcr.fetchChannel(chainID, promise.ChannelID, promise.Identity, promise.HermesID, promise); err != nil { 426 log.Error().Err(err).Msg("could not load initial earnings state") 427 } 428 } 429 } 430 431 func (hcr *HermesChannelRepository) fetchChannel(chainID int64, channelID string, id identity.Identity, hermesID common.Address, promise HermesPromise) (HermesChannel, error) { 432 // TODO Should call GetProviderChannelByID() but can't pass pending=false 433 // This will get retried so we do not need to explicitly retry 434 // TODO: maybe add a sane limit of retries 435 channel, err := hcr.channelProvider.GetProviderChannel(chainID, hermesID, id.ToCommonAddress(), true) 436 if err != nil { 437 return HermesChannel{}, fmt.Errorf("could not get provider channel for %v, hermes %v: %w", id, hermesID.Hex(), err) 438 } 439 440 benef, err := hcr.bprovider.GetBeneficiary(id.ToCommonAddress()) 441 if err != nil { 442 return HermesChannel{}, fmt.Errorf("could not get provider beneficiary for %v, hermes %v: %w", id, hermesID.Hex(), err) 443 } 444 hermesChannel := NewHermesChannel(channelID, id, hermesID, channel, promise, benef). 445 Copy() 446 447 hcr.updateChannel(chainID, hermesChannel) 448 449 return hermesChannel, nil 450 } 451 452 func (hcr *HermesChannelRepository) updateChannelWithLatestPromise(chainID int64, channelID string, id identity.Identity, hermesID common.Address, promise HermesPromise, protectChannels bool) error { 453 gotten, ok := hcr.Get(chainID, id, hermesID) 454 if !ok { 455 // this actually performs the update, so no need to do anything 456 _, err := hcr.fetchChannel(chainID, channelID, id, hermesID, promise) 457 return err 458 } 459 460 hermesChannel := NewHermesChannel(channelID, id, hermesID, gotten.Channel, promise, gotten.Beneficiary). 461 Copy() 462 463 // protect hcr.channels: handleHermesPromiseReceived -> updateChannelWithLatestPromise -> updateChannel 464 if protectChannels { 465 hcr.lock.Lock() 466 defer hcr.lock.Unlock() 467 } 468 hcr.updateChannel(chainID, hermesChannel) 469 470 return nil 471 } 472 473 func (hcr *HermesChannelRepository) updateChannel(chainID int64, new HermesChannel) { 474 earningsOld := hcr.sumChannelsDetailed(chainID, new.Identity) 475 476 updated := false 477 478 v := hcr.channels[chainID] 479 for i, channel := range v { 480 if channel.Identity == new.Identity && channel.HermesID == new.HermesID { 481 updated = true 482 // rewrites element in array. to prevent data race on array elements - use its deep-copy in other goroutines 483 hcr.channels[chainID][i] = new 484 break 485 } 486 } 487 res := append(hcr.channels[chainID], new) 488 if !updated { 489 hcr.channels[chainID] = res 490 } 491 492 log.Info().Msgf( 493 "Loaded state for provider %q, hermesID %q: balance %v, available balance %v, unsettled balance %v", 494 new.Identity, 495 new.HermesID.Hex(), 496 new.balance(), 497 new.availableBalance(), 498 new.UnsettledBalance(), 499 ) 500 501 earningsNew := hcr.sumChannelsDetailed(chainID, new.Identity) 502 go hcr.publisher.Publish(pingEvent.AppTopicEarningsChanged, pingEvent.AppEventEarningsChanged{ 503 Identity: new.Identity, 504 Previous: *earningsOld, 505 Current: *earningsNew, 506 }) 507 }