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 }