github.com/status-im/status-go@v1.1.0/mailserver/mailserver_test.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser 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 // The go-ethereum library 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 Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package mailserver 18 19 import ( 20 "crypto/ecdsa" 21 "encoding/binary" 22 "errors" 23 "fmt" 24 "testing" 25 "time" 26 27 "github.com/stretchr/testify/suite" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/crypto" 31 "github.com/ethereum/go-ethereum/rlp" 32 33 "github.com/status-im/status-go/eth-node/types" 34 "github.com/status-im/status-go/params" 35 waku "github.com/status-im/status-go/waku" 36 wakucommon "github.com/status-im/status-go/waku/common" 37 ) 38 39 const powRequirement = 0.00001 40 41 var keyID string 42 var seed = time.Now().Unix() 43 var testPayload = []byte("test payload") 44 45 type ServerTestParams struct { 46 topic types.TopicType 47 birth uint32 48 low uint32 49 upp uint32 50 limit uint32 51 key *ecdsa.PrivateKey 52 } 53 54 func TestMailserverSuite(t *testing.T) { 55 suite.Run(t, new(MailserverSuite)) 56 } 57 58 type MailserverSuite struct { 59 suite.Suite 60 server *WakuMailServer 61 shh *waku.Waku 62 config *params.WakuConfig 63 dataDir string 64 } 65 66 func (s *MailserverSuite) SetupTest() { 67 s.server = &WakuMailServer{} 68 s.shh = waku.New(&waku.DefaultConfig, nil) 69 s.shh.RegisterMailServer(s.server) 70 71 tmpDir := s.T().TempDir() 72 s.dataDir = tmpDir 73 74 s.config = ¶ms.WakuConfig{ 75 DataDir: tmpDir, 76 MailServerPassword: "testpassword", 77 } 78 } 79 80 func (s *MailserverSuite) TestInit() { 81 testCases := []struct { 82 config params.WakuConfig 83 expectedError error 84 info string 85 }{ 86 { 87 config: params.WakuConfig{DataDir: ""}, 88 expectedError: errDirectoryNotProvided, 89 info: "config with empty DataDir", 90 }, 91 { 92 config: params.WakuConfig{ 93 DataDir: s.config.DataDir, 94 MailServerPassword: "pwd", 95 }, 96 expectedError: nil, 97 info: "config with correct DataDir and Password", 98 }, 99 { 100 config: params.WakuConfig{ 101 DataDir: s.config.DataDir, 102 MailServerPassword: "pwd", 103 MailServerRateLimit: 5, 104 }, 105 expectedError: nil, 106 info: "config with rate limit", 107 }, 108 } 109 110 for _, testCase := range testCases { 111 // to satisfy gosec: C601 checks 112 tc := testCase 113 s.T().Run(tc.info, func(*testing.T) { 114 mailServer := &WakuMailServer{} 115 shh := waku.New(&waku.DefaultConfig, nil) 116 shh.RegisterMailServer(mailServer) 117 118 err := mailServer.Init(shh, &tc.config) 119 s.Require().Equal(tc.expectedError, err) 120 if err == nil { 121 defer mailServer.Close() 122 } 123 124 // db should be open only if there was no error 125 if tc.expectedError == nil { 126 s.NotNil(mailServer.ms.db) 127 } else { 128 s.Nil(mailServer.ms) 129 } 130 131 if tc.config.MailServerRateLimit > 0 { 132 s.NotNil(mailServer.ms.rateLimiter) 133 } 134 }) 135 } 136 } 137 138 func (s *MailserverSuite) TestArchive() { 139 config := *s.config 140 141 err := s.server.Init(s.shh, &config) 142 s.Require().NoError(err) 143 defer s.server.Close() 144 145 env, err := generateEnvelope(time.Now()) 146 s.NoError(err) 147 rawEnvelope, err := rlp.EncodeToBytes(env) 148 s.NoError(err) 149 150 s.server.Archive(env) 151 key := NewDBKey(env.Expiry-env.TTL, types.TopicType(env.Topic), types.Hash(env.Hash())) 152 archivedEnvelope, err := s.server.ms.db.GetEnvelope(key) 153 s.NoError(err) 154 155 s.Equal(rawEnvelope, archivedEnvelope) 156 } 157 158 func (s *MailserverSuite) TestManageLimits() { 159 err := s.server.Init(s.shh, s.config) 160 s.NoError(err) 161 s.server.ms.rateLimiter = newRateLimiter(time.Duration(5) * time.Millisecond) 162 s.False(s.server.ms.exceedsPeerRequests(types.BytesToHash([]byte("peerID")))) 163 s.Equal(1, len(s.server.ms.rateLimiter.db)) 164 firstSaved := s.server.ms.rateLimiter.db["peerID"] 165 166 // second call when limit is not accomplished does not store a new limit 167 s.True(s.server.ms.exceedsPeerRequests(types.BytesToHash([]byte("peerID")))) 168 s.Equal(1, len(s.server.ms.rateLimiter.db)) 169 s.Equal(firstSaved, s.server.ms.rateLimiter.db["peerID"]) 170 } 171 172 func (s *MailserverSuite) TestDBKey() { 173 var h types.Hash 174 var emptyTopic types.TopicType 175 i := uint32(time.Now().Unix()) 176 k := NewDBKey(i, emptyTopic, h) 177 s.Equal(len(k.Bytes()), DBKeyLength, "wrong DB key length") 178 s.Equal(byte(i%0x100), k.Bytes()[3], "raw representation should be big endian") 179 s.Equal(byte(i/0x1000000), k.Bytes()[0], "big endian expected") 180 } 181 182 func (s *MailserverSuite) TestRequestPaginationLimit() { 183 s.setupServer(s.server) 184 defer s.server.Close() 185 186 var ( 187 sentEnvelopes []*wakucommon.Envelope 188 sentHashes []common.Hash 189 receivedHashes []common.Hash 190 archiveKeys []string 191 ) 192 193 now := time.Now() 194 count := uint32(10) 195 196 for i := count; i > 0; i-- { 197 sentTime := now.Add(time.Duration(-i) * time.Second) 198 env, err := generateEnvelope(sentTime) 199 s.NoError(err) 200 s.server.Archive(env) 201 key := NewDBKey(env.Expiry-env.TTL, types.TopicType(env.Topic), types.Hash(env.Hash())) 202 archiveKeys = append(archiveKeys, fmt.Sprintf("%x", key.Cursor())) 203 sentEnvelopes = append(sentEnvelopes, env) 204 sentHashes = append(sentHashes, env.Hash()) 205 } 206 207 reqLimit := uint32(6) 208 peerID, request, err := s.prepareRequest(sentEnvelopes, reqLimit) 209 s.NoError(err) 210 payload, err := s.server.decompositeRequest(peerID, request) 211 s.NoError(err) 212 s.Nil(payload.Cursor) 213 s.Equal(reqLimit, payload.Limit) 214 215 receivedHashes, cursor, _ := processRequestAndCollectHashes(s.server, payload) 216 217 // 10 envelopes sent 218 s.Equal(count, uint32(len(sentEnvelopes))) 219 // 6 envelopes received 220 s.Len(receivedHashes, int(payload.Limit)) 221 // the 6 envelopes received should be in forward order 222 s.Equal(sentHashes[:payload.Limit], receivedHashes) 223 // cursor should be the key of the last envelope of the last page 224 s.Equal(archiveKeys[payload.Limit-1], fmt.Sprintf("%x", cursor)) 225 226 // second page 227 payload.Cursor = cursor 228 receivedHashes, cursor, _ = processRequestAndCollectHashes(s.server, payload) 229 230 // 4 envelopes received 231 s.Equal(int(count-payload.Limit), len(receivedHashes)) 232 // cursor is nil because there are no other pages 233 s.Nil(cursor) 234 } 235 236 func (s *MailserverSuite) TestMailServer() { 237 s.setupServer(s.server) 238 defer s.server.Close() 239 240 env, err := generateEnvelope(time.Now()) 241 s.NoError(err) 242 243 s.server.Archive(env) 244 245 testCases := []struct { 246 params *ServerTestParams 247 expect bool 248 isOK bool 249 info string 250 }{ 251 { 252 params: s.defaultServerParams(env), 253 expect: true, 254 isOK: true, 255 info: "Processing a request where from and to are equal to an existing register, should provide results", 256 }, 257 { 258 params: func() *ServerTestParams { 259 params := s.defaultServerParams(env) 260 params.low = params.birth + 1 261 params.upp = params.birth + 1 262 263 return params 264 }(), 265 expect: false, 266 isOK: true, 267 info: "Processing a request where from and to are greater than any existing register, should not provide results", 268 }, 269 { 270 params: func() *ServerTestParams { 271 params := s.defaultServerParams(env) 272 params.upp = params.birth + 1 273 params.topic[0] = 0xFF 274 275 return params 276 }(), 277 expect: false, 278 isOK: true, 279 info: "Processing a request where to is greater than any existing register and with a specific topic, should not provide results", 280 }, 281 { 282 params: func() *ServerTestParams { 283 params := s.defaultServerParams(env) 284 params.low = params.birth 285 params.upp = params.birth - 1 286 287 return params 288 }(), 289 isOK: false, 290 info: "Processing a request where to is lower than from should fail", 291 }, 292 { 293 params: func() *ServerTestParams { 294 params := s.defaultServerParams(env) 295 params.low = 0 296 params.upp = params.birth + 24 297 298 return params 299 }(), 300 isOK: false, 301 info: "Processing a request where difference between from and to is > 24 should fail", 302 }, 303 } 304 for _, testCase := range testCases { 305 // to satisfy gosec: C601 checks 306 tc := testCase 307 s.T().Run(tc.info, func(*testing.T) { 308 request := s.createRequest(tc.params) 309 src := crypto.FromECDSAPub(&tc.params.key.PublicKey) 310 payload, err := s.server.decompositeRequest(src, request) 311 s.Equal(tc.isOK, err == nil) 312 if err == nil { 313 s.Equal(tc.params.low, payload.Lower) 314 s.Equal(tc.params.upp, payload.Upper) 315 s.Equal(tc.params.limit, payload.Limit) 316 s.Equal(types.TopicToBloom(tc.params.topic), payload.Bloom) 317 s.Equal(tc.expect, s.messageExists(env, tc.params.low, tc.params.upp, payload.Bloom, tc.params.limit)) 318 319 src[0]++ 320 _, err = s.server.decompositeRequest(src, request) 321 s.True(err == nil) 322 } 323 }) 324 } 325 } 326 327 func (s *MailserverSuite) TestDecodeRequest() { 328 s.setupServer(s.server) 329 defer s.server.Close() 330 331 payload := MessagesRequestPayload{ 332 Lower: 50, 333 Upper: 100, 334 Bloom: []byte{0x01}, 335 Topics: [][]byte{}, 336 Limit: 10, 337 Cursor: []byte{}, 338 Batch: true, 339 } 340 data, err := rlp.EncodeToBytes(payload) 341 s.Require().NoError(err) 342 343 id, err := s.shh.NewKeyPair() 344 s.Require().NoError(err) 345 srcKey, err := s.shh.GetPrivateKey(id) 346 s.Require().NoError(err) 347 348 env := s.createEnvelope(types.TopicType{0x01}, data, srcKey) 349 350 decodedPayload, err := s.server.decodeRequest(nil, env) 351 s.Require().NoError(err) 352 s.Equal(payload, decodedPayload) 353 } 354 355 func (s *MailserverSuite) TestDecodeRequestNoUpper() { 356 s.setupServer(s.server) 357 defer s.server.Close() 358 359 payload := MessagesRequestPayload{ 360 Lower: 50, 361 Bloom: []byte{0x01}, 362 Limit: 10, 363 Cursor: []byte{}, 364 Batch: true, 365 } 366 data, err := rlp.EncodeToBytes(payload) 367 s.Require().NoError(err) 368 369 id, err := s.shh.NewKeyPair() 370 s.Require().NoError(err) 371 srcKey, err := s.shh.GetPrivateKey(id) 372 s.Require().NoError(err) 373 374 env := s.createEnvelope(types.TopicType{0x01}, data, srcKey) 375 376 decodedPayload, err := s.server.decodeRequest(nil, env) 377 s.Require().NoError(err) 378 s.NotEqual(0, decodedPayload.Upper) 379 } 380 381 func (s *MailserverSuite) TestProcessRequestDeadlockHandling() { 382 s.setupServer(s.server) 383 defer s.server.Close() 384 385 var archievedEnvelopes []*wakucommon.Envelope 386 387 now := time.Now() 388 count := uint32(10) 389 390 // Archieve some envelopes. 391 for i := count; i > 0; i-- { 392 sentTime := now.Add(time.Duration(-i) * time.Second) 393 env, err := generateEnvelope(sentTime) 394 s.NoError(err) 395 s.server.Archive(env) 396 archievedEnvelopes = append(archievedEnvelopes, env) 397 } 398 399 // Prepare a request. 400 peerID, request, err := s.prepareRequest(archievedEnvelopes, 5) 401 s.NoError(err) 402 payload, err := s.server.decompositeRequest(peerID, request) 403 s.NoError(err) 404 405 testCases := []struct { 406 Name string 407 Timeout time.Duration 408 Verify func( 409 Iterator, 410 time.Duration, // processRequestInBundles timeout 411 chan []rlp.RawValue, 412 ) 413 }{ 414 { 415 Name: "finish processing using `done` channel", 416 Timeout: time.Second * 5, 417 Verify: func( 418 iter Iterator, 419 timeout time.Duration, 420 bundles chan []rlp.RawValue, 421 ) { 422 done := make(chan struct{}) 423 processFinished := make(chan struct{}) 424 425 go func() { 426 s.server.ms.processRequestInBundles(iter, payload.Bloom, payload.Topics, int(payload.Limit), timeout, "req-01", bundles, done) 427 close(processFinished) 428 }() 429 go close(done) 430 431 select { 432 case <-processFinished: 433 case <-time.After(time.Second): 434 s.FailNow("waiting for processing finish timed out") 435 } 436 }, 437 }, 438 { 439 Name: "finish processing due to timeout", 440 Timeout: time.Second, 441 Verify: func( 442 iter Iterator, 443 timeout time.Duration, 444 bundles chan []rlp.RawValue, 445 ) { 446 done := make(chan struct{}) // won't be closed because we test timeout of `processRequestInBundles()` 447 processFinished := make(chan struct{}) 448 449 go func() { 450 s.server.ms.processRequestInBundles(iter, payload.Bloom, payload.Topics, int(payload.Limit), time.Second, "req-01", bundles, done) 451 close(processFinished) 452 }() 453 454 select { 455 case <-processFinished: 456 case <-time.After(time.Second * 5): 457 s.FailNow("waiting for processing finish timed out") 458 } 459 }, 460 }, 461 } 462 463 for _, tc := range testCases { 464 s.T().Run(tc.Name, func(t *testing.T) { 465 iter, err := s.server.ms.createIterator(payload) 466 s.Require().NoError(err) 467 468 defer func() { _ = iter.Release() }() 469 470 // Nothing reads from this unbuffered channel which simulates a situation 471 // when a connection between a peer and mail server was dropped. 472 bundles := make(chan []rlp.RawValue) 473 474 tc.Verify(iter, tc.Timeout, bundles) 475 }) 476 } 477 } 478 479 func (s *MailserverSuite) messageExists(envelope *wakucommon.Envelope, low, upp uint32, bloom []byte, limit uint32) bool { 480 receivedHashes, _, _ := processRequestAndCollectHashes(s.server, MessagesRequestPayload{ 481 Lower: low, 482 Upper: upp, 483 Bloom: bloom, 484 Limit: limit, 485 }) 486 for _, hash := range receivedHashes { 487 if hash == envelope.Hash() { 488 return true 489 } 490 } 491 return false 492 } 493 494 func (s *MailserverSuite) setupServer(server *WakuMailServer) { 495 const password = "password_for_this_test" 496 497 s.shh = waku.New(&waku.DefaultConfig, nil) 498 s.shh.RegisterMailServer(server) 499 500 err := server.Init(s.shh, ¶ms.WakuConfig{ 501 DataDir: s.dataDir, 502 MailServerPassword: password, 503 MinimumPoW: powRequirement, 504 }) 505 if err != nil { 506 s.T().Fatal(err) 507 } 508 509 keyID, err = s.shh.AddSymKeyFromPassword(password) 510 if err != nil { 511 s.T().Fatalf("failed to create symmetric key for mail request: %s", err) 512 } 513 } 514 515 func (s *MailserverSuite) prepareRequest(envelopes []*wakucommon.Envelope, limit uint32) ( 516 []byte, *wakucommon.Envelope, error, 517 ) { 518 if len(envelopes) == 0 { 519 return nil, nil, errors.New("envelopes is empty") 520 } 521 522 now := time.Now() 523 524 params := s.defaultServerParams(envelopes[0]) 525 params.low = uint32(now.Add(time.Duration(-len(envelopes)) * time.Second).Unix()) 526 params.upp = uint32(now.Unix()) 527 params.limit = limit 528 529 request := s.createRequest(params) 530 peerID := crypto.FromECDSAPub(¶ms.key.PublicKey) 531 532 return peerID, request, nil 533 } 534 535 func (s *MailserverSuite) defaultServerParams(env *wakucommon.Envelope) *ServerTestParams { 536 id, err := s.shh.NewKeyPair() 537 if err != nil { 538 s.T().Fatalf("failed to generate new key pair with seed %d: %s.", seed, err) 539 } 540 testPeerID, err := s.shh.GetPrivateKey(id) 541 if err != nil { 542 s.T().Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err) 543 } 544 birth := env.Expiry - env.TTL 545 546 return &ServerTestParams{ 547 topic: types.TopicType(env.Topic), 548 birth: birth, 549 low: birth - 1, 550 upp: birth + 1, 551 limit: 0, 552 key: testPeerID, 553 } 554 } 555 556 func (s *MailserverSuite) createRequest(p *ServerTestParams) *wakucommon.Envelope { 557 bloom := types.TopicToBloom(p.topic) 558 data := make([]byte, 8) 559 binary.BigEndian.PutUint32(data, p.low) 560 binary.BigEndian.PutUint32(data[4:], p.upp) 561 data = append(data, bloom...) 562 563 if p.limit != 0 { 564 limitData := make([]byte, 4) 565 binary.BigEndian.PutUint32(limitData, p.limit) 566 data = append(data, limitData...) 567 } 568 569 return s.createEnvelope(p.topic, data, p.key) 570 } 571 572 func (s *MailserverSuite) createEnvelope(topic types.TopicType, data []byte, srcKey *ecdsa.PrivateKey) *wakucommon.Envelope { 573 key, err := s.shh.GetSymKey(keyID) 574 if err != nil { 575 s.T().Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err) 576 } 577 578 params := &wakucommon.MessageParams{ 579 KeySym: key, 580 Topic: wakucommon.TopicType(topic), 581 Payload: data, 582 PoW: powRequirement * 2, 583 WorkTime: 2, 584 Src: srcKey, 585 } 586 587 msg, err := wakucommon.NewSentMessage(params) 588 if err != nil { 589 s.T().Fatalf("failed to create new message with seed %d: %s.", seed, err) 590 } 591 592 env, err := msg.Wrap(params, time.Now()) 593 if err != nil { 594 s.T().Fatalf("failed to wrap with seed %d: %s.", seed, err) 595 } 596 return env 597 } 598 599 func generateEnvelopeWithKeys(sentTime time.Time, keySym []byte, keyAsym *ecdsa.PublicKey) (*wakucommon.Envelope, error) { 600 params := &wakucommon.MessageParams{ 601 Topic: wakucommon.TopicType{0x1F, 0x7E, 0xA1, 0x7F}, 602 Payload: testPayload, 603 PoW: powRequirement, 604 WorkTime: 2, 605 } 606 607 if len(keySym) > 0 { 608 params.KeySym = keySym 609 } else if keyAsym != nil { 610 params.Dst = keyAsym 611 } 612 613 msg, err := wakucommon.NewSentMessage(params) 614 if err != nil { 615 return nil, fmt.Errorf("failed to create new message with seed %d: %s", seed, err) 616 } 617 env, err := msg.Wrap(params, sentTime) 618 if err != nil { 619 return nil, fmt.Errorf("failed to wrap with seed %d: %s", seed, err) 620 } 621 622 return env, nil 623 } 624 625 func generateEnvelope(sentTime time.Time) (*wakucommon.Envelope, error) { 626 h := crypto.Keccak256Hash([]byte("test sample data")) 627 return generateEnvelopeWithKeys(sentTime, h[:], nil) 628 } 629 630 func processRequestAndCollectHashes(server *WakuMailServer, payload MessagesRequestPayload) ([]common.Hash, []byte, types.Hash) { 631 iter, _ := server.ms.createIterator(payload) 632 defer func() { _ = iter.Release() }() 633 bundles := make(chan []rlp.RawValue, 10) 634 done := make(chan struct{}) 635 636 var hashes []common.Hash 637 go func() { 638 for bundle := range bundles { 639 for _, rawEnvelope := range bundle { 640 var env *wakucommon.Envelope 641 if err := rlp.DecodeBytes(rawEnvelope, &env); err != nil { 642 panic(err) 643 } 644 hashes = append(hashes, env.Hash()) 645 } 646 } 647 close(done) 648 }() 649 650 cursor, lastHash := server.ms.processRequestInBundles(iter, payload.Bloom, payload.Topics, int(payload.Limit), time.Minute, "req-01", bundles, done) 651 652 <-done 653 654 return hashes, cursor, lastHash 655 }