github.com/status-im/status-go@v1.1.0/services/mailservers/database.go (about) 1 package mailservers 2 3 import ( 4 "database/sql" 5 "database/sql/driver" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "strings" 10 "time" 11 12 "github.com/libp2p/go-libp2p/core/peer" 13 "github.com/multiformats/go-multiaddr" 14 15 "github.com/waku-org/go-waku/waku/v2/protocol/enr" 16 "github.com/waku-org/go-waku/waku/v2/utils" 17 18 "github.com/ethereum/go-ethereum/p2p/enode" 19 20 "github.com/status-im/status-go/protocol/transport" 21 ) 22 23 func MustDecodeENR(enrStr string) *enode.Node { 24 node, err := enode.Parse(enode.ValidSchemes, enrStr) 25 if err != nil || node == nil { 26 panic("could not decode enr: " + enrStr) 27 } 28 return node 29 } 30 31 func MustDecodeMultiaddress(multiaddrsStr string) *multiaddr.Multiaddr { 32 maddr, err := multiaddr.NewMultiaddr(multiaddrsStr) 33 if err != nil || maddr == nil { 34 panic("could not decode multiaddr: " + multiaddrsStr) 35 } 36 return &maddr 37 } 38 39 type Mailserver struct { 40 ID string `json:"id"` 41 Name string `json:"name"` 42 Custom bool `json:"custom"` 43 ENR *enode.Node `json:"enr"` 44 Addr *multiaddr.Multiaddr `json:"addr"` 45 46 // Deprecated: only used with WakuV1 47 Password string `json:"password,omitempty"` 48 Fleet string `json:"fleet"` 49 FailedRequests uint `json:"-"` 50 } 51 52 func (m Mailserver) PeerInfo() (*peer.AddrInfo, error) { 53 var maddrs []multiaddr.Multiaddr 54 55 if m.ENR != nil { 56 addrInfo, err := enr.EnodeToPeerInfo(m.ENR) 57 if err != nil { 58 return nil, err 59 } 60 addrInfo.Addrs = utils.EncapsulatePeerID(addrInfo.ID, addrInfo.Addrs...) 61 maddrs = append(maddrs, addrInfo.Addrs...) 62 } 63 64 if m.Addr != nil { 65 maddrs = append(maddrs, *m.Addr) 66 } 67 68 p, err := peer.AddrInfosFromP2pAddrs(maddrs...) 69 if err != nil { 70 return nil, err 71 } 72 73 if len(p) != 1 { 74 return nil, errors.New("invalid mailserver setup") 75 } 76 77 return &p[0], nil 78 } 79 80 func (m Mailserver) PeerID() (peer.ID, error) { 81 p, err := m.PeerInfo() 82 if err != nil { 83 return "", err 84 } 85 return p.ID, nil 86 } 87 88 func (m Mailserver) nullablePassword() (val sql.NullString) { 89 if m.Password != "" { 90 val.String = m.Password 91 val.Valid = true 92 } 93 return 94 } 95 96 type MailserverRequestGap struct { 97 ID string `json:"id"` 98 ChatID string `json:"chatId"` 99 From uint64 `json:"from"` 100 To uint64 `json:"to"` 101 } 102 103 type MailserverTopic struct { 104 PubsubTopic string `json:"pubsubTopic"` 105 ContentTopic string `json:"topic"` 106 Discovery bool `json:"discovery?"` 107 Negotiated bool `json:"negotiated?"` 108 ChatIDs []string `json:"chat-ids"` 109 LastRequest int `json:"last-request"` // default is 1 110 } 111 112 type ChatRequestRange struct { 113 ChatID string `json:"chat-id"` 114 LowestRequestFrom int `json:"lowest-request-from"` 115 HighestRequestTo int `json:"highest-request-to"` 116 } 117 118 // sqlStringSlice helps to serialize a slice of strings into a single column using JSON serialization. 119 type sqlStringSlice []string 120 121 // Scan implements the Scanner interface. 122 func (ss *sqlStringSlice) Scan(value interface{}) error { 123 if value == nil { 124 *ss = nil 125 return nil 126 } 127 src, ok := value.([]byte) 128 if !ok { 129 return errors.New("invalid value type, expected byte slice") 130 } 131 return json.Unmarshal(src, ss) 132 } 133 134 // Value implements the driver Valuer interface. 135 func (ss sqlStringSlice) Value() (driver.Value, error) { 136 return json.Marshal(ss) 137 } 138 139 // Database sql wrapper for operations with mailserver objects. 140 type Database struct { 141 db *sql.DB 142 } 143 144 func NewDB(db *sql.DB) *Database { 145 return &Database{db: db} 146 } 147 148 func (d *Database) Add(mailserver Mailserver) error { 149 // TODO: we are only storing the multiaddress. 150 // In a future PR we must allow storing multiple multiaddresses and ENR 151 _, err := d.db.Exec(`INSERT OR REPLACE INTO mailservers( 152 id, 153 name, 154 address, 155 password, 156 fleet 157 ) VALUES (?, ?, ?, ?, ?)`, 158 mailserver.ID, 159 mailserver.Name, 160 (*mailserver.Addr).String(), 161 mailserver.nullablePassword(), 162 mailserver.Fleet, 163 ) 164 return err 165 } 166 167 func (d *Database) Mailservers() ([]Mailserver, error) { 168 rows, err := d.db.Query(`SELECT id, name, address, password, fleet FROM mailservers`) 169 if err != nil { 170 return nil, err 171 } 172 defer rows.Close() 173 return toMailservers(rows) 174 } 175 176 func toMailservers(rows *sql.Rows) ([]Mailserver, error) { 177 var result []Mailserver 178 179 for rows.Next() { 180 var ( 181 m Mailserver 182 addrStr string 183 password sql.NullString 184 ) 185 if err := rows.Scan( 186 &m.ID, 187 &m.Name, 188 &addrStr, 189 &password, 190 &m.Fleet, 191 ); err != nil { 192 return nil, err 193 } 194 m.Custom = true 195 if password.Valid { 196 m.Password = password.String 197 } 198 199 // TODO: we are only storing the multiaddress. 200 // In a future PR we must allow storing multiple multiaddresses and ENR 201 maddr, err := multiaddr.NewMultiaddr(addrStr) 202 if err != nil { 203 return nil, err 204 } 205 m.Addr = &maddr 206 207 result = append(result, m) 208 } 209 210 return result, nil 211 } 212 213 func (d *Database) Delete(id string) error { 214 _, err := d.db.Exec(`DELETE FROM mailservers WHERE id = ?`, id) 215 return err 216 } 217 218 func (d *Database) AddGaps(gaps []MailserverRequestGap) error { 219 tx, err := d.db.Begin() 220 if err != nil { 221 return err 222 } 223 defer func() { 224 if err == nil { 225 err = tx.Commit() 226 return 227 } 228 _ = tx.Rollback() 229 }() 230 231 for _, gap := range gaps { 232 233 _, err = tx.Exec(`INSERT OR REPLACE INTO mailserver_request_gaps( 234 id, 235 chat_id, 236 gap_from, 237 gap_to 238 ) VALUES (?, ?, ?, ?)`, 239 gap.ID, 240 gap.ChatID, 241 gap.From, 242 gap.To, 243 ) 244 if err != nil { 245 return err 246 } 247 248 } 249 return nil 250 } 251 252 func (d *Database) RequestGaps(chatID string) ([]MailserverRequestGap, error) { 253 var result []MailserverRequestGap 254 255 rows, err := d.db.Query(`SELECT id, chat_id, gap_from, gap_to FROM mailserver_request_gaps WHERE chat_id = ?`, chatID) 256 if err != nil { 257 return nil, err 258 } 259 defer rows.Close() 260 261 for rows.Next() { 262 var m MailserverRequestGap 263 if err := rows.Scan( 264 &m.ID, 265 &m.ChatID, 266 &m.From, 267 &m.To, 268 ); err != nil { 269 return nil, err 270 } 271 result = append(result, m) 272 } 273 274 return result, nil 275 } 276 277 func (d *Database) DeleteGaps(ids []string) error { 278 if len(ids) == 0 { 279 return nil 280 } 281 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 282 query := fmt.Sprintf(`DELETE FROM mailserver_request_gaps WHERE id IN (%s)`, inVector) // nolint: gosec 283 idsArgs := make([]interface{}, 0, len(ids)) 284 for _, id := range ids { 285 idsArgs = append(idsArgs, id) 286 } 287 288 _, err := d.db.Exec(query, idsArgs...) 289 return err 290 } 291 292 func (d *Database) DeleteGapsByChatID(chatID string) error { 293 _, err := d.db.Exec(`DELETE FROM mailserver_request_gaps WHERE chat_id = ?`, chatID) 294 return err 295 } 296 297 func (d *Database) AddTopic(topic MailserverTopic) error { 298 299 chatIDs := sqlStringSlice(topic.ChatIDs) 300 _, err := d.db.Exec(`INSERT OR REPLACE INTO mailserver_topics( 301 pubsub_topic, 302 topic, 303 chat_ids, 304 last_request, 305 discovery, 306 negotiated 307 ) VALUES (?, ?, ?, ?, ?, ?)`, 308 topic.PubsubTopic, 309 topic.ContentTopic, 310 chatIDs, 311 topic.LastRequest, 312 topic.Discovery, 313 topic.Negotiated, 314 ) 315 return err 316 } 317 318 func (d *Database) AddTopics(topics []MailserverTopic) (err error) { 319 var tx *sql.Tx 320 tx, err = d.db.Begin() 321 if err != nil { 322 return 323 } 324 defer func() { 325 if err == nil { 326 err = tx.Commit() 327 return 328 } 329 _ = tx.Rollback() 330 }() 331 332 for _, topic := range topics { 333 chatIDs := sqlStringSlice(topic.ChatIDs) 334 _, err = tx.Exec(`INSERT OR REPLACE INTO mailserver_topics( 335 pubsub_topic, 336 topic, 337 chat_ids, 338 last_request, 339 discovery, 340 negotiated 341 ) VALUES (?, ?, ?, ?, ?, ?)`, 342 topic.PubsubTopic, 343 topic.ContentTopic, 344 chatIDs, 345 topic.LastRequest, 346 topic.Discovery, 347 topic.Negotiated, 348 ) 349 if err != nil { 350 return 351 } 352 } 353 return 354 } 355 356 func (d *Database) Topics() ([]MailserverTopic, error) { 357 var result []MailserverTopic 358 359 rows, err := d.db.Query(`SELECT pubsub_topic, topic, chat_ids, last_request,discovery,negotiated FROM mailserver_topics`) 360 if err != nil { 361 return nil, err 362 } 363 defer rows.Close() 364 365 for rows.Next() { 366 var ( 367 t MailserverTopic 368 chatIDs sqlStringSlice 369 ) 370 if err := rows.Scan( 371 &t.PubsubTopic, 372 &t.ContentTopic, 373 &chatIDs, 374 &t.LastRequest, 375 &t.Discovery, 376 &t.Negotiated, 377 ); err != nil { 378 return nil, err 379 } 380 t.ChatIDs = chatIDs 381 result = append(result, t) 382 } 383 384 return result, nil 385 } 386 387 func (d *Database) ResetLastRequest(pubsubTopic, contentTopic string) error { 388 _, err := d.db.Exec("UPDATE mailserver_topics SET last_request = 0 WHERE pubsub_topic = ? AND topic = ?", pubsubTopic, contentTopic) 389 return err 390 } 391 392 func (d *Database) DeleteTopic(pubsubTopic, contentTopic string) error { 393 _, err := d.db.Exec(`DELETE FROM mailserver_topics WHERE pubsub_topic = ? AND topic = ?`, pubsubTopic, contentTopic) 394 return err 395 } 396 397 // SetTopics deletes all topics excepts the one set, or upsert those if 398 // missing 399 func (d *Database) SetTopics(filters []*transport.Filter) (err error) { 400 var tx *sql.Tx 401 tx, err = d.db.Begin() 402 if err != nil { 403 return err 404 } 405 defer func() { 406 if err == nil { 407 err = tx.Commit() 408 return 409 } 410 _ = tx.Rollback() 411 }() 412 413 if len(filters) == 0 { 414 return nil 415 } 416 417 contentTopicsPerPubsubTopic := make(map[string]map[string]struct{}) 418 for _, filter := range filters { 419 contentTopics, ok := contentTopicsPerPubsubTopic[filter.PubsubTopic] 420 if !ok { 421 contentTopics = make(map[string]struct{}) 422 } 423 contentTopics[filter.ContentTopic.String()] = struct{}{} 424 contentTopicsPerPubsubTopic[filter.PubsubTopic] = contentTopics 425 } 426 427 for pubsubTopic, contentTopics := range contentTopicsPerPubsubTopic { 428 topicsArgs := make([]interface{}, 0, len(contentTopics)+1) 429 topicsArgs = append(topicsArgs, pubsubTopic) 430 for ct := range contentTopics { 431 topicsArgs = append(topicsArgs, ct) 432 } 433 434 inVector := strings.Repeat("?, ", len(contentTopics)-1) + "?" 435 436 // Delete topics 437 query := "DELETE FROM mailserver_topics WHERE pubsub_topic = ? AND topic NOT IN (" + inVector + ")" // nolint: gosec 438 _, err = tx.Exec(query, topicsArgs...) 439 } 440 441 // Default to now - 1.day 442 lastRequest := (time.Now().Add(-24 * time.Hour)).Unix() 443 // Insert if not existing 444 for _, filter := range filters { 445 // fetch 446 var topic string 447 err = tx.QueryRow(`SELECT topic FROM mailserver_topics WHERE topic = ? AND pubsub_topic = ?`, filter.ContentTopic.String(), filter.PubsubTopic).Scan(&topic) 448 if err != nil && err != sql.ErrNoRows { 449 return 450 } else if err == sql.ErrNoRows { 451 // we insert the topic 452 _, err = tx.Exec(`INSERT INTO mailserver_topics(topic,pubsub_topic,last_request,discovery,negotiated) VALUES (?,?,?,?,?)`, filter.ContentTopic.String(), filter.PubsubTopic, lastRequest, filter.Discovery, filter.Negotiated) 453 } 454 if err != nil { 455 return 456 } 457 } 458 459 return 460 } 461 462 func (d *Database) AddChatRequestRange(req ChatRequestRange) error { 463 _, err := d.db.Exec(`INSERT OR REPLACE INTO mailserver_chat_request_ranges( 464 chat_id, 465 lowest_request_from, 466 highest_request_to 467 ) VALUES (?, ?, ?)`, 468 req.ChatID, 469 req.LowestRequestFrom, 470 req.HighestRequestTo, 471 ) 472 return err 473 } 474 475 func (d *Database) AddChatRequestRanges(reqs []ChatRequestRange) (err error) { 476 var tx *sql.Tx 477 tx, err = d.db.Begin() 478 if err != nil { 479 return err 480 } 481 defer func() { 482 if err == nil { 483 err = tx.Commit() 484 return 485 } 486 _ = tx.Rollback() 487 }() 488 for _, req := range reqs { 489 490 _, err = tx.Exec(`INSERT OR REPLACE INTO mailserver_chat_request_ranges( 491 chat_id, 492 lowest_request_from, 493 highest_request_to 494 ) VALUES (?, ?, ?)`, 495 req.ChatID, 496 req.LowestRequestFrom, 497 req.HighestRequestTo, 498 ) 499 if err != nil { 500 return 501 } 502 } 503 return 504 } 505 506 func (d *Database) ChatRequestRanges() ([]ChatRequestRange, error) { 507 var result []ChatRequestRange 508 509 rows, err := d.db.Query(`SELECT chat_id, lowest_request_from, highest_request_to FROM mailserver_chat_request_ranges`) 510 if err != nil { 511 return nil, err 512 } 513 defer rows.Close() 514 515 for rows.Next() { 516 var req ChatRequestRange 517 if err := rows.Scan( 518 &req.ChatID, 519 &req.LowestRequestFrom, 520 &req.HighestRequestTo, 521 ); err != nil { 522 return nil, err 523 } 524 result = append(result, req) 525 } 526 527 return result, nil 528 } 529 530 func (d *Database) DeleteChatRequestRange(chatID string) error { 531 _, err := d.db.Exec(`DELETE FROM mailserver_chat_request_ranges WHERE chat_id = ?`, chatID) 532 return err 533 }