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  }