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  }