github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/consumer/session/session_storage.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 session
    19  
    20  import (
    21  	"errors"
    22  	"math/big"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/asdine/storm/v3"
    27  	"github.com/rs/zerolog/log"
    28  
    29  	"github.com/mysteriumnetwork/node/core/connection/connectionstate"
    30  	"github.com/mysteriumnetwork/node/core/storage/boltdb"
    31  	"github.com/mysteriumnetwork/node/eventbus"
    32  	"github.com/mysteriumnetwork/node/identity"
    33  	session_node "github.com/mysteriumnetwork/node/session"
    34  	session_event "github.com/mysteriumnetwork/node/session/event"
    35  	pingpong_event "github.com/mysteriumnetwork/node/session/pingpong/event"
    36  )
    37  
    38  const sessionStorageBucketName = "session-history"
    39  
    40  type timeGetter func() time.Time
    41  
    42  // Storage contains functions for storing, getting session objects.
    43  type Storage struct {
    44  	storage    *boltdb.Bolt
    45  	timeGetter timeGetter
    46  
    47  	mu             sync.RWMutex
    48  	sessionsActive map[session_node.ID]History
    49  }
    50  
    51  // NewSessionStorage creates session repository with given dependencies.
    52  func NewSessionStorage(storage *boltdb.Bolt) *Storage {
    53  	return &Storage{
    54  		storage:    storage,
    55  		timeGetter: time.Now,
    56  
    57  		sessionsActive: make(map[session_node.ID]History),
    58  	}
    59  }
    60  
    61  // Subscribe subscribes to relevant events of event bus.
    62  func (repo *Storage) Subscribe(bus eventbus.Subscriber) error {
    63  	if err := bus.Subscribe(session_event.AppTopicSession, repo.consumeServiceSessionEvent); err != nil {
    64  		return err
    65  	}
    66  	if err := bus.SubscribeAsync(session_event.AppTopicDataTransferred, repo.consumeServiceSessionStatisticsEvent); err != nil {
    67  		return err
    68  	}
    69  	if err := bus.SubscribeAsync(session_event.AppTopicTokensEarned, repo.consumeServiceSessionEarningsEvent); err != nil {
    70  		return err
    71  	}
    72  	if err := bus.Subscribe(connectionstate.AppTopicConnectionSession, repo.consumeConnectionSessionEvent); err != nil {
    73  		return err
    74  	}
    75  	if err := bus.Subscribe(connectionstate.AppTopicConnectionStatistics, repo.consumeConnectionStatisticsEvent); err != nil {
    76  		return err
    77  	}
    78  	return bus.Subscribe(pingpong_event.AppTopicInvoicePaid, repo.consumeConnectionSpendingEvent)
    79  }
    80  
    81  // GetAll returns array of all sessions.
    82  func (repo *Storage) GetAll() ([]History, error) {
    83  	return repo.List(NewFilter())
    84  }
    85  
    86  // List retrieves stored entries.
    87  func (repo *Storage) List(filter *Filter) (result []History, err error) {
    88  	repo.storage.RLock()
    89  	defer repo.storage.RUnlock()
    90  	query := repo.storage.DB().
    91  		From(sessionStorageBucketName).
    92  		Select(filter.toMatcher()).
    93  		OrderBy("Started").
    94  		Reverse()
    95  
    96  	err = query.Find(&result)
    97  	if errors.Is(err, storm.ErrNotFound) {
    98  		return []History{}, nil
    99  	}
   100  
   101  	return result, err
   102  }
   103  
   104  // Stats fetches aggregated statistics to Filter.Stats.
   105  func (repo *Storage) Stats(filter *Filter) (result Stats, err error) {
   106  	repo.storage.RLock()
   107  	defer repo.storage.RUnlock()
   108  	query := repo.storage.DB().
   109  		From(sessionStorageBucketName).
   110  		Select(filter.toMatcher()).
   111  		OrderBy("Started").
   112  		Reverse()
   113  
   114  	result = NewStats()
   115  	err = query.Each(new(History), func(record interface{}) error {
   116  		session := record.(*History)
   117  
   118  		result.Add(*session)
   119  
   120  		return nil
   121  	})
   122  	return result, err
   123  }
   124  
   125  const stepDay = 24 * time.Hour
   126  
   127  // StatsByDay retrieves aggregated statistics grouped by day to Filter.StatsByDay.
   128  func (repo *Storage) StatsByDay(filter *Filter) (result map[time.Time]Stats, err error) {
   129  	repo.storage.RLock()
   130  	defer repo.storage.RUnlock()
   131  	query := repo.storage.DB().
   132  		From(sessionStorageBucketName).
   133  		Select(filter.toMatcher()).
   134  		OrderBy("Started").
   135  		Reverse()
   136  
   137  	// fill the period with zeros
   138  	result = make(map[time.Time]Stats)
   139  	if filter.StartedFrom != nil && filter.StartedTo != nil {
   140  		for i := filter.StartedFrom.Truncate(stepDay); !i.After(*filter.StartedTo); i = i.Add(stepDay) {
   141  			result[i] = NewStats()
   142  		}
   143  	}
   144  
   145  	err = query.Each(new(History), func(record interface{}) error {
   146  		session := record.(*History)
   147  
   148  		i := session.Started.Truncate(stepDay)
   149  		stats := result[i]
   150  		stats.Add(*session)
   151  		result[i] = stats
   152  
   153  		return nil
   154  	})
   155  	return result, err
   156  }
   157  
   158  // consumeServiceSessionEvent consumes the provided sessions.
   159  func (repo *Storage) consumeServiceSessionEvent(e session_event.AppEventSession) {
   160  	sessionID := session_node.ID(e.Session.ID)
   161  
   162  	switch e.Status {
   163  	case session_event.RemovedStatus:
   164  		repo.handleEndedEvent(sessionID)
   165  	case session_event.CreatedStatus:
   166  		repo.mu.Lock()
   167  		repo.sessionsActive[sessionID] = History{
   168  			SessionID:       sessionID,
   169  			Direction:       DirectionProvided,
   170  			ConsumerID:      e.Session.ConsumerID,
   171  			HermesID:        e.Session.HermesID.Hex(),
   172  			ProviderID:      identity.FromAddress(e.Session.Proposal.ProviderID),
   173  			ServiceType:     e.Session.Proposal.ServiceType,
   174  			ConsumerCountry: e.Session.ConsumerLocation.Country,
   175  			ProviderCountry: e.Session.Proposal.Location.Country,
   176  			Started:         e.Session.StartedAt.UTC(),
   177  			Tokens:          new(big.Int),
   178  		}
   179  		repo.mu.Unlock()
   180  
   181  		repo.handleCreatedEvent(sessionID)
   182  	}
   183  }
   184  
   185  func (repo *Storage) consumeServiceSessionStatisticsEvent(e session_event.AppEventDataTransferred) {
   186  	repo.mu.Lock()
   187  	defer repo.mu.Unlock()
   188  
   189  	sessionID := session_node.ID(e.ID)
   190  	row, ok := repo.activeSession(sessionID)
   191  	if !ok {
   192  		return
   193  	}
   194  
   195  	row.DataSent = e.Down
   196  	row.DataReceived = e.Up
   197  	repo.sessionsActive[sessionID] = row
   198  }
   199  
   200  func (repo *Storage) consumeServiceSessionEarningsEvent(e session_event.AppEventTokensEarned) {
   201  	repo.mu.Lock()
   202  	defer repo.mu.Unlock()
   203  
   204  	sessionID := session_node.ID(e.SessionID)
   205  	row, ok := repo.activeSession(sessionID)
   206  	if !ok {
   207  		return
   208  	}
   209  
   210  	if big.NewInt(0).Cmp(e.Total) == 0 {
   211  		log.Debug().Fields(map[string]interface{}{
   212  			"sessionID":    sessionID,
   213  			"consumerID":   row.ConsumerID,
   214  			"providerID":   row.ProviderID,
   215  			"dataReceived": row.DataReceived,
   216  			"dataSent":     row.DataSent,
   217  			"duration":     row.GetDuration(),
   218  		}).Msgf("Zero earning event")
   219  	}
   220  	row.Tokens = e.Total
   221  	repo.sessionsActive[sessionID] = row
   222  }
   223  
   224  func (repo *Storage) activeSession(sessionID session_node.ID) (History, bool) {
   225  	history, ok := repo.sessionsActive[sessionID]
   226  	if !ok {
   227  		log.Warn().Msgf("Received a unknown session update, sessionID: %s", sessionID)
   228  		return History{}, false
   229  	}
   230  	return history, true
   231  }
   232  
   233  // consumeConnectionSessionEvent consumes the session state change events
   234  func (repo *Storage) consumeConnectionSessionEvent(e connectionstate.AppEventConnectionSession) {
   235  	sessionID := e.SessionInfo.SessionID
   236  
   237  	switch e.Status {
   238  	case connectionstate.SessionEndedStatus:
   239  		repo.handleEndedEvent(sessionID)
   240  	case connectionstate.SessionCreatedStatus:
   241  		repo.mu.Lock()
   242  		repo.sessionsActive[sessionID] = History{
   243  			SessionID:       sessionID,
   244  			Direction:       DirectionConsumed,
   245  			ConsumerID:      e.SessionInfo.ConsumerID,
   246  			HermesID:        e.SessionInfo.HermesID.Hex(),
   247  			ProviderID:      identity.FromAddress(e.SessionInfo.Proposal.ProviderID),
   248  			ServiceType:     e.SessionInfo.Proposal.ServiceType,
   249  			ConsumerCountry: e.SessionInfo.ConsumerLocation.Country,
   250  			ProviderCountry: e.SessionInfo.Proposal.Location.Country,
   251  			Started:         e.SessionInfo.StartedAt.UTC(),
   252  			IPType:          e.SessionInfo.Proposal.Location.IPType,
   253  			Tokens:          new(big.Int),
   254  		}
   255  		repo.mu.Unlock()
   256  
   257  		repo.handleCreatedEvent(sessionID)
   258  	}
   259  }
   260  
   261  func (repo *Storage) consumeConnectionStatisticsEvent(e connectionstate.AppEventConnectionStatistics) {
   262  	repo.mu.Lock()
   263  	defer repo.mu.Unlock()
   264  
   265  	row, ok := repo.activeSession(e.SessionInfo.SessionID)
   266  	if !ok {
   267  		return
   268  	}
   269  
   270  	row.DataSent = e.Stats.BytesSent
   271  	row.DataReceived = e.Stats.BytesReceived
   272  	repo.sessionsActive[e.SessionInfo.SessionID] = row
   273  }
   274  
   275  func (repo *Storage) consumeConnectionSpendingEvent(e pingpong_event.AppEventInvoicePaid) {
   276  	repo.mu.Lock()
   277  	defer repo.mu.Unlock()
   278  
   279  	sessionID := session_node.ID(e.SessionID)
   280  	row, ok := repo.activeSession(sessionID)
   281  	if !ok {
   282  		return
   283  	}
   284  	row.Updated = repo.timeGetter().UTC()
   285  	row.Tokens = e.Invoice.AgreementTotal
   286  
   287  	err := repo.storage.Update(sessionStorageBucketName, &row)
   288  	if err != nil {
   289  		log.Error().Err(err).Msgf("Session %v update failed", sessionID)
   290  		return
   291  	}
   292  
   293  	repo.sessionsActive[sessionID] = row
   294  	log.Debug().Msgf("Session %v updated", sessionID)
   295  }
   296  
   297  func (repo *Storage) handleEndedEvent(sessionID session_node.ID) {
   298  	repo.mu.Lock()
   299  	defer repo.mu.Unlock()
   300  
   301  	row, ok := repo.sessionsActive[sessionID]
   302  	if !ok {
   303  		log.Warn().Msgf("Can't find session %v to update", sessionID)
   304  		return
   305  	}
   306  	row.Updated = repo.timeGetter().UTC()
   307  	row.Status = StatusCompleted
   308  
   309  	err := repo.storage.Update(sessionStorageBucketName, &row)
   310  	if err != nil {
   311  		log.Error().Err(err).Msgf("Session %v update failed", sessionID)
   312  		return
   313  	}
   314  
   315  	delete(repo.sessionsActive, sessionID)
   316  	log.Debug().Msgf("Session %v updated with final data", sessionID)
   317  }
   318  
   319  func (repo *Storage) handleCreatedEvent(sessionID session_node.ID) {
   320  	repo.mu.Lock()
   321  	defer repo.mu.Unlock()
   322  
   323  	row, ok := repo.sessionsActive[sessionID]
   324  	if !ok {
   325  		log.Warn().Msgf("Can't find session %v to store", sessionID)
   326  		return
   327  	}
   328  	row.Status = StatusNew
   329  
   330  	err := repo.storage.Store(sessionStorageBucketName, &row)
   331  	if err != nil {
   332  		log.Error().Err(err).Msgf("Session %v insert failed", row.SessionID)
   333  		return
   334  	}
   335  
   336  	repo.sessionsActive[sessionID] = row
   337  	log.Debug().Msgf("Session %v saved", row.SessionID)
   338  }