github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/consumer/migration/hermes_migrator.go (about) 1 /* 2 * Copyright (C) 2022 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 migration 19 20 import ( 21 "errors" 22 "fmt" 23 "math/big" 24 "time" 25 26 "github.com/ethereum/go-ethereum/accounts/abi/bind" 27 "github.com/ethereum/go-ethereum/common" 28 "github.com/mysteriumnetwork/node/config" 29 "github.com/mysteriumnetwork/node/eventbus" 30 "github.com/mysteriumnetwork/node/identity" 31 "github.com/mysteriumnetwork/node/identity/registry" 32 "github.com/mysteriumnetwork/node/session/pingpong" 33 "github.com/mysteriumnetwork/payments/client" 34 "github.com/mysteriumnetwork/payments/crypto" 35 "github.com/rs/zerolog/log" 36 ) 37 38 const oldBalanceMigrationMinimumMyst = 0.1 39 40 var openChannelTimeout = time.Hour 41 42 type blockchain interface { 43 GetConsumerChannel(chainID int64, addr common.Address, mystSCAddress common.Address) (client.ConsumerChannel, error) 44 } 45 46 // HermesMigrator migrate identity from old hermes to new. 47 // It opens a new channel for new Hermes and sends all MYST to a new payment channel. 48 type HermesMigrator struct { 49 transactor *registry.Transactor 50 addressProvider registry.AddressProvider 51 hps pingpong.HermesPromiseSettler 52 hermesURLGetter *pingpong.HermesURLGetter 53 hermesCallerFactory pingpong.HermesCallerFactory 54 registry registry.IdentityRegistry 55 cbt *pingpong.ConsumerBalanceTracker 56 st *Storage 57 bc blockchain 58 } 59 60 // NewHermesMigrator create new HermesMigrator 61 func NewHermesMigrator( 62 transactor *registry.Transactor, 63 addressProvider registry.AddressProvider, 64 hermesURLGetter *pingpong.HermesURLGetter, 65 hermesCallerFactory pingpong.HermesCallerFactory, 66 hps pingpong.HermesPromiseSettler, 67 registry registry.IdentityRegistry, 68 cbt *pingpong.ConsumerBalanceTracker, 69 st *Storage, 70 bc blockchain, 71 ) *HermesMigrator { 72 return &HermesMigrator{ 73 transactor: transactor, 74 addressProvider: addressProvider, 75 hermesURLGetter: hermesURLGetter, 76 hermesCallerFactory: hermesCallerFactory, 77 hps: hps, 78 registry: registry, 79 cbt: cbt, 80 st: st, 81 bc: bc, 82 } 83 } 84 85 // HermesClient responses for receiving info from Hermes 86 type HermesClient interface { 87 GetConsumerData(chainID int64, id string, cacheDuration time.Duration) (pingpong.HermesUserInfo, error) 88 } 89 90 // Start begins migration from old hermes to new 91 func (m *HermesMigrator) Start(id string) error { 92 chainID := config.GetInt64(config.FlagChainID) 93 if !m.st.isMigrationRequired(chainID, id) { 94 log.Info().Msg("Migration is already done") 95 return nil 96 } 97 98 // if user not registered - do not do migration at all 99 registered, err := m.isRegistered(chainID, id) 100 if err != nil { 101 return fmt.Errorf("could not get identity register status: %w", err) 102 } else if !registered { 103 return errors.New("identity is unregistered") 104 } 105 106 // get old and new hermeses 107 activeHermes, oldHermesPointer, err := m.getHermeses(chainID) 108 if err != nil { 109 return err 110 } 111 112 if oldHermesPointer == nil { 113 return nil 114 } 115 oldHermes := *oldHermesPointer 116 117 // get registry address 118 registryAddress, err := m.addressProvider.GetRegistryAddress(chainID) 119 if err != nil { 120 return fmt.Errorf("could not get registry address: %w", err) 121 } 122 123 // try open channel only if it's not opened yet. 124 if err = m.openChannel(id, err, chainID, activeHermes, registryAddress); err != nil { 125 return fmt.Errorf("open channel error: %w", err) 126 } 127 128 // get data from old hermes 129 data, err := m.getUserData(chainID, oldHermes.Hex(), id) 130 if err != nil { 131 newHermesData, err := m.getUserData(chainID, activeHermes.Hex(), id) 132 if err != nil { 133 return fmt.Errorf("error during getting balance from hermes: %w", err) 134 } 135 // old hermes unavailable, but user already using new hermes 136 if newHermesData.Balance.Cmp(big.NewInt(0)) > 0 || (newHermesData.LatestPromise.Amount != nil && newHermesData.LatestPromise.Amount.Cmp(big.NewInt(0)) > 0) { 137 m.st.MarkAsMigrated(chainID, id) 138 return nil 139 } 140 return fmt.Errorf("error during getting balance: %w", err) 141 } 142 // skip migration for offchain identities 143 if data.IsOffchain { 144 m.st.MarkAsMigrated(chainID, id) 145 return nil 146 } 147 oldBalance := data.Balance 148 149 // get channel implementation contract address 150 channelImpl, err := m.addressProvider.GetActiveChannelImplementation(chainID) 151 if err != nil { 152 return fmt.Errorf("error during getting channel implementation: %w", err) 153 } 154 // calculate new channel address 155 newChannel, err := crypto.GenerateChannelAddress(id, activeHermes.Hex(), registryAddress.Hex(), channelImpl.Hex()) 156 if err != nil { 157 return fmt.Errorf("generate channel address erro: %w", err) 158 } 159 160 providerId := identity.FromAddress(id) 161 162 // check if balance enough for migration 163 if crypto.FloatToBigMyst(oldBalanceMigrationMinimumMyst).Cmp(oldBalance) >= 0 { 164 // If not enough balance we should still check that latest withdrawal succeeded 165 amountToWithdraw, chid, err := m.hps.CheckLatestWithdrawal(chainID, identity.FromAddress(id), oldHermes) 166 if err != nil { 167 if !errors.Is(err, pingpong.ErrNotFound) { 168 return fmt.Errorf("failed to check latest withdrawal status: %w", err) 169 } 170 log.Info().Msg("No promise saved") 171 } else if amountToWithdraw != nil && amountToWithdraw.Cmp(big.NewInt(0)) == 1 { 172 log.Debug().Msgf("Found withdrawal which is not settled, will retry to withdraw") 173 return m.hps.RetryWithdrawLatest(chainID, amountToWithdraw, chid, common.HexToAddress(newChannel), providerId) 174 } 175 log.Info().Msgf("Not enough balance for migration or already migrated (id: %s, old balance: %.2f)", id, crypto.BigMystToFloat(oldBalance)) 176 m.st.MarkAsMigrated(chainID, id) 177 return nil 178 } 179 180 log.Debug().Msgf("Send transaction. Old Hermes: %s, new Hermes %s (channel: %s)", oldHermes, activeHermes, newChannel) 181 182 // send all money from old channel to new 183 if err := m.hps.Withdraw(chainID, chainID, providerId, oldHermes, common.HexToAddress(newChannel), nil); err != nil { 184 return err 185 } 186 187 m.cbt.ForceBalanceUpdateCached(chainID, providerId) 188 189 return nil 190 } 191 192 func (m *HermesMigrator) openChannel(id string, err error, chainID int64, activeHermes common.Address, registryAddress common.Address) error { 193 statusResponse, err := m.transactor.ChannelStatus(chainID, id, activeHermes.Hex(), registryAddress.Hex()) 194 if err != nil { 195 return fmt.Errorf("channel status error: %w", err) 196 } 197 198 log.Debug().Msgf("Channel status: %s", statusResponse.Status) 199 if statusResponse.Status == registry.ChannelStatusNotFound { 200 if err := m.transactor.OpenChannel(chainID, id, activeHermes.Hex(), registryAddress.Hex()); err != nil { 201 return fmt.Errorf("open new channel error: %w", err) 202 } 203 err = m.waitForChannelOpened(chainID, common.HexToAddress(id), activeHermes, registryAddress, openChannelTimeout) 204 } else if statusResponse.Status == registry.ChannelStatusInProgress { 205 err = m.waitForChannelOpened(chainID, common.HexToAddress(id), activeHermes, registryAddress, openChannelTimeout) 206 } 207 if err != nil { 208 return fmt.Errorf("error during waiting for channel opening: %w", err) 209 } 210 211 return nil 212 } 213 214 // IsMigrationRequired check whether migration required 215 func (m *HermesMigrator) IsMigrationRequired(id string) (bool, error) { 216 chainID := config.GetInt64(config.FlagChainID) 217 // check local db if there is need to try to migrate 218 if !m.st.isMigrationRequired(chainID, id) { 219 log.Info().Msg("Skipping require check, migration is already done or was never needed") 220 return false, nil 221 } 222 223 // if user not registered - do not do migration at all 224 registered, err := m.isRegistered(chainID, id) 225 if err != nil { 226 return false, fmt.Errorf("could not get identity register status: %w", err) 227 } else if !registered { 228 log.Info().Msg("Migration is not required: identity is not registered in old Hermes") 229 m.st.MarkAsMigrated(chainID, id) 230 return false, nil 231 } 232 233 activeHermes, oldHermesPointer, err := m.getHermeses(chainID) 234 if err != nil { 235 return false, err 236 } 237 238 if oldHermesPointer == nil { 239 return false, nil 240 } 241 oldHermes := *oldHermesPointer 242 243 // get data from new hermes 244 newHermesData, err := m.getUserData(chainID, activeHermes.Hex(), id) 245 if err != nil { 246 return false, fmt.Errorf("error during getting balance: %w", err) 247 } 248 249 // get data from old hermes 250 oldHermesData, err := m.getUserData(chainID, oldHermes.Hex(), id) 251 if err != nil { 252 // old hermes unavailable, but user already using new hermes 253 if newHermesData.Balance.Cmp(big.NewInt(0)) > 0 || (newHermesData.LatestPromise.Amount != nil && newHermesData.LatestPromise.Amount.Cmp(big.NewInt(0)) > 0) { 254 m.st.MarkAsMigrated(chainID, id) 255 return false, nil 256 } 257 return false, fmt.Errorf("error during getting balance: %w", err) 258 } 259 260 // if identity is offchain no migration is needed 261 if oldHermesData.IsOffchain { 262 log.Info().Msg("Migration is not required: identity is offchain") 263 m.st.MarkAsMigrated(chainID, id) 264 return false, nil 265 } 266 267 // if channel is not opened in old hermes - skip 268 opened, err := m.isChannelOpened(chainID, common.HexToAddress(id), oldHermes) 269 if err != nil { 270 return false, err 271 } else if !opened { 272 log.Info().Msg("Migration is not required: channel is not opened in old hermes") 273 m.st.MarkAsMigrated(chainID, id) 274 return false, nil 275 } 276 277 // check payment channel status 278 status, err := m.getChannelStatus(chainID, common.HexToAddress(id), activeHermes) 279 if err != nil { 280 return false, err 281 } 282 if status == registry.ChannelStatusNotFound || status == registry.ChannelStatusInProgress { 283 return true, nil 284 } 285 286 amountToWithdraw, _, err := m.hps.CheckLatestWithdrawal(chainID, identity.FromAddress(id), oldHermes) 287 if err != nil { 288 if !errors.Is(err, pingpong.ErrNotFound) { 289 return false, fmt.Errorf("failed to check latest withdrawal status: %w", err) 290 } 291 log.Warn().Err(err).Msg("No promise saved") 292 } else if amountToWithdraw != nil && amountToWithdraw.Cmp(big.NewInt(0)) == 1 { 293 return true, nil 294 } 295 296 // amount to withdraw greater then threshold 297 required := crypto.FloatToBigMyst(oldBalanceMigrationMinimumMyst).Cmp(oldHermesData.Balance) < 0 && newHermesData.Balance.Cmp(new(big.Int)) == 0 298 if !required { 299 log.Info().Msgf("Migration is not required: lack of balance (%s)", oldHermesData.Balance.String()) 300 m.st.MarkAsMigrated(chainID, id) 301 } 302 303 return required, nil 304 } 305 306 // Subscribe for EventBus events 307 func (m *HermesMigrator) Subscribe(eb eventbus.Subscriber) error { 308 return eb.Subscribe(registry.AppTopicTransactorRegistration, m.handleRegistrationEvent) 309 } 310 311 func (m *HermesMigrator) waitForChannelOpened(chainID int64, id, hermesID, registryAddress common.Address, timeout time.Duration) error { 312 log.Debug().Msg("Hermes migration: opening new channel") 313 timer := time.NewTimer(timeout) 314 defer timer.Stop() 315 for { 316 select { 317 case <-timer.C: 318 return fmt.Errorf("channel opening timeout for id: %s", id) 319 case <-time.After(5 * time.Second): 320 statusResponse, err := m.transactor.ChannelStatus(chainID, id.Hex(), hermesID.Hex(), registryAddress.Hex()) 321 if err != nil { 322 log.Debug().Msgf("Hermes migration error: open channel failed %s", err.Error()) 323 return err 324 } else if statusResponse.Status == registry.ChannelStatusFail || statusResponse.Status == registry.ChannelStatusOpen { 325 log.Debug().Msg("Hermes migration: new channel opened successfully") 326 return nil 327 } 328 } 329 } 330 } 331 332 func (m *HermesMigrator) getHermesCaller(chainID int64, hermesID string) (HermesClient, error) { 333 addr, err := m.hermesURLGetter.GetHermesURL(chainID, common.HexToAddress(hermesID)) 334 if err != nil { 335 return nil, err 336 } 337 338 return m.hermesCallerFactory(addr), nil 339 } 340 341 // getBalance gets the current balance for given identity 342 func (m *HermesMigrator) getUserData(chainID int64, hermesID, id string) (pingpong.HermesUserInfo, error) { 343 var data pingpong.HermesUserInfo 344 c, err := m.getHermesCaller(chainID, hermesID) 345 if err != nil { 346 return data, err 347 } 348 349 data, err = c.GetConsumerData(chainID, id, time.Minute) 350 if err != nil { 351 if errors.Is(err, pingpong.ErrHermesNotFound) { 352 return data, nil 353 } 354 return data, err 355 } 356 357 return data, nil 358 } 359 360 func getOldHermeses(knownHermeses []common.Address, activeHermes common.Address) []common.Address { 361 oldHermeses := []common.Address{} 362 for _, address := range knownHermeses { 363 if address.Hex() != activeHermes.Hex() { 364 oldHermeses = append(oldHermeses, address) 365 } 366 } 367 368 return oldHermeses 369 } 370 371 func (m *HermesMigrator) isRegistered(chainID int64, id string) (bool, error) { 372 status, err := m.registry.GetRegistrationStatus(chainID, identity.FromAddress(id)) 373 if err != nil { 374 return false, err 375 } 376 return status == registry.Registered, nil 377 } 378 379 func (m *HermesMigrator) getChannelStatus(chainID int64, identity, hermesID common.Address) (registry.ChannelStatus, error) { 380 registryAddress, err := m.addressProvider.GetRegistryAddress(chainID) 381 if err != nil { 382 return registry.ChannelStatusFail, fmt.Errorf("could not get registry address: %w", err) 383 } 384 385 opened, err := m.isChannelOpened(chainID, identity, hermesID) 386 if err != nil { 387 return registry.ChannelStatusFail, fmt.Errorf("could not check channel status: %w", err) 388 } else if opened { 389 return registry.ChannelStatusOpen, nil 390 } 391 channelStatusReponse, err := m.transactor.ChannelStatus(chainID, identity.Hex(), hermesID.Hex(), registryAddress.Hex()) 392 393 return channelStatusReponse.Status, err 394 } 395 396 func (m *HermesMigrator) isChannelOpened(chainID int64, identity, hermesID common.Address) (bool, error) { 397 registryAddress, err := m.addressProvider.GetRegistryAddress(chainID) 398 if err != nil { 399 return false, fmt.Errorf("could not get registry address: %w", err) 400 } 401 402 channelImpl, err := m.addressProvider.GetChannelImplementationForHermes(chainID, hermesID) 403 if err != nil { 404 return false, err 405 } 406 407 channelAddress, err := crypto.GenerateChannelAddress(identity.Hex(), hermesID.Hex(), registryAddress.Hex(), channelImpl.Hex()) 408 409 if err != nil { 410 return false, err 411 } 412 413 mystAddress, err := m.addressProvider.GetMystAddress(chainID) 414 if err != nil { 415 return false, err 416 } 417 418 _, err = m.bc.GetConsumerChannel(chainID, common.HexToAddress(channelAddress), mystAddress) 419 if err != nil && errors.Is(err, bind.ErrNoCode) { 420 return false, nil 421 422 } else if err != nil { 423 return false, err 424 } 425 426 return true, nil 427 } 428 429 func (m *HermesMigrator) getHermeses(chainID int64) (common.Address, *common.Address, error) { 430 activeHermes, err := m.addressProvider.GetActiveHermes(chainID) 431 if err != nil { 432 return common.Address{}, nil, fmt.Errorf("could not get hermes address: %w", err) 433 } 434 knownHermeses, err := m.addressProvider.GetKnownHermeses(chainID) 435 if err != nil { 436 return common.Address{}, nil, fmt.Errorf("could not get hermes address: %w", err) 437 } 438 oldHermeses := getOldHermeses(knownHermeses, activeHermes) 439 if len(oldHermeses) != 1 { 440 log.Warn().Msg("Migration skipped: there isn't a single hermes to migrate.") 441 return common.Address{}, nil, nil 442 } 443 444 return activeHermes, &oldHermeses[0], nil 445 } 446 447 func (m *HermesMigrator) handleRegistrationEvent(ev registry.IdentityRegistrationRequest) { 448 m.st.MarkAsMigrated(ev.ChainID, ev.Identity) 449 }