refactor_: remove mailservers code
This commit is contained in:
parent
05e3a35bf7
commit
04833f559f
|
@ -148,7 +148,6 @@ func randomNodeConfig() *params.NodeConfig {
|
||||||
Web3ProviderConfig: params.Web3ProviderConfig{Enabled: randomBool()},
|
Web3ProviderConfig: params.Web3ProviderConfig{Enabled: randomBool()},
|
||||||
ConnectorConfig: params.ConnectorConfig{Enabled: randomBool()},
|
ConnectorConfig: params.ConnectorConfig{Enabled: randomBool()},
|
||||||
SwarmConfig: params.SwarmConfig{Enabled: randomBool()},
|
SwarmConfig: params.SwarmConfig{Enabled: randomBool()},
|
||||||
MailServerRegistryAddress: randomString(),
|
|
||||||
HTTPEnabled: randomBool(),
|
HTTPEnabled: randomBool(),
|
||||||
HTTPHost: randomString(),
|
HTTPHost: randomString(),
|
||||||
HTTPPort: randomInt(math.MaxInt64),
|
HTTPPort: randomInt(math.MaxInt64),
|
||||||
|
@ -230,20 +229,10 @@ func randomNodeConfig() *params.NodeConfig {
|
||||||
Enabled: randomBool(),
|
Enabled: randomBool(),
|
||||||
LightClient: randomBool(),
|
LightClient: randomBool(),
|
||||||
FullNode: randomBool(),
|
FullNode: randomBool(),
|
||||||
EnableMailServer: randomBool(),
|
|
||||||
DataDir: randomString(),
|
DataDir: randomString(),
|
||||||
MinimumPoW: randomFloat(math.MaxInt64),
|
MinimumPoW: randomFloat(math.MaxInt64),
|
||||||
MailServerPassword: randomString(),
|
|
||||||
MailServerRateLimit: randomInt(math.MaxInt64),
|
|
||||||
MailServerDataRetention: randomInt(math.MaxInt64),
|
|
||||||
TTL: randomInt(math.MaxInt64),
|
TTL: randomInt(math.MaxInt64),
|
||||||
MaxMessageSize: uint32(randomInt(math.MaxInt64)),
|
MaxMessageSize: uint32(randomInt(math.MaxInt64)),
|
||||||
DatabaseConfig: params.DatabaseConfig{
|
|
||||||
PGConfig: params.PGConfig{
|
|
||||||
Enabled: randomBool(),
|
|
||||||
URI: randomString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
EnableRateLimiter: randomBool(),
|
EnableRateLimiter: randomBool(),
|
||||||
PacketRateLimitIP: int64(randomInt(math.MaxInt64)),
|
PacketRateLimitIP: int64(randomInt(math.MaxInt64)),
|
||||||
PacketRateLimitPeerID: int64(randomInt(math.MaxInt64)),
|
PacketRateLimitPeerID: int64(randomInt(math.MaxInt64)),
|
||||||
|
|
|
@ -56,7 +56,6 @@ var (
|
||||||
|
|
||||||
dataDir = flag.String("dir", getDefaultDataDir(), "Directory used by node to store data")
|
dataDir = flag.String("dir", getDefaultDataDir(), "Directory used by node to store data")
|
||||||
register = flag.Bool("register", false, "Register and make the node discoverable by other nodes")
|
register = flag.Bool("register", false, "Register and make the node discoverable by other nodes")
|
||||||
mailserver = flag.Bool("mailserver", false, "Enable Mail Server with default configuration")
|
|
||||||
networkID = flag.Int(
|
networkID = flag.Int(
|
||||||
"network-id",
|
"network-id",
|
||||||
params.GoerliNetworkID,
|
params.GoerliNetworkID,
|
||||||
|
@ -118,9 +117,6 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []params.Option{params.WithFleet(*fleet)}
|
opts := []params.Option{params.WithFleet(*fleet)}
|
||||||
if *mailserver {
|
|
||||||
opts = append(opts, params.WithMailserver())
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := params.NewNodeConfigWithDefaultsAndFiles(
|
config, err := params.NewNodeConfigWithDefaultsAndFiles(
|
||||||
*dataDir,
|
*dataDir,
|
||||||
|
@ -140,9 +136,7 @@ func main() {
|
||||||
config.ListenAddr = *listenAddr
|
config.ListenAddr = *listenAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
if *register && *mailserver {
|
if *register {
|
||||||
config.RegisterTopics = append(config.RegisterTopics, params.MailServerDiscv5Topic)
|
|
||||||
} else if *register {
|
|
||||||
config.RegisterTopics = append(config.RegisterTopics, params.WhisperDiscv5Topic)
|
config.RegisterTopics = append(config.RegisterTopics, params.WhisperDiscv5Topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +245,6 @@ func main() {
|
||||||
identity,
|
identity,
|
||||||
gethbridge.NewNodeBridge(backend.StatusNode().GethNode(), backend.StatusNode().WakuService(), backend.StatusNode().WakuV2Service()),
|
gethbridge.NewNodeBridge(backend.StatusNode().GethNode(), backend.StatusNode().WakuService(), backend.StatusNode().WakuV2Service()),
|
||||||
installationID.String(),
|
installationID.String(),
|
||||||
nil,
|
|
||||||
config.Version,
|
config.Version,
|
||||||
options...,
|
options...,
|
||||||
)
|
)
|
||||||
|
@ -324,7 +317,6 @@ func main() {
|
||||||
identity,
|
identity,
|
||||||
gethbridge.NewNodeBridge(backend.StatusNode().GethNode(), backend.StatusNode().WakuService(), backend.StatusNode().WakuV2Service()),
|
gethbridge.NewNodeBridge(backend.StatusNode().GethNode(), backend.StatusNode().WakuService(), backend.StatusNode().WakuV2Service()),
|
||||||
installationID.String(),
|
installationID.String(),
|
||||||
nil,
|
|
||||||
config.Version,
|
config.Version,
|
||||||
options...,
|
options...,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableNTPSync": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"MailServerPassword": "status-offline-inbox"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ package gethbridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/waku"
|
|
||||||
|
|
||||||
wakucommon "github.com/status-im/status-go/waku/common"
|
wakucommon "github.com/status-im/status-go/waku/common"
|
||||||
wakuv2common "github.com/status-im/status-go/wakuv2/common"
|
wakuv2common "github.com/status-im/status-go/wakuv2/common"
|
||||||
|
@ -21,8 +20,6 @@ func NewWakuEnvelopeEventWrapper(envelopeEvent *wakucommon.EnvelopeEvent) *types
|
||||||
for index := range data {
|
for index := range data {
|
||||||
wrappedData[index] = *NewWakuEnvelopeErrorWrapper(&data[index])
|
wrappedData[index] = *NewWakuEnvelopeErrorWrapper(&data[index])
|
||||||
}
|
}
|
||||||
case *waku.MailServerResponse:
|
|
||||||
wrappedData = NewWakuMailServerResponseWrapper(data)
|
|
||||||
}
|
}
|
||||||
return &types.EnvelopeEvent{
|
return &types.EnvelopeEvent{
|
||||||
Event: types.EventType(envelopeEvent.Event),
|
Event: types.EventType(envelopeEvent.Event),
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package gethbridge
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/waku"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewWakuMailServerResponseWrapper returns a types.MailServerResponse object that mimics Geth's MailServerResponse
|
|
||||||
func NewWakuMailServerResponseWrapper(mailServerResponse *waku.MailServerResponse) *types.MailServerResponse {
|
|
||||||
if mailServerResponse == nil {
|
|
||||||
panic("mailServerResponse should not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.MailServerResponse{
|
|
||||||
LastEnvelopeHash: types.Hash(mailServerResponse.LastEnvelopeHash),
|
|
||||||
Cursor: mailServerResponse.Cursor,
|
|
||||||
Error: mailServerResponse.Error,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -53,32 +53,3 @@ func (r *MessagesRequest) SetDefaults(now time.Time) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailServerResponse is the response payload sent by the mailserver.
|
|
||||||
type MailServerResponse struct {
|
|
||||||
LastEnvelopeHash Hash
|
|
||||||
Cursor []byte
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncMailRequest contains details which envelopes should be synced
|
|
||||||
// between Mail Servers.
|
|
||||||
type SyncMailRequest struct {
|
|
||||||
// Lower is a lower bound of time range for which messages are requested.
|
|
||||||
Lower uint32
|
|
||||||
// Upper is a lower bound of time range for which messages are requested.
|
|
||||||
Upper uint32
|
|
||||||
// Bloom is a bloom filter to filter envelopes.
|
|
||||||
Bloom []byte
|
|
||||||
// Limit is the max number of envelopes to return.
|
|
||||||
Limit uint32
|
|
||||||
// Cursor is used for pagination of the results.
|
|
||||||
Cursor []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncEventResponse is a response from the Mail Server
|
|
||||||
// form which the peer received envelopes.
|
|
||||||
type SyncEventResponse struct {
|
|
||||||
Cursor []byte
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
|
@ -43,7 +43,4 @@ type Whisper interface {
|
||||||
GetFilter(id string) Filter
|
GetFilter(id string) Filter
|
||||||
Unsubscribe(id string) error
|
Unsubscribe(id string) error
|
||||||
UnsubscribeMany(ids []string) error
|
UnsubscribeMany(ids []string) error
|
||||||
|
|
||||||
// SyncMessages can be sent between two Mail Servers and syncs envelopes between them.
|
|
||||||
SyncMessages(peerID []byte, req SyncMailRequest) error
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
MailServer
|
|
||||||
==========
|
|
||||||
|
|
||||||
This document is meant to collect various information about our MailServer implementation.
|
|
||||||
|
|
||||||
## Syncing between mail servers
|
|
||||||
|
|
||||||
It might happen that one mail server is behind other due to various reasons like a machine being down for a few minutes etc.
|
|
||||||
|
|
||||||
There is an option to fix such a mail server:
|
|
||||||
1. SSH to a machine where this broken mail server runs,
|
|
||||||
2. Add a mail server from which you want to sync:
|
|
||||||
```
|
|
||||||
# sudo might be not needed in your setup
|
|
||||||
$ echo '{"jsonrpc":"2.0","method":"admin_addPeer", "params": ["enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:30504"], "id":1}' | \
|
|
||||||
sudo socat -d -d - UNIX-CONNECT:/docker/statusd-mail/data/geth.ipc
|
|
||||||
```
|
|
||||||
3. Mark it as a trusted peer:
|
|
||||||
```
|
|
||||||
# sudo might be not needed in your setup
|
|
||||||
$ echo '{"jsonrpc":"2.0","method":"shh_markTrustedPeer", "params": ["enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:30504"], "id":1}' | \
|
|
||||||
sudo socat -d -d - UNIX-CONNECT:/docker/statusd-mail/data/geth.ipc
|
|
||||||
```
|
|
||||||
4. Finally, trigger the sync command:
|
|
||||||
```
|
|
||||||
# sudo might be not needed in your setup
|
|
||||||
$ echo '{"jsonrpc":"2.0","method":"shhext_syncMessages","params":[{"mailServerPeer":"enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:30504", "to": 1550479953, "from": 1550393583, "limit": 1000}],"id":1}' | \
|
|
||||||
sudo socat -d -d - UNIX-CONNECT:/docker/statusd-mail/data/geth.ipc
|
|
||||||
```
|
|
||||||
|
|
||||||
You can add `"followCursor": true` if you want it to automatically download messages until the cursor is empty meaning all data was synced.
|
|
||||||
|
|
||||||
### Debugging
|
|
||||||
|
|
||||||
To verify that your mail server received any responses, watch logs and seek for logs like this:
|
|
||||||
```
|
|
||||||
INFO [02-18|09:08:54.257] received sync response count=217 final=false err= cursor=[]
|
|
||||||
```
|
|
||||||
|
|
||||||
And it should finish with:
|
|
||||||
```
|
|
||||||
INFO [02-18|09:08:54.431] received sync response count=0 final=true err= cursor=[]
|
|
||||||
```
|
|
|
@ -1,85 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dbCleanerBatchSize = 1000
|
|
||||||
dbCleanerPeriod = time.Hour
|
|
||||||
)
|
|
||||||
|
|
||||||
// dbCleaner removes old messages from a db.
|
|
||||||
type dbCleaner struct {
|
|
||||||
sync.RWMutex
|
|
||||||
|
|
||||||
db DB
|
|
||||||
batchSize int
|
|
||||||
retention time.Duration
|
|
||||||
|
|
||||||
period time.Duration
|
|
||||||
cancel chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newDBCleaner returns a new cleaner for db.
|
|
||||||
func newDBCleaner(db DB, retention time.Duration) *dbCleaner {
|
|
||||||
return &dbCleaner{
|
|
||||||
db: db,
|
|
||||||
retention: retention,
|
|
||||||
|
|
||||||
batchSize: dbCleanerBatchSize,
|
|
||||||
period: dbCleanerPeriod,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts a loop that cleans up old messages.
|
|
||||||
func (c *dbCleaner) Start() {
|
|
||||||
log.Info("Starting cleaning envelopes", "period", c.period, "retention", c.retention)
|
|
||||||
|
|
||||||
cancel := make(chan struct{})
|
|
||||||
|
|
||||||
c.Lock()
|
|
||||||
c.cancel = cancel
|
|
||||||
c.Unlock()
|
|
||||||
|
|
||||||
go c.schedule(c.period, cancel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stops stops the cleaning loop.
|
|
||||||
func (c *dbCleaner) Stop() {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
if c.cancel == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(c.cancel)
|
|
||||||
c.cancel = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *dbCleaner) schedule(period time.Duration, cancel <-chan struct{}) {
|
|
||||||
t := time.NewTicker(period)
|
|
||||||
defer t.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
count, err := c.PruneEntriesOlderThan(time.Now().Add(-c.retention))
|
|
||||||
if err != nil {
|
|
||||||
log.Error("failed to prune data", "err", err)
|
|
||||||
}
|
|
||||||
log.Info("Prunned some some messages successfully", "count", count)
|
|
||||||
case <-cancel:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PruneEntriesOlderThan removes messages sent between lower and upper timestamps
|
|
||||||
// and returns how many have been removed.
|
|
||||||
func (c *dbCleaner) PruneEntriesOlderThan(t time.Time) (int, error) {
|
|
||||||
return c.db.Prune(t, c.batchSize)
|
|
||||||
}
|
|
|
@ -1,160 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
waku "github.com/status-im/status-go/waku/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCleaner(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
server := setupTestServer(t)
|
|
||||||
defer server.Close()
|
|
||||||
cleaner := newDBCleaner(server.ms.db, time.Hour)
|
|
||||||
|
|
||||||
archiveEnvelope(t, now.Add(-10*time.Second), server)
|
|
||||||
archiveEnvelope(t, now.Add(-3*time.Second), server)
|
|
||||||
archiveEnvelope(t, now.Add(-1*time.Second), server)
|
|
||||||
|
|
||||||
testMessagesCount(t, 3, server)
|
|
||||||
|
|
||||||
testPrune(t, now.Add(-5*time.Second), 1, cleaner)
|
|
||||||
testPrune(t, now.Add(-2*time.Second), 1, cleaner)
|
|
||||||
testPrune(t, now, 1, cleaner)
|
|
||||||
|
|
||||||
testMessagesCount(t, 0, server)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanerSchedule(t *testing.T) {
|
|
||||||
now := time.Now()
|
|
||||||
server := setupTestServer(t)
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
cleaner := newDBCleaner(server.ms.db, time.Hour)
|
|
||||||
cleaner.period = time.Millisecond * 10
|
|
||||||
cleaner.Start()
|
|
||||||
defer cleaner.Stop()
|
|
||||||
|
|
||||||
archiveEnvelope(t, now.Add(-3*time.Hour), server)
|
|
||||||
archiveEnvelope(t, now.Add(-2*time.Hour), server)
|
|
||||||
archiveEnvelope(t, now.Add(-1*time.Minute), server)
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 50)
|
|
||||||
|
|
||||||
testMessagesCount(t, 1, server)
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkCleanerPrune(b *testing.B, messages int, batchSize int) {
|
|
||||||
t := &testing.T{}
|
|
||||||
now := time.Now()
|
|
||||||
sentTime := now.Add(-10 * time.Second)
|
|
||||||
server := setupTestServer(t)
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
cleaner := newDBCleaner(server.ms.db, time.Hour)
|
|
||||||
cleaner.batchSize = batchSize
|
|
||||||
|
|
||||||
for i := 0; i < messages; i++ {
|
|
||||||
archiveEnvelope(t, sentTime, server)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
testPrune(t, now, 0, cleaner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCleanerPruneM100_000_B100_000(b *testing.B) {
|
|
||||||
benchmarkCleanerPrune(b, 100000, 100000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCleanerPruneM100_000_B10_000(b *testing.B) {
|
|
||||||
benchmarkCleanerPrune(b, 100000, 10000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCleanerPruneM100_000_B1000(b *testing.B) {
|
|
||||||
benchmarkCleanerPrune(b, 100000, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCleanerPruneM100_000_B100(b *testing.B) {
|
|
||||||
benchmarkCleanerPrune(b, 100000, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTestServer(t *testing.T) *WakuMailServer {
|
|
||||||
var s WakuMailServer
|
|
||||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
|
||||||
|
|
||||||
s.ms = &mailServer{
|
|
||||||
db: &LevelDB{
|
|
||||||
ldb: db,
|
|
||||||
done: make(chan struct{}),
|
|
||||||
},
|
|
||||||
adapter: &wakuAdapter{},
|
|
||||||
}
|
|
||||||
s.minRequestPoW = powRequirement
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
func archiveEnvelope(t *testing.T, sentTime time.Time, server *WakuMailServer) *waku.Envelope {
|
|
||||||
env, err := generateEnvelope(sentTime)
|
|
||||||
require.NoError(t, err)
|
|
||||||
server.Archive(env)
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPrune(t *testing.T, u time.Time, expected int, c *dbCleaner) {
|
|
||||||
n, err := c.PruneEntriesOlderThan(u)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expected, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMessagesCount(t *testing.T, expected int, s *WakuMailServer) {
|
|
||||||
count := countMessages(t, s.ms.db)
|
|
||||||
require.Equal(t, expected, count, fmt.Sprintf("expected %d message, got: %d", expected, count))
|
|
||||||
}
|
|
||||||
|
|
||||||
func countMessages(t *testing.T, db DB) int {
|
|
||||||
var (
|
|
||||||
count int
|
|
||||||
zero types.Hash
|
|
||||||
emptyTopic types.TopicType
|
|
||||||
)
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
kl := NewDBKey(uint32(0), emptyTopic, zero)
|
|
||||||
ku := NewDBKey(uint32(now.Unix()), emptyTopic, zero)
|
|
||||||
|
|
||||||
query := CursorQuery{
|
|
||||||
start: kl.raw,
|
|
||||||
end: ku.raw,
|
|
||||||
}
|
|
||||||
|
|
||||||
i, _ := db.BuildIterator(query)
|
|
||||||
defer func() { _ = i.Release() }()
|
|
||||||
|
|
||||||
for i.Next() {
|
|
||||||
var env waku.Envelope
|
|
||||||
value, err := i.GetEnvelopeByBloomFilter(query.bloom)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rlp.DecodeBytes(value, &env)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
return count
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DBKeyLength is a size of the envelope key.
|
|
||||||
DBKeyLength = types.HashLength + timestampLength + types.TopicLength
|
|
||||||
CursorLength = types.HashLength + timestampLength
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidByteSize is returned when DBKey can't be created
|
|
||||||
// from a byte slice because it has invalid length.
|
|
||||||
ErrInvalidByteSize = errors.New("byte slice has invalid length")
|
|
||||||
)
|
|
||||||
|
|
||||||
// DBKey key to be stored in a db.
|
|
||||||
type DBKey struct {
|
|
||||||
raw []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns a bytes representation of the DBKey.
|
|
||||||
func (k *DBKey) Bytes() []byte {
|
|
||||||
return k.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *DBKey) Topic() types.TopicType {
|
|
||||||
return types.BytesToTopic(k.raw[timestampLength+types.HashLength:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *DBKey) EnvelopeHash() types.Hash {
|
|
||||||
return types.BytesToHash(k.raw[timestampLength : types.HashLength+timestampLength])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *DBKey) Cursor() []byte {
|
|
||||||
// We don't use the whole cursor for backward compatibility (also it's not needed)
|
|
||||||
return k.raw[:CursorLength]
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDBKey creates a new DBKey with the given values.
|
|
||||||
func NewDBKey(timestamp uint32, topic types.TopicType, h types.Hash) *DBKey {
|
|
||||||
var k DBKey
|
|
||||||
k.raw = make([]byte, DBKeyLength)
|
|
||||||
binary.BigEndian.PutUint32(k.raw, timestamp)
|
|
||||||
copy(k.raw[timestampLength:], h[:])
|
|
||||||
copy(k.raw[timestampLength+types.HashLength:], topic[:])
|
|
||||||
return &k
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewDBKey(t *testing.T) {
|
|
||||||
topic := types.BytesToTopic([]byte{0x01, 0x02, 0x03, 0x04})
|
|
||||||
|
|
||||||
hash := types.BytesToHash([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32})
|
|
||||||
dbKey := NewDBKey(0xabcdef12, topic, hash)
|
|
||||||
expected := []byte{0xab, 0xcd, 0xef, 0x12, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x01, 0x02, 0x03, 0x04}
|
|
||||||
require.Equal(t, expected, dbKey.Bytes())
|
|
||||||
require.Equal(t, topic, dbKey.Topic())
|
|
||||||
require.Equal(t, hash, dbKey.EnvelopeHash())
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type rateLimiter struct {
|
|
||||||
sync.RWMutex
|
|
||||||
|
|
||||||
lifespan time.Duration // duration of the limit
|
|
||||||
db map[string]time.Time
|
|
||||||
|
|
||||||
period time.Duration
|
|
||||||
cancel chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRateLimiter(duration time.Duration) *rateLimiter {
|
|
||||||
return &rateLimiter{
|
|
||||||
lifespan: duration,
|
|
||||||
db: make(map[string]time.Time),
|
|
||||||
period: time.Second,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *rateLimiter) Start() {
|
|
||||||
cancel := make(chan struct{})
|
|
||||||
|
|
||||||
l.Lock()
|
|
||||||
l.cancel = cancel
|
|
||||||
l.Unlock()
|
|
||||||
|
|
||||||
go l.cleanUp(l.period, cancel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *rateLimiter) Stop() {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
|
|
||||||
if l.cancel == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(l.cancel)
|
|
||||||
l.cancel = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *rateLimiter) Add(id string) {
|
|
||||||
l.Lock()
|
|
||||||
l.db[id] = time.Now()
|
|
||||||
l.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *rateLimiter) IsAllowed(id string) bool {
|
|
||||||
l.RLock()
|
|
||||||
defer l.RUnlock()
|
|
||||||
|
|
||||||
if lastRequestTime, ok := l.db[id]; ok {
|
|
||||||
return lastRequestTime.Add(l.lifespan).Before(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *rateLimiter) cleanUp(period time.Duration, cancel <-chan struct{}) {
|
|
||||||
t := time.NewTicker(period)
|
|
||||||
defer t.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
l.deleteExpired()
|
|
||||||
case <-cancel:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *rateLimiter) deleteExpired() {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
for id, lastRequestTime := range l.db {
|
|
||||||
if lastRequestTime.Add(l.lifespan).Before(now) {
|
|
||||||
delete(l.db, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIsAllowed(t *testing.T) {
|
|
||||||
peerID := "peerID"
|
|
||||||
testCases := []struct {
|
|
||||||
t time.Duration
|
|
||||||
shouldBeAllowed bool
|
|
||||||
db func() map[string]time.Time
|
|
||||||
info string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
t: 5 * time.Millisecond,
|
|
||||||
shouldBeAllowed: true,
|
|
||||||
db: func() map[string]time.Time {
|
|
||||||
return make(map[string]time.Time)
|
|
||||||
},
|
|
||||||
info: "Expecting limiter.isAllowed to allow with an empty db",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
t: 5 * time.Millisecond,
|
|
||||||
shouldBeAllowed: true,
|
|
||||||
db: func() map[string]time.Time {
|
|
||||||
db := make(map[string]time.Time)
|
|
||||||
db[peerID] = time.Now().Add(time.Duration(-10) * time.Millisecond)
|
|
||||||
return db
|
|
||||||
},
|
|
||||||
info: "Expecting limiter.isAllowed to allow with an expired peer on its db",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
t: 5 * time.Millisecond,
|
|
||||||
shouldBeAllowed: false,
|
|
||||||
db: func() map[string]time.Time {
|
|
||||||
db := make(map[string]time.Time)
|
|
||||||
db[peerID] = time.Now().Add(time.Duration(-1) * time.Millisecond)
|
|
||||||
return db
|
|
||||||
},
|
|
||||||
info: "Expecting limiter.isAllowed to not allow with a non expired peer on its db",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.info, func(*testing.T) {
|
|
||||||
l := newRateLimiter(tc.t)
|
|
||||||
l.db = tc.db()
|
|
||||||
assert.Equal(t, tc.shouldBeAllowed, l.IsAllowed(peerID))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveExpiredRateLimits(t *testing.T) {
|
|
||||||
peer := "peer"
|
|
||||||
l := newRateLimiter(time.Duration(5) * time.Second)
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
peerID := fmt.Sprintf("%s%d", peer, i)
|
|
||||||
l.db[peerID] = time.Now().Add(time.Duration(i*(-2)) * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.deleteExpired()
|
|
||||||
assert.Equal(t, 3, len(l.db))
|
|
||||||
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
peerID := fmt.Sprintf("%s%d", peer, i)
|
|
||||||
_, ok := l.db[peerID]
|
|
||||||
assert.True(t, ok, fmt.Sprintf("Non expired peer '%s' should exist, but it doesn't", peerID))
|
|
||||||
}
|
|
||||||
for i := 3; i < 10; i++ {
|
|
||||||
peerID := fmt.Sprintf("%s%d", peer, i)
|
|
||||||
_, ok := l.db[peerID]
|
|
||||||
assert.False(t, ok, fmt.Sprintf("Expired peer '%s' should not exist, but it does", peerID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleaningUpExpiredRateLimits(t *testing.T) {
|
|
||||||
l := newRateLimiter(5 * time.Second)
|
|
||||||
l.period = time.Millisecond * 10
|
|
||||||
l.Start()
|
|
||||||
defer l.Stop()
|
|
||||||
|
|
||||||
l.db["peer01"] = time.Now().Add(-1 * time.Second)
|
|
||||||
l.db["peer02"] = time.Now().Add(-2 * time.Second)
|
|
||||||
l.db["peer03"] = time.Now().Add(-10 * time.Second)
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 20)
|
|
||||||
|
|
||||||
_, ok := l.db["peer01"]
|
|
||||||
assert.True(t, ok)
|
|
||||||
_, ok = l.db["peer02"]
|
|
||||||
assert.True(t, ok)
|
|
||||||
_, ok = l.db["peer03"]
|
|
||||||
assert.False(t, ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddingLimts(t *testing.T) {
|
|
||||||
peerID := "peerAdding"
|
|
||||||
l := newRateLimiter(time.Duration(5) * time.Second)
|
|
||||||
pre := time.Now()
|
|
||||||
l.Add(peerID)
|
|
||||||
post := time.Now()
|
|
||||||
assert.True(t, l.db[peerID].After(pre))
|
|
||||||
assert.True(t, l.db[peerID].Before(post))
|
|
||||||
}
|
|
|
@ -1,948 +0,0 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
prom "github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/params"
|
|
||||||
"github.com/status-im/status-go/waku"
|
|
||||||
wakucommon "github.com/status-im/status-go/waku/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxQueryRange = 24 * time.Hour
|
|
||||||
maxQueryLimit = 1000
|
|
||||||
// When we default the upper limit, we want to extend the range a bit
|
|
||||||
// to accommodate for envelopes with slightly higher timestamp, in seconds
|
|
||||||
whisperTTLSafeThreshold = 60
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errDirectoryNotProvided = errors.New("data directory not provided")
|
|
||||||
errDecryptionMethodNotProvided = errors.New("decryption method is not provided")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
timestampLength = 4
|
|
||||||
requestLimitLength = 4
|
|
||||||
requestTimeRangeLength = timestampLength * 2
|
|
||||||
processRequestTimeout = time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
// DataDir points to a directory where mailserver's data is stored.
|
|
||||||
DataDir string
|
|
||||||
// Password is used to create a symmetric key to decrypt requests.
|
|
||||||
Password string
|
|
||||||
// AsymKey is an asymmetric key to decrypt requests.
|
|
||||||
AsymKey string
|
|
||||||
// MininumPoW is a minimum PoW for requests.
|
|
||||||
MinimumPoW float64
|
|
||||||
// RateLimit is a maximum number of requests per second from a peer.
|
|
||||||
RateLimit int
|
|
||||||
// DataRetention specifies a number of days an envelope should be stored for.
|
|
||||||
DataRetention int
|
|
||||||
PostgresEnabled bool
|
|
||||||
PostgresURI string
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------
|
|
||||||
// WakuMailServer
|
|
||||||
// --------------
|
|
||||||
|
|
||||||
type WakuMailServer struct {
|
|
||||||
ms *mailServer
|
|
||||||
shh *waku.Waku
|
|
||||||
minRequestPoW float64
|
|
||||||
|
|
||||||
symFilter *wakucommon.Filter
|
|
||||||
asymFilter *wakucommon.Filter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuMailServer) Init(waku *waku.Waku, cfg *params.WakuConfig) error {
|
|
||||||
s.shh = waku
|
|
||||||
s.minRequestPoW = cfg.MinimumPoW
|
|
||||||
|
|
||||||
config := Config{
|
|
||||||
DataDir: cfg.DataDir,
|
|
||||||
Password: cfg.MailServerPassword,
|
|
||||||
MinimumPoW: cfg.MinimumPoW,
|
|
||||||
DataRetention: cfg.MailServerDataRetention,
|
|
||||||
RateLimit: cfg.MailServerRateLimit,
|
|
||||||
PostgresEnabled: cfg.DatabaseConfig.PGConfig.Enabled,
|
|
||||||
PostgresURI: cfg.DatabaseConfig.PGConfig.URI,
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
s.ms, err = newMailServer(
|
|
||||||
config,
|
|
||||||
&wakuAdapter{},
|
|
||||||
&wakuService{Waku: waku},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.setupDecryptor(config.Password, config.AsymKey); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuMailServer) Close() {
|
|
||||||
s.ms.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuMailServer) Archive(env *wakucommon.Envelope) {
|
|
||||||
s.ms.Archive(gethbridge.NewWakuEnvelope(env))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuMailServer) Deliver(peerID []byte, req wakucommon.MessagesRequest) {
|
|
||||||
s.ms.DeliverMail(types.BytesToHash(peerID), types.BytesToHash(req.ID), MessagesRequestPayload{
|
|
||||||
Lower: req.From,
|
|
||||||
Upper: req.To,
|
|
||||||
Bloom: req.Bloom,
|
|
||||||
Topics: req.Topics,
|
|
||||||
Limit: req.Limit,
|
|
||||||
Cursor: req.Cursor,
|
|
||||||
Batch: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEPRECATED; user Deliver instead
|
|
||||||
func (s *WakuMailServer) DeliverMail(peerID []byte, req *wakucommon.Envelope) {
|
|
||||||
payload, err := s.decodeRequest(peerID, req)
|
|
||||||
if err != nil {
|
|
||||||
deliveryFailuresCounter.WithLabelValues("validation").Inc()
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:DeliverMail] request failed validaton",
|
|
||||||
"peerID", types.BytesToHash(peerID),
|
|
||||||
"requestID", req.Hash().String(),
|
|
||||||
"err", err,
|
|
||||||
)
|
|
||||||
s.ms.sendHistoricMessageErrorResponse(types.BytesToHash(peerID), types.Hash(req.Hash()), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.ms.DeliverMail(types.BytesToHash(peerID), types.Hash(req.Hash()), payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bloomFromReceivedMessage for a given whisper.ReceivedMessage it extracts the
|
|
||||||
// used bloom filter.
|
|
||||||
func (s *WakuMailServer) bloomFromReceivedMessage(msg *wakucommon.ReceivedMessage) ([]byte, error) {
|
|
||||||
payloadSize := len(msg.Payload)
|
|
||||||
|
|
||||||
if payloadSize < 8 {
|
|
||||||
return nil, errors.New("Undersized p2p request")
|
|
||||||
} else if payloadSize == 8 {
|
|
||||||
return wakucommon.MakeFullNodeBloom(), nil
|
|
||||||
} else if payloadSize < 8+wakucommon.BloomFilterSize {
|
|
||||||
return nil, errors.New("Undersized bloom filter in p2p request")
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg.Payload[8 : 8+wakucommon.BloomFilterSize], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuMailServer) decompositeRequest(peerID []byte, request *wakucommon.Envelope) (MessagesRequestPayload, error) {
|
|
||||||
var (
|
|
||||||
payload MessagesRequestPayload
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if s.minRequestPoW > 0.0 && request.PoW() < s.minRequestPoW {
|
|
||||||
return payload, fmt.Errorf("PoW() is too low")
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypted := s.openEnvelope(request)
|
|
||||||
if decrypted == nil {
|
|
||||||
return payload, fmt.Errorf("failed to decrypt p2p request")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkMsgSignature(decrypted.Src, peerID); err != nil {
|
|
||||||
return payload, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.Bloom, err = s.bloomFromReceivedMessage(decrypted)
|
|
||||||
if err != nil {
|
|
||||||
return payload, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.Lower = binary.BigEndian.Uint32(decrypted.Payload[:4])
|
|
||||||
payload.Upper = binary.BigEndian.Uint32(decrypted.Payload[4:8])
|
|
||||||
|
|
||||||
if payload.Upper < payload.Lower {
|
|
||||||
err := fmt.Errorf("query range is invalid: from > to (%d > %d)", payload.Lower, payload.Upper)
|
|
||||||
return payload, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lowerTime := time.Unix(int64(payload.Lower), 0)
|
|
||||||
upperTime := time.Unix(int64(payload.Upper), 0)
|
|
||||||
if upperTime.Sub(lowerTime) > maxQueryRange {
|
|
||||||
err := fmt.Errorf("query range too big for peer %s", string(peerID))
|
|
||||||
return payload, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decrypted.Payload) >= requestTimeRangeLength+wakucommon.BloomFilterSize+requestLimitLength {
|
|
||||||
payload.Limit = binary.BigEndian.Uint32(decrypted.Payload[requestTimeRangeLength+wakucommon.BloomFilterSize:])
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decrypted.Payload) == requestTimeRangeLength+wakucommon.BloomFilterSize+requestLimitLength+DBKeyLength {
|
|
||||||
payload.Cursor = decrypted.Payload[requestTimeRangeLength+wakucommon.BloomFilterSize+requestLimitLength:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuMailServer) setupDecryptor(password, asymKey string) error {
|
|
||||||
s.symFilter = nil
|
|
||||||
s.asymFilter = nil
|
|
||||||
|
|
||||||
if password != "" {
|
|
||||||
keyID, err := s.shh.AddSymKeyFromPassword(password)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create symmetric key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
symKey, err := s.shh.GetSymKey(keyID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("save symmetric key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.symFilter = &wakucommon.Filter{KeySym: symKey}
|
|
||||||
}
|
|
||||||
|
|
||||||
if asymKey != "" {
|
|
||||||
keyAsym, err := crypto.HexToECDSA(asymKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.asymFilter = &wakucommon.Filter{KeyAsym: keyAsym}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// openEnvelope tries to decrypt an envelope, first based on asymetric key (if
|
|
||||||
// provided) and second on the symetric key (if provided)
|
|
||||||
func (s *WakuMailServer) openEnvelope(request *wakucommon.Envelope) *wakucommon.ReceivedMessage {
|
|
||||||
if s.asymFilter != nil {
|
|
||||||
if d := request.Open(s.asymFilter); d != nil {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.symFilter != nil {
|
|
||||||
if d := request.Open(s.symFilter); d != nil {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuMailServer) decodeRequest(peerID []byte, request *wakucommon.Envelope) (MessagesRequestPayload, error) {
|
|
||||||
var payload MessagesRequestPayload
|
|
||||||
|
|
||||||
if s.minRequestPoW > 0.0 && request.PoW() < s.minRequestPoW {
|
|
||||||
return payload, errors.New("PoW too low")
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypted := s.openEnvelope(request)
|
|
||||||
if decrypted == nil {
|
|
||||||
log.Warn("Failed to decrypt p2p request")
|
|
||||||
return payload, errors.New("failed to decrypt p2p request")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkMsgSignature(decrypted.Src, peerID); err != nil {
|
|
||||||
log.Warn("Check message signature failed", "err", err.Error())
|
|
||||||
return payload, fmt.Errorf("check message signature failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rlp.DecodeBytes(decrypted.Payload, &payload); err != nil {
|
|
||||||
return payload, fmt.Errorf("failed to decode data: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if payload.Upper == 0 {
|
|
||||||
payload.Upper = uint32(time.Now().Unix() + whisperTTLSafeThreshold)
|
|
||||||
}
|
|
||||||
|
|
||||||
if payload.Upper < payload.Lower {
|
|
||||||
log.Error("Query range is invalid: lower > upper", "lower", payload.Lower, "upper", payload.Upper)
|
|
||||||
return payload, errors.New("query range is invalid: lower > upper")
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------
|
|
||||||
// adapter
|
|
||||||
// -------
|
|
||||||
|
|
||||||
type adapter interface {
|
|
||||||
CreateRequestFailedPayload(reqID types.Hash, err error) []byte
|
|
||||||
CreateRequestCompletedPayload(reqID, lastEnvelopeHash types.Hash, cursor []byte) []byte
|
|
||||||
CreateSyncResponse(envelopes []types.Envelope, cursor []byte, final bool, err string) interface{}
|
|
||||||
CreateRawSyncResponse(envelopes []rlp.RawValue, cursor []byte, final bool, err string) interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------
|
|
||||||
// wakuAdapter
|
|
||||||
// -----------
|
|
||||||
|
|
||||||
type wakuAdapter struct{}
|
|
||||||
|
|
||||||
var _ adapter = (*wakuAdapter)(nil)
|
|
||||||
|
|
||||||
func (wakuAdapter) CreateRequestFailedPayload(reqID types.Hash, err error) []byte {
|
|
||||||
return waku.CreateMailServerRequestFailedPayload(common.Hash(reqID), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wakuAdapter) CreateRequestCompletedPayload(reqID, lastEnvelopeHash types.Hash, cursor []byte) []byte {
|
|
||||||
return waku.CreateMailServerRequestCompletedPayload(common.Hash(reqID), common.Hash(lastEnvelopeHash), cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wakuAdapter) CreateSyncResponse(_ []types.Envelope, _ []byte, _ bool, _ string) interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wakuAdapter) CreateRawSyncResponse(_ []rlp.RawValue, _ []byte, _ bool, _ string) interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------
|
|
||||||
// service
|
|
||||||
// -------
|
|
||||||
|
|
||||||
type service interface {
|
|
||||||
SendHistoricMessageResponse(peerID []byte, payload []byte) error
|
|
||||||
SendRawP2PDirect(peerID []byte, envelopes ...rlp.RawValue) error
|
|
||||||
MaxMessageSize() uint32
|
|
||||||
SendRawSyncResponse(peerID []byte, data interface{}) error // optional
|
|
||||||
SendSyncResponse(peerID []byte, data interface{}) error // optional
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------
|
|
||||||
// wakuService
|
|
||||||
// -----------
|
|
||||||
|
|
||||||
type wakuService struct {
|
|
||||||
*waku.Waku
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *wakuService) SendRawSyncResponse(peerID []byte, data interface{}) error {
|
|
||||||
return errors.New("syncing mailservers is not support by Waku")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *wakuService) SendSyncResponse(peerID []byte, data interface{}) error {
|
|
||||||
return errors.New("syncing mailservers is not support by Waku")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------
|
|
||||||
// mailServer
|
|
||||||
// ----------
|
|
||||||
|
|
||||||
type mailServer struct {
|
|
||||||
adapter adapter
|
|
||||||
service service
|
|
||||||
db DB
|
|
||||||
cleaner *dbCleaner // removes old envelopes
|
|
||||||
muRateLimiter sync.RWMutex
|
|
||||||
rateLimiter *rateLimiter
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMailServer(cfg Config, adapter adapter, service service) (*mailServer, error) {
|
|
||||||
if len(cfg.DataDir) == 0 {
|
|
||||||
return nil, errDirectoryNotProvided
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move out
|
|
||||||
if len(cfg.Password) == 0 && len(cfg.AsymKey) == 0 {
|
|
||||||
return nil, errDecryptionMethodNotProvided
|
|
||||||
}
|
|
||||||
|
|
||||||
s := mailServer{
|
|
||||||
adapter: adapter,
|
|
||||||
service: service,
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.RateLimit > 0 {
|
|
||||||
s.setupRateLimiter(time.Duration(cfg.RateLimit) * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open database in the last step in order not to init with error
|
|
||||||
// and leave the database open by accident.
|
|
||||||
if cfg.PostgresEnabled {
|
|
||||||
log.Info("Connecting to postgres database")
|
|
||||||
database, err := NewPostgresDB(cfg.PostgresURI)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("open DB: %s", err)
|
|
||||||
}
|
|
||||||
s.db = database
|
|
||||||
log.Info("Connected to postgres database")
|
|
||||||
} else {
|
|
||||||
// Defaults to LevelDB
|
|
||||||
database, err := NewLevelDB(cfg.DataDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("open DB: %s", err)
|
|
||||||
}
|
|
||||||
s.db = database
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.DataRetention > 0 {
|
|
||||||
// MailServerDataRetention is a number of days.
|
|
||||||
s.setupCleaner(time.Duration(cfg.DataRetention) * time.Hour * 24)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupRateLimiter in case limit is bigger than 0 it will setup an automated
|
|
||||||
// limit db cleanup.
|
|
||||||
func (s *mailServer) setupRateLimiter(limit time.Duration) {
|
|
||||||
s.rateLimiter = newRateLimiter(limit)
|
|
||||||
s.rateLimiter.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) setupCleaner(retention time.Duration) {
|
|
||||||
s.cleaner = newDBCleaner(s.db, retention)
|
|
||||||
s.cleaner.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) Archive(env types.Envelope) {
|
|
||||||
err := s.db.SaveEnvelope(env)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Could not save envelope", "hash", env.Hash().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) DeliverMail(peerID, reqID types.Hash, req MessagesRequestPayload) {
|
|
||||||
timer := prom.NewTimer(mailDeliveryDuration)
|
|
||||||
defer timer.ObserveDuration()
|
|
||||||
|
|
||||||
deliveryAttemptsCounter.Inc()
|
|
||||||
log.Info(
|
|
||||||
"[mailserver:DeliverMail] delivering mail",
|
|
||||||
"peerID", peerID.String(),
|
|
||||||
"requestID", reqID.String(),
|
|
||||||
)
|
|
||||||
|
|
||||||
req.SetDefaults()
|
|
||||||
|
|
||||||
log.Info(
|
|
||||||
"[mailserver:DeliverMail] processing request",
|
|
||||||
"peerID", peerID.String(),
|
|
||||||
"requestID", reqID.String(),
|
|
||||||
"lower", req.Lower,
|
|
||||||
"upper", req.Upper,
|
|
||||||
"bloom", req.Bloom,
|
|
||||||
"topics", req.Topics,
|
|
||||||
"limit", req.Limit,
|
|
||||||
"cursor", req.Cursor,
|
|
||||||
"batch", req.Batch,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := req.Validate(); err != nil {
|
|
||||||
syncFailuresCounter.WithLabelValues("req_invalid").Inc()
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:DeliverMail] request invalid",
|
|
||||||
"peerID", peerID.String(),
|
|
||||||
"requestID", reqID.String(),
|
|
||||||
"err", err,
|
|
||||||
)
|
|
||||||
s.sendHistoricMessageErrorResponse(peerID, reqID, fmt.Errorf("request is invalid: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.exceedsPeerRequests(peerID) {
|
|
||||||
deliveryFailuresCounter.WithLabelValues("peer_req_limit").Inc()
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:DeliverMail] peer exceeded the limit",
|
|
||||||
"peerID", peerID.String(),
|
|
||||||
"requestID", reqID.String(),
|
|
||||||
)
|
|
||||||
s.sendHistoricMessageErrorResponse(peerID, reqID, fmt.Errorf("rate limit exceeded"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Batch {
|
|
||||||
requestsBatchedCounter.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
iter, err := s.createIterator(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:DeliverMail] request failed",
|
|
||||||
"peerID", peerID.String(),
|
|
||||||
"requestID", reqID.String(),
|
|
||||||
"err", err,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() { _ = iter.Release() }()
|
|
||||||
|
|
||||||
bundles := make(chan []rlp.RawValue, 5)
|
|
||||||
errCh := make(chan error)
|
|
||||||
cancelProcessing := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
counter := 0
|
|
||||||
for bundle := range bundles {
|
|
||||||
if err := s.sendRawEnvelopes(peerID, bundle, req.Batch); err != nil {
|
|
||||||
close(cancelProcessing)
|
|
||||||
errCh <- err
|
|
||||||
break
|
|
||||||
}
|
|
||||||
counter++
|
|
||||||
}
|
|
||||||
close(errCh)
|
|
||||||
log.Info(
|
|
||||||
"[mailserver:DeliverMail] finished sending bundles",
|
|
||||||
"peerID", peerID,
|
|
||||||
"requestID", reqID.String(),
|
|
||||||
"counter", counter,
|
|
||||||
)
|
|
||||||
}()
|
|
||||||
|
|
||||||
nextPageCursor, lastEnvelopeHash := s.processRequestInBundles(
|
|
||||||
iter,
|
|
||||||
req.Bloom,
|
|
||||||
req.Topics,
|
|
||||||
int(req.Limit),
|
|
||||||
processRequestTimeout,
|
|
||||||
reqID.String(),
|
|
||||||
bundles,
|
|
||||||
cancelProcessing,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wait for the goroutine to finish the work. It may return an error.
|
|
||||||
if err := <-errCh; err != nil {
|
|
||||||
deliveryFailuresCounter.WithLabelValues("process").Inc()
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:DeliverMail] error while processing",
|
|
||||||
"err", err,
|
|
||||||
"peerID", peerID,
|
|
||||||
"requestID", reqID,
|
|
||||||
)
|
|
||||||
s.sendHistoricMessageErrorResponse(peerID, reqID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processing of the request could be finished earlier due to iterator error.
|
|
||||||
if err := iter.Error(); err != nil {
|
|
||||||
deliveryFailuresCounter.WithLabelValues("iterator").Inc()
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:DeliverMail] iterator failed",
|
|
||||||
"err", err,
|
|
||||||
"peerID", peerID,
|
|
||||||
"requestID", reqID,
|
|
||||||
)
|
|
||||||
s.sendHistoricMessageErrorResponse(peerID, reqID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(
|
|
||||||
"[mailserver:DeliverMail] sending historic message response",
|
|
||||||
"peerID", peerID,
|
|
||||||
"requestID", reqID,
|
|
||||||
"last", lastEnvelopeHash,
|
|
||||||
"next", nextPageCursor,
|
|
||||||
)
|
|
||||||
|
|
||||||
s.sendHistoricMessageResponse(peerID, reqID, lastEnvelopeHash, nextPageCursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) SyncMail(peerID types.Hash, req MessagesRequestPayload) error {
|
|
||||||
log.Info("Started syncing envelopes", "peer", peerID.String(), "req", req)
|
|
||||||
|
|
||||||
requestID := fmt.Sprintf("%d-%d", time.Now().UnixNano(), rand.Intn(1000)) // nolint: gosec
|
|
||||||
|
|
||||||
syncAttemptsCounter.Inc()
|
|
||||||
|
|
||||||
// Check rate limiting for a requesting peer.
|
|
||||||
if s.exceedsPeerRequests(peerID) {
|
|
||||||
syncFailuresCounter.WithLabelValues("req_per_sec_limit").Inc()
|
|
||||||
log.Error("Peer exceeded request per seconds limit", "peerID", peerID.String())
|
|
||||||
return fmt.Errorf("requests per seconds limit exceeded")
|
|
||||||
}
|
|
||||||
|
|
||||||
req.SetDefaults()
|
|
||||||
|
|
||||||
if err := req.Validate(); err != nil {
|
|
||||||
syncFailuresCounter.WithLabelValues("req_invalid").Inc()
|
|
||||||
return fmt.Errorf("request is invalid: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
iter, err := s.createIterator(req)
|
|
||||||
if err != nil {
|
|
||||||
syncFailuresCounter.WithLabelValues("iterator").Inc()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() { _ = iter.Release() }()
|
|
||||||
|
|
||||||
bundles := make(chan []rlp.RawValue, 5)
|
|
||||||
errCh := make(chan error)
|
|
||||||
cancelProcessing := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for bundle := range bundles {
|
|
||||||
resp := s.adapter.CreateRawSyncResponse(bundle, nil, false, "")
|
|
||||||
if err := s.service.SendRawSyncResponse(peerID.Bytes(), resp); err != nil {
|
|
||||||
close(cancelProcessing)
|
|
||||||
errCh <- fmt.Errorf("failed to send sync response: %v", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(errCh)
|
|
||||||
}()
|
|
||||||
|
|
||||||
nextCursor, _ := s.processRequestInBundles(
|
|
||||||
iter,
|
|
||||||
req.Bloom,
|
|
||||||
req.Topics,
|
|
||||||
int(req.Limit),
|
|
||||||
processRequestTimeout,
|
|
||||||
requestID,
|
|
||||||
bundles,
|
|
||||||
cancelProcessing,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wait for the goroutine to finish the work. It may return an error.
|
|
||||||
if err := <-errCh; err != nil {
|
|
||||||
syncFailuresCounter.WithLabelValues("routine").Inc()
|
|
||||||
_ = s.service.SendSyncResponse(
|
|
||||||
peerID.Bytes(),
|
|
||||||
s.adapter.CreateSyncResponse(nil, nil, false, "failed to send a response"),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processing of the request could be finished earlier due to iterator error.
|
|
||||||
if err := iter.Error(); err != nil {
|
|
||||||
syncFailuresCounter.WithLabelValues("iterator").Inc()
|
|
||||||
_ = s.service.SendSyncResponse(
|
|
||||||
peerID.Bytes(),
|
|
||||||
s.adapter.CreateSyncResponse(nil, nil, false, "failed to process all envelopes"),
|
|
||||||
)
|
|
||||||
return fmt.Errorf("LevelDB iterator failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Finished syncing envelopes", "peer", peerID.String())
|
|
||||||
|
|
||||||
err = s.service.SendSyncResponse(
|
|
||||||
peerID.Bytes(),
|
|
||||||
s.adapter.CreateSyncResponse(nil, nextCursor, true, ""),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
syncFailuresCounter.WithLabelValues("response_send").Inc()
|
|
||||||
return fmt.Errorf("failed to send the final sync response: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the mailserver and its associated db connection.
|
|
||||||
func (s *mailServer) Close() {
|
|
||||||
if s.db != nil {
|
|
||||||
if err := s.db.Close(); err != nil {
|
|
||||||
log.Error("closing database failed", "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.rateLimiter != nil {
|
|
||||||
s.rateLimiter.Stop()
|
|
||||||
}
|
|
||||||
if s.cleaner != nil {
|
|
||||||
s.cleaner.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) exceedsPeerRequests(peerID types.Hash) bool {
|
|
||||||
s.muRateLimiter.RLock()
|
|
||||||
defer s.muRateLimiter.RUnlock()
|
|
||||||
|
|
||||||
if s.rateLimiter == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.rateLimiter.IsAllowed(peerID.String()) {
|
|
||||||
s.rateLimiter.Add(peerID.String())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("peerID exceeded the number of requests per second", "peerID", peerID.String())
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) createIterator(req MessagesRequestPayload) (Iterator, error) {
|
|
||||||
var (
|
|
||||||
emptyHash types.Hash
|
|
||||||
emptyTopic types.TopicType
|
|
||||||
ku, kl *DBKey
|
|
||||||
)
|
|
||||||
|
|
||||||
ku = NewDBKey(req.Upper+1, emptyTopic, emptyHash)
|
|
||||||
kl = NewDBKey(req.Lower, emptyTopic, emptyHash)
|
|
||||||
|
|
||||||
query := CursorQuery{
|
|
||||||
start: kl.Bytes(),
|
|
||||||
end: ku.Bytes(),
|
|
||||||
cursor: req.Cursor,
|
|
||||||
topics: req.Topics,
|
|
||||||
bloom: req.Bloom,
|
|
||||||
limit: req.Limit,
|
|
||||||
}
|
|
||||||
return s.db.BuildIterator(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) processRequestInBundles(
|
|
||||||
iter Iterator,
|
|
||||||
bloom []byte,
|
|
||||||
topics [][]byte,
|
|
||||||
limit int,
|
|
||||||
timeout time.Duration,
|
|
||||||
requestID string,
|
|
||||||
output chan<- []rlp.RawValue,
|
|
||||||
cancel <-chan struct{},
|
|
||||||
) ([]byte, types.Hash) {
|
|
||||||
timer := prom.NewTimer(requestsInBundlesDuration)
|
|
||||||
defer timer.ObserveDuration()
|
|
||||||
|
|
||||||
var (
|
|
||||||
bundle []rlp.RawValue
|
|
||||||
bundleSize uint32
|
|
||||||
batches [][]rlp.RawValue
|
|
||||||
processedEnvelopes int
|
|
||||||
processedEnvelopesSize int64
|
|
||||||
nextCursor []byte
|
|
||||||
lastEnvelopeHash types.Hash
|
|
||||||
)
|
|
||||||
|
|
||||||
log.Info(
|
|
||||||
"[mailserver:processRequestInBundles] processing request",
|
|
||||||
"requestID", requestID,
|
|
||||||
"limit", limit,
|
|
||||||
)
|
|
||||||
|
|
||||||
var topicsMap map[types.TopicType]bool
|
|
||||||
|
|
||||||
if len(topics) != 0 {
|
|
||||||
topicsMap = make(map[types.TopicType]bool)
|
|
||||||
for _, t := range topics {
|
|
||||||
topicsMap[types.BytesToTopic(t)] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We iterate over the envelopes.
|
|
||||||
// We collect envelopes in batches.
|
|
||||||
// If there still room and we haven't reached the limit
|
|
||||||
// append and continue.
|
|
||||||
// Otherwise publish what you have so far, reset the bundle to the
|
|
||||||
// current envelope, and leave if we hit the limit
|
|
||||||
for iter.Next() {
|
|
||||||
var rawValue []byte
|
|
||||||
var err error
|
|
||||||
if len(topicsMap) != 0 {
|
|
||||||
rawValue, err = iter.GetEnvelopeByTopicsMap(topicsMap)
|
|
||||||
|
|
||||||
} else if len(bloom) != 0 {
|
|
||||||
rawValue, err = iter.GetEnvelopeByBloomFilter(bloom)
|
|
||||||
} else {
|
|
||||||
err = errors.New("either topics or bloom must be specified")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:processRequestInBundles]Failed to get envelope from iterator",
|
|
||||||
"err", err,
|
|
||||||
"requestID", requestID,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if rawValue == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := iter.DBKey()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:processRequestInBundles] failed getting key",
|
|
||||||
"requestID", requestID,
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(adam): this is invalid code. If the limit is 1000,
|
|
||||||
// it will only send 999 items and send a cursor.
|
|
||||||
lastEnvelopeHash = key.EnvelopeHash()
|
|
||||||
processedEnvelopes++
|
|
||||||
envelopeSize := uint32(len(rawValue))
|
|
||||||
limitReached := processedEnvelopes >= limit
|
|
||||||
newSize := bundleSize + envelopeSize
|
|
||||||
|
|
||||||
// If we still have some room for messages, add and continue
|
|
||||||
if !limitReached && newSize < s.service.MaxMessageSize() {
|
|
||||||
bundle = append(bundle, rawValue)
|
|
||||||
bundleSize = newSize
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish if anything is in the bundle (there should always be
|
|
||||||
// something unless limit = 1)
|
|
||||||
if len(bundle) != 0 {
|
|
||||||
batches = append(batches, bundle)
|
|
||||||
processedEnvelopesSize += int64(bundleSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the bundle with the current envelope
|
|
||||||
bundle = []rlp.RawValue{rawValue}
|
|
||||||
bundleSize = envelopeSize
|
|
||||||
|
|
||||||
// Leave if we reached the limit
|
|
||||||
if limitReached {
|
|
||||||
nextCursor = key.Cursor()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bundle) > 0 {
|
|
||||||
batches = append(batches, bundle)
|
|
||||||
processedEnvelopesSize += int64(bundleSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(
|
|
||||||
"[mailserver:processRequestInBundles] publishing envelopes",
|
|
||||||
"requestID", requestID,
|
|
||||||
"batchesCount", len(batches),
|
|
||||||
"envelopeCount", processedEnvelopes,
|
|
||||||
"processedEnvelopesSize", processedEnvelopesSize,
|
|
||||||
"cursor", nextCursor,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Publish
|
|
||||||
batchLoop:
|
|
||||||
for _, batch := range batches {
|
|
||||||
select {
|
|
||||||
case output <- batch:
|
|
||||||
// It might happen that during producing the batches,
|
|
||||||
// the connection with the peer goes down and
|
|
||||||
// the consumer of `output` channel exits prematurely.
|
|
||||||
// In such a case, we should stop pushing batches and exit.
|
|
||||||
case <-cancel:
|
|
||||||
log.Info(
|
|
||||||
"[mailserver:processRequestInBundles] failed to push all batches",
|
|
||||||
"requestID", requestID,
|
|
||||||
)
|
|
||||||
break batchLoop
|
|
||||||
case <-time.After(timeout):
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:processRequestInBundles] timed out pushing a batch",
|
|
||||||
"requestID", requestID,
|
|
||||||
)
|
|
||||||
break batchLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
envelopesCounter.Inc()
|
|
||||||
sentEnvelopeBatchSizeMeter.Observe(float64(processedEnvelopesSize))
|
|
||||||
|
|
||||||
log.Info(
|
|
||||||
"[mailserver:processRequestInBundles] envelopes published",
|
|
||||||
"requestID", requestID,
|
|
||||||
)
|
|
||||||
close(output)
|
|
||||||
|
|
||||||
return nextCursor, lastEnvelopeHash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) sendRawEnvelopes(peerID types.Hash, envelopes []rlp.RawValue, batch bool) error {
|
|
||||||
timer := prom.NewTimer(sendRawEnvelopeDuration)
|
|
||||||
defer timer.ObserveDuration()
|
|
||||||
|
|
||||||
if batch {
|
|
||||||
return s.service.SendRawP2PDirect(peerID.Bytes(), envelopes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, env := range envelopes {
|
|
||||||
if err := s.service.SendRawP2PDirect(peerID.Bytes(), env); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) sendHistoricMessageResponse(peerID, reqID, lastEnvelopeHash types.Hash, cursor []byte) {
|
|
||||||
payload := s.adapter.CreateRequestCompletedPayload(reqID, lastEnvelopeHash, cursor)
|
|
||||||
err := s.service.SendHistoricMessageResponse(peerID.Bytes(), payload)
|
|
||||||
if err != nil {
|
|
||||||
deliveryFailuresCounter.WithLabelValues("historic_msg_resp").Inc()
|
|
||||||
log.Error(
|
|
||||||
"[mailserver:DeliverMail] error sending historic message response",
|
|
||||||
"err", err,
|
|
||||||
"peerID", peerID,
|
|
||||||
"requestID", reqID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *mailServer) sendHistoricMessageErrorResponse(peerID, reqID types.Hash, errorToReport error) {
|
|
||||||
payload := s.adapter.CreateRequestFailedPayload(reqID, errorToReport)
|
|
||||||
err := s.service.SendHistoricMessageResponse(peerID.Bytes(), payload)
|
|
||||||
// if we can't report an error, probably something is wrong with p2p connection,
|
|
||||||
// so we just print a log entry to document this sad fact
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error while reporting error response", "err", err, "peerID", peerID.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractBloomFromEncodedEnvelope(rawValue rlp.RawValue) ([]byte, error) {
|
|
||||||
var envelope wakucommon.Envelope
|
|
||||||
decodeErr := rlp.DecodeBytes(rawValue, &envelope)
|
|
||||||
if decodeErr != nil {
|
|
||||||
return nil, decodeErr
|
|
||||||
}
|
|
||||||
return envelope.Bloom(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkMsgSignature returns an error in case the message is not correctly signed.
|
|
||||||
func checkMsgSignature(reqSrc *ecdsa.PublicKey, id []byte) error {
|
|
||||||
src := crypto.FromECDSAPub(reqSrc)
|
|
||||||
if len(src)-len(id) == 1 {
|
|
||||||
src = src[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// if you want to check the signature, you can do it here. e.g.:
|
|
||||||
// if !bytes.Equal(peerID, src) {
|
|
||||||
if src == nil {
|
|
||||||
return errors.New("wrong signature of p2p request")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// every this many seconds check real envelopes count
|
|
||||||
const envelopeCountCheckInterval = 60
|
|
||||||
|
|
||||||
// DB is an interface to abstract interactions with the db so that the mailserver
|
|
||||||
// is agnostic to the underlying technology used
|
|
||||||
type DB interface {
|
|
||||||
Close() error
|
|
||||||
// SaveEnvelope stores an envelope
|
|
||||||
SaveEnvelope(types.Envelope) error
|
|
||||||
// GetEnvelope returns an rlp encoded envelope from the datastore
|
|
||||||
GetEnvelope(*DBKey) ([]byte, error)
|
|
||||||
// Prune removes envelopes older than time
|
|
||||||
Prune(time.Time, int) (int, error)
|
|
||||||
// BuildIterator returns an iterator over envelopes
|
|
||||||
BuildIterator(query CursorQuery) (Iterator, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Iterator interface {
|
|
||||||
Next() bool
|
|
||||||
DBKey() (*DBKey, error)
|
|
||||||
Release() error
|
|
||||||
Error() error
|
|
||||||
GetEnvelopeByBloomFilter(bloom []byte) ([]byte, error)
|
|
||||||
GetEnvelopeByTopicsMap(topics map[types.TopicType]bool) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CursorQuery struct {
|
|
||||||
start []byte
|
|
||||||
end []byte
|
|
||||||
cursor []byte
|
|
||||||
limit uint32
|
|
||||||
bloom []byte
|
|
||||||
topics [][]byte
|
|
||||||
}
|
|
|
@ -1,242 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/util"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
waku "github.com/status-im/status-go/waku/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LevelDB struct {
|
|
||||||
// We can't embed as there are some state problems with go-routines
|
|
||||||
ldb *leveldb.DB
|
|
||||||
name string
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type LevelDBIterator struct {
|
|
||||||
iterator.Iterator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *LevelDBIterator) DBKey() (*DBKey, error) {
|
|
||||||
return &DBKey{
|
|
||||||
raw: i.Key(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *LevelDBIterator) GetEnvelopeByTopicsMap(topics map[types.TopicType]bool) ([]byte, error) {
|
|
||||||
rawValue := make([]byte, len(i.Value()))
|
|
||||||
copy(rawValue, i.Value())
|
|
||||||
|
|
||||||
key, err := i.DBKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !topics[key.Topic()] {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return rawValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *LevelDBIterator) GetEnvelopeByBloomFilter(bloom []byte) ([]byte, error) {
|
|
||||||
var envelopeBloom []byte
|
|
||||||
rawValue := make([]byte, len(i.Value()))
|
|
||||||
copy(rawValue, i.Value())
|
|
||||||
|
|
||||||
key, err := i.DBKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(key.Bytes()) != DBKeyLength {
|
|
||||||
var err error
|
|
||||||
envelopeBloom, err = extractBloomFromEncodedEnvelope(rawValue)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
envelopeBloom = types.TopicToBloom(key.Topic())
|
|
||||||
}
|
|
||||||
if !types.BloomFilterMatch(bloom, envelopeBloom) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return rawValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *LevelDBIterator) Release() error {
|
|
||||||
i.Iterator.Release()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLevelDB(dataDir string) (*LevelDB, error) {
|
|
||||||
// Open opens an existing leveldb database
|
|
||||||
db, err := leveldb.OpenFile(dataDir, nil)
|
|
||||||
if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
|
|
||||||
log.Info("database is corrupted trying to recover", "path", dataDir)
|
|
||||||
db, err = leveldb.RecoverFile(dataDir, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
instance := LevelDB{
|
|
||||||
ldb: db,
|
|
||||||
name: dataDir, // name is used for metrics labels
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize the metric value
|
|
||||||
instance.updateArchivedEnvelopesCount()
|
|
||||||
// checking count on every insert is inefficient
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-instance.done:
|
|
||||||
return
|
|
||||||
case <-time.After(time.Second * envelopeCountCheckInterval):
|
|
||||||
instance.updateArchivedEnvelopesCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return &instance, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEnvelope get an envelope by its key
|
|
||||||
func (db *LevelDB) GetEnvelope(key *DBKey) ([]byte, error) {
|
|
||||||
defer recoverLevelDBPanics("GetEnvelope")
|
|
||||||
return db.ldb.Get(key.Bytes(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *LevelDB) updateArchivedEnvelopesCount() {
|
|
||||||
if count, err := db.envelopesCount(); err != nil {
|
|
||||||
log.Warn("db query for envelopes count failed", "err", err)
|
|
||||||
} else {
|
|
||||||
archivedEnvelopesGauge.WithLabelValues(db.name).Set(float64(count))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build iterator returns an iterator given a start/end and a cursor
|
|
||||||
func (db *LevelDB) BuildIterator(query CursorQuery) (Iterator, error) {
|
|
||||||
defer recoverLevelDBPanics("BuildIterator")
|
|
||||||
|
|
||||||
i := db.ldb.NewIterator(&util.Range{Start: query.start, Limit: query.end}, nil)
|
|
||||||
|
|
||||||
envelopeQueriesCounter.WithLabelValues("unknown", "unknown").Inc()
|
|
||||||
// seek to the end as we want to return envelopes in a descending order
|
|
||||||
if len(query.cursor) == CursorLength {
|
|
||||||
i.Seek(query.cursor)
|
|
||||||
}
|
|
||||||
return &LevelDBIterator{i}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune removes envelopes older than time
|
|
||||||
func (db *LevelDB) Prune(t time.Time, batchSize int) (int, error) {
|
|
||||||
defer recoverLevelDBPanics("Prune")
|
|
||||||
|
|
||||||
var zero types.Hash
|
|
||||||
var emptyTopic types.TopicType
|
|
||||||
kl := NewDBKey(0, emptyTopic, zero)
|
|
||||||
ku := NewDBKey(uint32(t.Unix()), emptyTopic, zero)
|
|
||||||
query := CursorQuery{
|
|
||||||
start: kl.Bytes(),
|
|
||||||
end: ku.Bytes(),
|
|
||||||
}
|
|
||||||
i, err := db.BuildIterator(query)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer func() { _ = i.Release() }()
|
|
||||||
|
|
||||||
batch := leveldb.Batch{}
|
|
||||||
removed := 0
|
|
||||||
|
|
||||||
for i.Next() {
|
|
||||||
dbKey, err := i.DBKey()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.Delete(dbKey.Bytes())
|
|
||||||
|
|
||||||
if batch.Len() == batchSize {
|
|
||||||
if err := db.ldb.Write(&batch, nil); err != nil {
|
|
||||||
return removed, err
|
|
||||||
}
|
|
||||||
|
|
||||||
removed = removed + batch.Len()
|
|
||||||
batch.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if batch.Len() > 0 {
|
|
||||||
if err := db.ldb.Write(&batch, nil); err != nil {
|
|
||||||
return removed, err
|
|
||||||
}
|
|
||||||
|
|
||||||
removed = removed + batch.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
return removed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *LevelDB) envelopesCount() (int, error) {
|
|
||||||
defer recoverLevelDBPanics("envelopesCount")
|
|
||||||
iterator, err := db.BuildIterator(CursorQuery{})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// LevelDB does not have API for getting a count
|
|
||||||
var count int
|
|
||||||
for iterator.Next() {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveEnvelope stores an envelope in leveldb and increments the metrics
|
|
||||||
func (db *LevelDB) SaveEnvelope(env types.Envelope) error {
|
|
||||||
defer recoverLevelDBPanics("SaveEnvelope")
|
|
||||||
|
|
||||||
key := NewDBKey(env.Expiry()-env.TTL(), env.Topic(), env.Hash())
|
|
||||||
rawEnvelope, err := rlp.EncodeToBytes(env.Unwrap())
|
|
||||||
if err != nil {
|
|
||||||
log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err))
|
|
||||||
archivedErrorsCounter.WithLabelValues(db.name).Inc()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.ldb.Put(key.Bytes(), rawEnvelope, nil); err != nil {
|
|
||||||
log.Error(fmt.Sprintf("Writing to DB failed: %s", err))
|
|
||||||
archivedErrorsCounter.WithLabelValues(db.name).Inc()
|
|
||||||
}
|
|
||||||
archivedEnvelopesGauge.WithLabelValues(db.name).Inc()
|
|
||||||
archivedEnvelopeSizeMeter.WithLabelValues(db.name).Observe(
|
|
||||||
float64(waku.EnvelopeHeaderLength + env.Size()))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *LevelDB) Close() error {
|
|
||||||
select {
|
|
||||||
case <-db.done:
|
|
||||||
default:
|
|
||||||
close(db.done)
|
|
||||||
}
|
|
||||||
return db.ldb.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func recoverLevelDBPanics(calleMethodName string) {
|
|
||||||
// Recover from possible goleveldb panics
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
if errString, ok := r.(string); ok {
|
|
||||||
log.Error(fmt.Sprintf("recovered from panic in %s: %s", calleMethodName, errString))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
waku "github.com/status-im/status-go/waku/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLevelDB_BuildIteratorWithTopic(t *testing.T) {
|
|
||||||
topic := []byte{0x01, 0x02, 0x03, 0x04}
|
|
||||||
|
|
||||||
db, err := NewLevelDB(t.TempDir())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
envelope, err := newTestEnvelope(topic)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = db.SaveEnvelope(envelope)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
iter, err := db.BuildIterator(CursorQuery{
|
|
||||||
start: NewDBKey(uint32(time.Now().Add(-time.Hour).Unix()), types.BytesToTopic(topic), types.Hash{}).Bytes(),
|
|
||||||
end: NewDBKey(uint32(time.Now().Add(time.Second).Unix()), types.BytesToTopic(topic), types.Hash{}).Bytes(),
|
|
||||||
topics: [][]byte{topic},
|
|
||||||
limit: 10,
|
|
||||||
})
|
|
||||||
topicsMap := make(map[types.TopicType]bool)
|
|
||||||
topicsMap[types.BytesToTopic(topic)] = true
|
|
||||||
require.NoError(t, err)
|
|
||||||
hasNext := iter.Next()
|
|
||||||
require.True(t, hasNext)
|
|
||||||
rawValue, err := iter.GetEnvelopeByTopicsMap(topicsMap)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEmpty(t, rawValue)
|
|
||||||
var receivedEnvelope waku.Envelope
|
|
||||||
err = rlp.DecodeBytes(rawValue, &receivedEnvelope)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, waku.BytesToTopic(topic), receivedEnvelope.Topic)
|
|
||||||
|
|
||||||
err = iter.Release()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, iter.Error())
|
|
||||||
}
|
|
|
@ -1,309 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/lib/pq"
|
|
||||||
|
|
||||||
// Import postgres driver
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
"github.com/status-im/migrate/v4"
|
|
||||||
"github.com/status-im/migrate/v4/database/postgres"
|
|
||||||
bindata "github.com/status-im/migrate/v4/source/go_bindata"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/mailserver/migrations"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
waku "github.com/status-im/status-go/waku/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PostgresDB struct {
|
|
||||||
db *sql.DB
|
|
||||||
name string
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPostgresDB(uri string) (*PostgresDB, error) {
|
|
||||||
db, err := sql.Open("postgres", uri)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
instance := &PostgresDB{
|
|
||||||
db: db,
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
if err := instance.setup(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// name is used for metrics labels
|
|
||||||
if name, err := instance.getDBName(uri); err == nil {
|
|
||||||
instance.name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize the metric value
|
|
||||||
instance.updateArchivedEnvelopesCount()
|
|
||||||
// checking count on every insert is inefficient
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-instance.done:
|
|
||||||
return
|
|
||||||
case <-time.After(time.Second * envelopeCountCheckInterval):
|
|
||||||
instance.updateArchivedEnvelopesCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return instance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type postgresIterator struct {
|
|
||||||
*sql.Rows
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) getDBName(uri string) (string, error) {
|
|
||||||
query := "SELECT current_database()"
|
|
||||||
var dbName string
|
|
||||||
return dbName, i.db.QueryRow(query).Scan(&dbName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) envelopesCount() (int, error) {
|
|
||||||
query := "SELECT count(*) FROM envelopes"
|
|
||||||
var count int
|
|
||||||
return count, i.db.QueryRow(query).Scan(&count)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) updateArchivedEnvelopesCount() {
|
|
||||||
if count, err := i.envelopesCount(); err != nil {
|
|
||||||
log.Warn("db query for envelopes count failed", "err", err)
|
|
||||||
} else {
|
|
||||||
archivedEnvelopesGauge.WithLabelValues(i.name).Set(float64(count))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *postgresIterator) DBKey() (*DBKey, error) {
|
|
||||||
var value []byte
|
|
||||||
var id []byte
|
|
||||||
if err := i.Scan(&id, &value); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &DBKey{raw: id}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *postgresIterator) Error() error {
|
|
||||||
return i.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *postgresIterator) Release() error {
|
|
||||||
return i.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *postgresIterator) GetEnvelopeByBloomFilter(bloom []byte) ([]byte, error) {
|
|
||||||
var value []byte
|
|
||||||
var id []byte
|
|
||||||
if err := i.Scan(&id, &value); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *postgresIterator) GetEnvelopeByTopicsMap(topics map[types.TopicType]bool) ([]byte, error) {
|
|
||||||
var value []byte
|
|
||||||
var id []byte
|
|
||||||
if err := i.Scan(&id, &value); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) BuildIterator(query CursorQuery) (Iterator, error) {
|
|
||||||
var args []interface{}
|
|
||||||
|
|
||||||
stmtString := "SELECT id, data FROM envelopes"
|
|
||||||
|
|
||||||
var historyRange string
|
|
||||||
if len(query.cursor) > 0 {
|
|
||||||
args = append(args, query.start, query.cursor)
|
|
||||||
// If we have a cursor, we don't want to include that envelope in the result set
|
|
||||||
stmtString += " " + "WHERE id >= $1 AND id < $2"
|
|
||||||
historyRange = "partial" //nolint: goconst
|
|
||||||
} else {
|
|
||||||
args = append(args, query.start, query.end)
|
|
||||||
stmtString += " " + "WHERE id >= $1 AND id <= $2"
|
|
||||||
historyRange = "full" //nolint: goconst
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterRange string
|
|
||||||
if len(query.topics) > 0 {
|
|
||||||
args = append(args, pq.Array(query.topics))
|
|
||||||
stmtString += " " + "AND topic = any($3)"
|
|
||||||
filterRange = "partial" //nolint: goconst
|
|
||||||
} else {
|
|
||||||
stmtString += " " + fmt.Sprintf("AND bloom & b'%s'::bit(512) = bloom", toBitString(query.bloom))
|
|
||||||
filterRange = "full" //nolint: goconst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Positional argument depends on the fact whether the query uses topics or bloom filter.
|
|
||||||
// If topic is used, the list of topics is passed as an argument to the query.
|
|
||||||
// If bloom filter is used, it is included into the query statement.
|
|
||||||
args = append(args, query.limit)
|
|
||||||
stmtString += " " + fmt.Sprintf("ORDER BY ID DESC LIMIT $%d", len(args))
|
|
||||||
|
|
||||||
stmt, err := i.db.Prepare(stmtString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
envelopeQueriesCounter.WithLabelValues(filterRange, historyRange).Inc()
|
|
||||||
rows, err := stmt.Query(args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &postgresIterator{rows}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) setup() error {
|
|
||||||
resources := bindata.Resource(
|
|
||||||
migrations.AssetNames(),
|
|
||||||
migrations.Asset,
|
|
||||||
)
|
|
||||||
|
|
||||||
source, err := bindata.WithInstance(resources)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := postgres.WithInstance(i.db, &postgres.Config{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := migrate.NewWithInstance(
|
|
||||||
"go-bindata",
|
|
||||||
source,
|
|
||||||
"postgres",
|
|
||||||
driver)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = m.Up(); err != migrate.ErrNoChange {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) Close() error {
|
|
||||||
select {
|
|
||||||
case <-i.done:
|
|
||||||
default:
|
|
||||||
close(i.done)
|
|
||||||
}
|
|
||||||
return i.db.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) GetEnvelope(key *DBKey) ([]byte, error) {
|
|
||||||
statement := `SELECT data FROM envelopes WHERE id = $1`
|
|
||||||
|
|
||||||
stmt, err := i.db.Prepare(statement)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
var envelope []byte
|
|
||||||
|
|
||||||
if err = stmt.QueryRow(key.Bytes()).Scan(&envelope); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return envelope, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) Prune(t time.Time, batch int) (int, error) {
|
|
||||||
var zero types.Hash
|
|
||||||
var emptyTopic types.TopicType
|
|
||||||
kl := NewDBKey(0, emptyTopic, zero)
|
|
||||||
ku := NewDBKey(uint32(t.Unix()), emptyTopic, zero)
|
|
||||||
statement := "DELETE FROM envelopes WHERE id BETWEEN $1 AND $2"
|
|
||||||
|
|
||||||
stmt, err := i.db.Prepare(statement)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
result, err := stmt.Exec(kl.Bytes(), ku.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
rows, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return int(rows), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *PostgresDB) SaveEnvelope(env types.Envelope) error {
|
|
||||||
topic := env.Topic()
|
|
||||||
key := NewDBKey(env.Expiry()-env.TTL(), topic, env.Hash())
|
|
||||||
rawEnvelope, err := rlp.EncodeToBytes(env.Unwrap())
|
|
||||||
if err != nil {
|
|
||||||
log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err))
|
|
||||||
archivedErrorsCounter.WithLabelValues(i.name).Inc()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if rawEnvelope == nil {
|
|
||||||
archivedErrorsCounter.WithLabelValues(i.name).Inc()
|
|
||||||
return errors.New("failed to encode envelope to bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
statement := "INSERT INTO envelopes (id, data, topic, bloom) VALUES ($1, $2, $3, B'"
|
|
||||||
statement += toBitString(env.Bloom())
|
|
||||||
statement += "'::bit(512)) ON CONFLICT (id) DO NOTHING;"
|
|
||||||
stmt, err := i.db.Prepare(statement)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
_, err = stmt.Exec(
|
|
||||||
key.Bytes(),
|
|
||||||
rawEnvelope,
|
|
||||||
topicToByte(topic),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
archivedErrorsCounter.WithLabelValues(i.name).Inc()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
archivedEnvelopesGauge.WithLabelValues(i.name).Inc()
|
|
||||||
archivedEnvelopeSizeMeter.WithLabelValues(i.name).Observe(
|
|
||||||
float64(waku.EnvelopeHeaderLength + env.Size()))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func topicToByte(t types.TopicType) []byte {
|
|
||||||
return []byte{t[0], t[1], t[2], t[3]}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toBitString(bloom []byte) string {
|
|
||||||
val := ""
|
|
||||||
for _, n := range bloom {
|
|
||||||
val += fmt.Sprintf("%08b", n)
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
// In order to run these tests, you must run a PostgreSQL database.
|
|
||||||
//
|
|
||||||
// Using Docker:
|
|
||||||
// docker run -e POSTGRES_HOST_AUTH_METHOD=trust -d -p 5432:5432 postgres:9.6-alpine
|
|
||||||
//
|
|
||||||
|
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
|
|
||||||
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/postgres"
|
|
||||||
waku "github.com/status-im/status-go/waku/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMailServerPostgresDBSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(MailServerPostgresDBSuite))
|
|
||||||
}
|
|
||||||
|
|
||||||
type MailServerPostgresDBSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailServerPostgresDBSuite) SetupSuite() {
|
|
||||||
// ResetDefaultTestPostgresDB Required to completely reset the Postgres DB
|
|
||||||
err := postgres.ResetDefaultTestPostgresDB()
|
|
||||||
s.NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailServerPostgresDBSuite) TestPostgresDB_BuildIteratorWithBloomFilter() {
|
|
||||||
topic := []byte{0xaa, 0xbb, 0xcc, 0xdd}
|
|
||||||
|
|
||||||
db, err := NewPostgresDB(postgres.DefaultTestURI)
|
|
||||||
s.NoError(err)
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
envelope, err := newTestEnvelope(topic)
|
|
||||||
s.NoError(err)
|
|
||||||
err = db.SaveEnvelope(envelope)
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
iter, err := db.BuildIterator(CursorQuery{
|
|
||||||
start: NewDBKey(uint32(time.Now().Add(-time.Hour).Unix()), types.BytesToTopic(topic), types.Hash{}).Bytes(),
|
|
||||||
end: NewDBKey(uint32(time.Now().Add(time.Second).Unix()), types.BytesToTopic(topic), types.Hash{}).Bytes(),
|
|
||||||
bloom: types.TopicToBloom(types.BytesToTopic(topic)),
|
|
||||||
limit: 10,
|
|
||||||
})
|
|
||||||
s.NoError(err)
|
|
||||||
hasNext := iter.Next()
|
|
||||||
s.True(hasNext)
|
|
||||||
rawValue, err := iter.GetEnvelopeByBloomFilter(nil)
|
|
||||||
s.NoError(err)
|
|
||||||
s.NotEmpty(rawValue)
|
|
||||||
var receivedEnvelope waku.Envelope
|
|
||||||
err = rlp.DecodeBytes(rawValue, &receivedEnvelope)
|
|
||||||
s.NoError(err)
|
|
||||||
s.EqualValues(waku.BytesToTopic(topic), receivedEnvelope.Topic)
|
|
||||||
|
|
||||||
err = iter.Release()
|
|
||||||
s.NoError(err)
|
|
||||||
s.NoError(iter.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailServerPostgresDBSuite) TestPostgresDB_BuildIteratorWithTopic() {
|
|
||||||
topic := []byte{0x01, 0x02, 0x03, 0x04}
|
|
||||||
|
|
||||||
db, err := NewPostgresDB(postgres.DefaultTestURI)
|
|
||||||
s.NoError(err)
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
envelope, err := newTestEnvelope(topic)
|
|
||||||
s.NoError(err)
|
|
||||||
err = db.SaveEnvelope(envelope)
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
iter, err := db.BuildIterator(CursorQuery{
|
|
||||||
start: NewDBKey(uint32(time.Now().Add(-time.Hour).Unix()), types.BytesToTopic(topic), types.Hash{}).Bytes(),
|
|
||||||
end: NewDBKey(uint32(time.Now().Add(time.Second).Unix()), types.BytesToTopic(topic), types.Hash{}).Bytes(),
|
|
||||||
topics: [][]byte{topic},
|
|
||||||
limit: 10,
|
|
||||||
})
|
|
||||||
s.NoError(err)
|
|
||||||
hasNext := iter.Next()
|
|
||||||
s.True(hasNext)
|
|
||||||
rawValue, err := iter.GetEnvelopeByBloomFilter(nil)
|
|
||||||
s.NoError(err)
|
|
||||||
s.NotEmpty(rawValue)
|
|
||||||
var receivedEnvelope waku.Envelope
|
|
||||||
err = rlp.DecodeBytes(rawValue, &receivedEnvelope)
|
|
||||||
s.NoError(err)
|
|
||||||
s.EqualValues(waku.BytesToTopic(topic), receivedEnvelope.Topic)
|
|
||||||
|
|
||||||
err = iter.Release()
|
|
||||||
s.NoError(err)
|
|
||||||
s.NoError(iter.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestEnvelope(topic []byte) (types.Envelope, error) {
|
|
||||||
privateKey, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params := waku.MessageParams{
|
|
||||||
TTL: 10,
|
|
||||||
PoW: 2.0,
|
|
||||||
Payload: []byte("hello world"),
|
|
||||||
WorkTime: 1,
|
|
||||||
Topic: waku.BytesToTopic(topic),
|
|
||||||
Dst: &privateKey.PublicKey,
|
|
||||||
}
|
|
||||||
message, err := waku.NewSentMessage(¶ms)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
envelope, err := message.Wrap(¶ms, now)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return gethbridge.NewWakuEnvelope(envelope), nil
|
|
||||||
}
|
|
|
@ -1,655 +0,0 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/params"
|
|
||||||
waku "github.com/status-im/status-go/waku"
|
|
||||||
wakucommon "github.com/status-im/status-go/waku/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
const powRequirement = 0.00001
|
|
||||||
|
|
||||||
var keyID string
|
|
||||||
var seed = time.Now().Unix()
|
|
||||||
var testPayload = []byte("test payload")
|
|
||||||
|
|
||||||
type ServerTestParams struct {
|
|
||||||
topic types.TopicType
|
|
||||||
birth uint32
|
|
||||||
low uint32
|
|
||||||
upp uint32
|
|
||||||
limit uint32
|
|
||||||
key *ecdsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMailserverSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(MailserverSuite))
|
|
||||||
}
|
|
||||||
|
|
||||||
type MailserverSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
server *WakuMailServer
|
|
||||||
shh *waku.Waku
|
|
||||||
config *params.WakuConfig
|
|
||||||
dataDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) SetupTest() {
|
|
||||||
s.server = &WakuMailServer{}
|
|
||||||
s.shh = waku.New(&waku.DefaultConfig, nil)
|
|
||||||
s.shh.RegisterMailServer(s.server)
|
|
||||||
|
|
||||||
tmpDir := s.T().TempDir()
|
|
||||||
s.dataDir = tmpDir
|
|
||||||
|
|
||||||
s.config = ¶ms.WakuConfig{
|
|
||||||
DataDir: tmpDir,
|
|
||||||
MailServerPassword: "testpassword",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestInit() {
|
|
||||||
testCases := []struct {
|
|
||||||
config params.WakuConfig
|
|
||||||
expectedError error
|
|
||||||
info string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
config: params.WakuConfig{DataDir: ""},
|
|
||||||
expectedError: errDirectoryNotProvided,
|
|
||||||
info: "config with empty DataDir",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: params.WakuConfig{
|
|
||||||
DataDir: s.config.DataDir,
|
|
||||||
MailServerPassword: "pwd",
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
info: "config with correct DataDir and Password",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: params.WakuConfig{
|
|
||||||
DataDir: s.config.DataDir,
|
|
||||||
MailServerPassword: "pwd",
|
|
||||||
MailServerRateLimit: 5,
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
info: "config with rate limit",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
// to satisfy gosec: C601 checks
|
|
||||||
tc := testCase
|
|
||||||
s.T().Run(tc.info, func(*testing.T) {
|
|
||||||
mailServer := &WakuMailServer{}
|
|
||||||
shh := waku.New(&waku.DefaultConfig, nil)
|
|
||||||
shh.RegisterMailServer(mailServer)
|
|
||||||
|
|
||||||
err := mailServer.Init(shh, &tc.config)
|
|
||||||
s.Require().Equal(tc.expectedError, err)
|
|
||||||
if err == nil {
|
|
||||||
defer mailServer.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// db should be open only if there was no error
|
|
||||||
if tc.expectedError == nil {
|
|
||||||
s.NotNil(mailServer.ms.db)
|
|
||||||
} else {
|
|
||||||
s.Nil(mailServer.ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.config.MailServerRateLimit > 0 {
|
|
||||||
s.NotNil(mailServer.ms.rateLimiter)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestArchive() {
|
|
||||||
config := *s.config
|
|
||||||
|
|
||||||
err := s.server.Init(s.shh, &config)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
defer s.server.Close()
|
|
||||||
|
|
||||||
env, err := generateEnvelope(time.Now())
|
|
||||||
s.NoError(err)
|
|
||||||
rawEnvelope, err := rlp.EncodeToBytes(env)
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
s.server.Archive(env)
|
|
||||||
key := NewDBKey(env.Expiry-env.TTL, types.TopicType(env.Topic), types.Hash(env.Hash()))
|
|
||||||
archivedEnvelope, err := s.server.ms.db.GetEnvelope(key)
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
s.Equal(rawEnvelope, archivedEnvelope)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestManageLimits() {
|
|
||||||
err := s.server.Init(s.shh, s.config)
|
|
||||||
s.NoError(err)
|
|
||||||
s.server.ms.rateLimiter = newRateLimiter(time.Duration(5) * time.Millisecond)
|
|
||||||
s.False(s.server.ms.exceedsPeerRequests(types.BytesToHash([]byte("peerID"))))
|
|
||||||
s.Equal(1, len(s.server.ms.rateLimiter.db))
|
|
||||||
firstSaved := s.server.ms.rateLimiter.db["peerID"]
|
|
||||||
|
|
||||||
// second call when limit is not accomplished does not store a new limit
|
|
||||||
s.True(s.server.ms.exceedsPeerRequests(types.BytesToHash([]byte("peerID"))))
|
|
||||||
s.Equal(1, len(s.server.ms.rateLimiter.db))
|
|
||||||
s.Equal(firstSaved, s.server.ms.rateLimiter.db["peerID"])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestDBKey() {
|
|
||||||
var h types.Hash
|
|
||||||
var emptyTopic types.TopicType
|
|
||||||
i := uint32(time.Now().Unix())
|
|
||||||
k := NewDBKey(i, emptyTopic, h)
|
|
||||||
s.Equal(len(k.Bytes()), DBKeyLength, "wrong DB key length")
|
|
||||||
s.Equal(byte(i%0x100), k.Bytes()[3], "raw representation should be big endian")
|
|
||||||
s.Equal(byte(i/0x1000000), k.Bytes()[0], "big endian expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestRequestPaginationLimit() {
|
|
||||||
s.setupServer(s.server)
|
|
||||||
defer s.server.Close()
|
|
||||||
|
|
||||||
var (
|
|
||||||
sentEnvelopes []*wakucommon.Envelope
|
|
||||||
sentHashes []common.Hash
|
|
||||||
receivedHashes []common.Hash
|
|
||||||
archiveKeys []string
|
|
||||||
)
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
count := uint32(10)
|
|
||||||
|
|
||||||
for i := count; i > 0; i-- {
|
|
||||||
sentTime := now.Add(time.Duration(-i) * time.Second)
|
|
||||||
env, err := generateEnvelope(sentTime)
|
|
||||||
s.NoError(err)
|
|
||||||
s.server.Archive(env)
|
|
||||||
key := NewDBKey(env.Expiry-env.TTL, types.TopicType(env.Topic), types.Hash(env.Hash()))
|
|
||||||
archiveKeys = append(archiveKeys, fmt.Sprintf("%x", key.Cursor()))
|
|
||||||
sentEnvelopes = append(sentEnvelopes, env)
|
|
||||||
sentHashes = append(sentHashes, env.Hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
reqLimit := uint32(6)
|
|
||||||
peerID, request, err := s.prepareRequest(sentEnvelopes, reqLimit)
|
|
||||||
s.NoError(err)
|
|
||||||
payload, err := s.server.decompositeRequest(peerID, request)
|
|
||||||
s.NoError(err)
|
|
||||||
s.Nil(payload.Cursor)
|
|
||||||
s.Equal(reqLimit, payload.Limit)
|
|
||||||
|
|
||||||
receivedHashes, cursor, _ := processRequestAndCollectHashes(s.server, payload)
|
|
||||||
|
|
||||||
// 10 envelopes sent
|
|
||||||
s.Equal(count, uint32(len(sentEnvelopes)))
|
|
||||||
// 6 envelopes received
|
|
||||||
s.Len(receivedHashes, int(payload.Limit))
|
|
||||||
// the 6 envelopes received should be in forward order
|
|
||||||
s.Equal(sentHashes[:payload.Limit], receivedHashes)
|
|
||||||
// cursor should be the key of the last envelope of the last page
|
|
||||||
s.Equal(archiveKeys[payload.Limit-1], fmt.Sprintf("%x", cursor))
|
|
||||||
|
|
||||||
// second page
|
|
||||||
payload.Cursor = cursor
|
|
||||||
receivedHashes, cursor, _ = processRequestAndCollectHashes(s.server, payload)
|
|
||||||
|
|
||||||
// 4 envelopes received
|
|
||||||
s.Equal(int(count-payload.Limit), len(receivedHashes))
|
|
||||||
// cursor is nil because there are no other pages
|
|
||||||
s.Nil(cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestMailServer() {
|
|
||||||
s.setupServer(s.server)
|
|
||||||
defer s.server.Close()
|
|
||||||
|
|
||||||
env, err := generateEnvelope(time.Now())
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
s.server.Archive(env)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
params *ServerTestParams
|
|
||||||
expect bool
|
|
||||||
isOK bool
|
|
||||||
info string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
params: s.defaultServerParams(env),
|
|
||||||
expect: true,
|
|
||||||
isOK: true,
|
|
||||||
info: "Processing a request where from and to are equal to an existing register, should provide results",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params: func() *ServerTestParams {
|
|
||||||
params := s.defaultServerParams(env)
|
|
||||||
params.low = params.birth + 1
|
|
||||||
params.upp = params.birth + 1
|
|
||||||
|
|
||||||
return params
|
|
||||||
}(),
|
|
||||||
expect: false,
|
|
||||||
isOK: true,
|
|
||||||
info: "Processing a request where from and to are greater than any existing register, should not provide results",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params: func() *ServerTestParams {
|
|
||||||
params := s.defaultServerParams(env)
|
|
||||||
params.upp = params.birth + 1
|
|
||||||
params.topic[0] = 0xFF
|
|
||||||
|
|
||||||
return params
|
|
||||||
}(),
|
|
||||||
expect: false,
|
|
||||||
isOK: true,
|
|
||||||
info: "Processing a request where to is greater than any existing register and with a specific topic, should not provide results",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params: func() *ServerTestParams {
|
|
||||||
params := s.defaultServerParams(env)
|
|
||||||
params.low = params.birth
|
|
||||||
params.upp = params.birth - 1
|
|
||||||
|
|
||||||
return params
|
|
||||||
}(),
|
|
||||||
isOK: false,
|
|
||||||
info: "Processing a request where to is lower than from should fail",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params: func() *ServerTestParams {
|
|
||||||
params := s.defaultServerParams(env)
|
|
||||||
params.low = 0
|
|
||||||
params.upp = params.birth + 24
|
|
||||||
|
|
||||||
return params
|
|
||||||
}(),
|
|
||||||
isOK: false,
|
|
||||||
info: "Processing a request where difference between from and to is > 24 should fail",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
// to satisfy gosec: C601 checks
|
|
||||||
tc := testCase
|
|
||||||
s.T().Run(tc.info, func(*testing.T) {
|
|
||||||
request := s.createRequest(tc.params)
|
|
||||||
src := crypto.FromECDSAPub(&tc.params.key.PublicKey)
|
|
||||||
payload, err := s.server.decompositeRequest(src, request)
|
|
||||||
s.Equal(tc.isOK, err == nil)
|
|
||||||
if err == nil {
|
|
||||||
s.Equal(tc.params.low, payload.Lower)
|
|
||||||
s.Equal(tc.params.upp, payload.Upper)
|
|
||||||
s.Equal(tc.params.limit, payload.Limit)
|
|
||||||
s.Equal(types.TopicToBloom(tc.params.topic), payload.Bloom)
|
|
||||||
s.Equal(tc.expect, s.messageExists(env, tc.params.low, tc.params.upp, payload.Bloom, tc.params.limit))
|
|
||||||
|
|
||||||
src[0]++
|
|
||||||
_, err = s.server.decompositeRequest(src, request)
|
|
||||||
s.True(err == nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestDecodeRequest() {
|
|
||||||
s.setupServer(s.server)
|
|
||||||
defer s.server.Close()
|
|
||||||
|
|
||||||
payload := MessagesRequestPayload{
|
|
||||||
Lower: 50,
|
|
||||||
Upper: 100,
|
|
||||||
Bloom: []byte{0x01},
|
|
||||||
Topics: [][]byte{},
|
|
||||||
Limit: 10,
|
|
||||||
Cursor: []byte{},
|
|
||||||
Batch: true,
|
|
||||||
}
|
|
||||||
data, err := rlp.EncodeToBytes(payload)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
id, err := s.shh.NewKeyPair()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
srcKey, err := s.shh.GetPrivateKey(id)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
env := s.createEnvelope(types.TopicType{0x01}, data, srcKey)
|
|
||||||
|
|
||||||
decodedPayload, err := s.server.decodeRequest(nil, env)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Equal(payload, decodedPayload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestDecodeRequestNoUpper() {
|
|
||||||
s.setupServer(s.server)
|
|
||||||
defer s.server.Close()
|
|
||||||
|
|
||||||
payload := MessagesRequestPayload{
|
|
||||||
Lower: 50,
|
|
||||||
Bloom: []byte{0x01},
|
|
||||||
Limit: 10,
|
|
||||||
Cursor: []byte{},
|
|
||||||
Batch: true,
|
|
||||||
}
|
|
||||||
data, err := rlp.EncodeToBytes(payload)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
id, err := s.shh.NewKeyPair()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
srcKey, err := s.shh.GetPrivateKey(id)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
env := s.createEnvelope(types.TopicType{0x01}, data, srcKey)
|
|
||||||
|
|
||||||
decodedPayload, err := s.server.decodeRequest(nil, env)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.NotEqual(0, decodedPayload.Upper)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestProcessRequestDeadlockHandling() {
|
|
||||||
s.setupServer(s.server)
|
|
||||||
defer s.server.Close()
|
|
||||||
|
|
||||||
var archievedEnvelopes []*wakucommon.Envelope
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
count := uint32(10)
|
|
||||||
|
|
||||||
// Archieve some envelopes.
|
|
||||||
for i := count; i > 0; i-- {
|
|
||||||
sentTime := now.Add(time.Duration(-i) * time.Second)
|
|
||||||
env, err := generateEnvelope(sentTime)
|
|
||||||
s.NoError(err)
|
|
||||||
s.server.Archive(env)
|
|
||||||
archievedEnvelopes = append(archievedEnvelopes, env)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare a request.
|
|
||||||
peerID, request, err := s.prepareRequest(archievedEnvelopes, 5)
|
|
||||||
s.NoError(err)
|
|
||||||
payload, err := s.server.decompositeRequest(peerID, request)
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
Name string
|
|
||||||
Timeout time.Duration
|
|
||||||
Verify func(
|
|
||||||
Iterator,
|
|
||||||
time.Duration, // processRequestInBundles timeout
|
|
||||||
chan []rlp.RawValue,
|
|
||||||
)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "finish processing using `done` channel",
|
|
||||||
Timeout: time.Second * 5,
|
|
||||||
Verify: func(
|
|
||||||
iter Iterator,
|
|
||||||
timeout time.Duration,
|
|
||||||
bundles chan []rlp.RawValue,
|
|
||||||
) {
|
|
||||||
done := make(chan struct{})
|
|
||||||
processFinished := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
s.server.ms.processRequestInBundles(iter, payload.Bloom, payload.Topics, int(payload.Limit), timeout, "req-01", bundles, done)
|
|
||||||
close(processFinished)
|
|
||||||
}()
|
|
||||||
go close(done)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-processFinished:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
s.FailNow("waiting for processing finish timed out")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "finish processing due to timeout",
|
|
||||||
Timeout: time.Second,
|
|
||||||
Verify: func(
|
|
||||||
iter Iterator,
|
|
||||||
timeout time.Duration,
|
|
||||||
bundles chan []rlp.RawValue,
|
|
||||||
) {
|
|
||||||
done := make(chan struct{}) // won't be closed because we test timeout of `processRequestInBundles()`
|
|
||||||
processFinished := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
s.server.ms.processRequestInBundles(iter, payload.Bloom, payload.Topics, int(payload.Limit), time.Second, "req-01", bundles, done)
|
|
||||||
close(processFinished)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-processFinished:
|
|
||||||
case <-time.After(time.Second * 5):
|
|
||||||
s.FailNow("waiting for processing finish timed out")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
s.T().Run(tc.Name, func(t *testing.T) {
|
|
||||||
iter, err := s.server.ms.createIterator(payload)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
defer func() { _ = iter.Release() }()
|
|
||||||
|
|
||||||
// Nothing reads from this unbuffered channel which simulates a situation
|
|
||||||
// when a connection between a peer and mail server was dropped.
|
|
||||||
bundles := make(chan []rlp.RawValue)
|
|
||||||
|
|
||||||
tc.Verify(iter, tc.Timeout, bundles)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) messageExists(envelope *wakucommon.Envelope, low, upp uint32, bloom []byte, limit uint32) bool {
|
|
||||||
receivedHashes, _, _ := processRequestAndCollectHashes(s.server, MessagesRequestPayload{
|
|
||||||
Lower: low,
|
|
||||||
Upper: upp,
|
|
||||||
Bloom: bloom,
|
|
||||||
Limit: limit,
|
|
||||||
})
|
|
||||||
for _, hash := range receivedHashes {
|
|
||||||
if hash == envelope.Hash() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) setupServer(server *WakuMailServer) {
|
|
||||||
const password = "password_for_this_test"
|
|
||||||
|
|
||||||
s.shh = waku.New(&waku.DefaultConfig, nil)
|
|
||||||
s.shh.RegisterMailServer(server)
|
|
||||||
|
|
||||||
err := server.Init(s.shh, ¶ms.WakuConfig{
|
|
||||||
DataDir: s.dataDir,
|
|
||||||
MailServerPassword: password,
|
|
||||||
MinimumPoW: powRequirement,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
s.T().Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyID, err = s.shh.AddSymKeyFromPassword(password)
|
|
||||||
if err != nil {
|
|
||||||
s.T().Fatalf("failed to create symmetric key for mail request: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) prepareRequest(envelopes []*wakucommon.Envelope, limit uint32) (
|
|
||||||
[]byte, *wakucommon.Envelope, error,
|
|
||||||
) {
|
|
||||||
if len(envelopes) == 0 {
|
|
||||||
return nil, nil, errors.New("envelopes is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
params := s.defaultServerParams(envelopes[0])
|
|
||||||
params.low = uint32(now.Add(time.Duration(-len(envelopes)) * time.Second).Unix())
|
|
||||||
params.upp = uint32(now.Unix())
|
|
||||||
params.limit = limit
|
|
||||||
|
|
||||||
request := s.createRequest(params)
|
|
||||||
peerID := crypto.FromECDSAPub(¶ms.key.PublicKey)
|
|
||||||
|
|
||||||
return peerID, request, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) defaultServerParams(env *wakucommon.Envelope) *ServerTestParams {
|
|
||||||
id, err := s.shh.NewKeyPair()
|
|
||||||
if err != nil {
|
|
||||||
s.T().Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
|
|
||||||
}
|
|
||||||
testPeerID, err := s.shh.GetPrivateKey(id)
|
|
||||||
if err != nil {
|
|
||||||
s.T().Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err)
|
|
||||||
}
|
|
||||||
birth := env.Expiry - env.TTL
|
|
||||||
|
|
||||||
return &ServerTestParams{
|
|
||||||
topic: types.TopicType(env.Topic),
|
|
||||||
birth: birth,
|
|
||||||
low: birth - 1,
|
|
||||||
upp: birth + 1,
|
|
||||||
limit: 0,
|
|
||||||
key: testPeerID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) createRequest(p *ServerTestParams) *wakucommon.Envelope {
|
|
||||||
bloom := types.TopicToBloom(p.topic)
|
|
||||||
data := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint32(data, p.low)
|
|
||||||
binary.BigEndian.PutUint32(data[4:], p.upp)
|
|
||||||
data = append(data, bloom...)
|
|
||||||
|
|
||||||
if p.limit != 0 {
|
|
||||||
limitData := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(limitData, p.limit)
|
|
||||||
data = append(data, limitData...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.createEnvelope(p.topic, data, p.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) createEnvelope(topic types.TopicType, data []byte, srcKey *ecdsa.PrivateKey) *wakucommon.Envelope {
|
|
||||||
key, err := s.shh.GetSymKey(keyID)
|
|
||||||
if err != nil {
|
|
||||||
s.T().Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
params := &wakucommon.MessageParams{
|
|
||||||
KeySym: key,
|
|
||||||
Topic: wakucommon.TopicType(topic),
|
|
||||||
Payload: data,
|
|
||||||
PoW: powRequirement * 2,
|
|
||||||
WorkTime: 2,
|
|
||||||
Src: srcKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := wakucommon.NewSentMessage(params)
|
|
||||||
if err != nil {
|
|
||||||
s.T().Fatalf("failed to create new message with seed %d: %s.", seed, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
env, err := msg.Wrap(params, time.Now())
|
|
||||||
if err != nil {
|
|
||||||
s.T().Fatalf("failed to wrap with seed %d: %s.", seed, err)
|
|
||||||
}
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateEnvelopeWithKeys(sentTime time.Time, keySym []byte, keyAsym *ecdsa.PublicKey) (*wakucommon.Envelope, error) {
|
|
||||||
params := &wakucommon.MessageParams{
|
|
||||||
Topic: wakucommon.TopicType{0x1F, 0x7E, 0xA1, 0x7F},
|
|
||||||
Payload: testPayload,
|
|
||||||
PoW: powRequirement,
|
|
||||||
WorkTime: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keySym) > 0 {
|
|
||||||
params.KeySym = keySym
|
|
||||||
} else if keyAsym != nil {
|
|
||||||
params.Dst = keyAsym
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := wakucommon.NewSentMessage(params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create new message with seed %d: %s", seed, err)
|
|
||||||
}
|
|
||||||
env, err := msg.Wrap(params, sentTime)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to wrap with seed %d: %s", seed, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return env, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateEnvelope(sentTime time.Time) (*wakucommon.Envelope, error) {
|
|
||||||
h := crypto.Keccak256Hash([]byte("test sample data"))
|
|
||||||
return generateEnvelopeWithKeys(sentTime, h[:], nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func processRequestAndCollectHashes(server *WakuMailServer, payload MessagesRequestPayload) ([]common.Hash, []byte, types.Hash) {
|
|
||||||
iter, _ := server.ms.createIterator(payload)
|
|
||||||
defer func() { _ = iter.Release() }()
|
|
||||||
bundles := make(chan []rlp.RawValue, 10)
|
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
var hashes []common.Hash
|
|
||||||
go func() {
|
|
||||||
for bundle := range bundles {
|
|
||||||
for _, rawEnvelope := range bundle {
|
|
||||||
var env *wakucommon.Envelope
|
|
||||||
if err := rlp.DecodeBytes(rawEnvelope, &env); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
hashes = append(hashes, env.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
cursor, lastHash := server.ms.processRequestInBundles(iter, payload.Bloom, payload.Topics, int(payload.Limit), time.Minute, "req-01", bundles, done)
|
|
||||||
|
|
||||||
<-done
|
|
||||||
|
|
||||||
return hashes, cursor, lastHash
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import prom "github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
// By default the /metrics endpoint is not available.
|
|
||||||
// It is exposed only if -metrics flag is set.
|
|
||||||
|
|
||||||
var (
|
|
||||||
envelopesCounter = prom.NewCounter(prom.CounterOpts{
|
|
||||||
Name: "mailserver_envelopes_total",
|
|
||||||
Help: "Number of envelopes processed.",
|
|
||||||
})
|
|
||||||
deliveryFailuresCounter = prom.NewCounterVec(prom.CounterOpts{
|
|
||||||
Name: "mailserver_delivery_failures_total",
|
|
||||||
Help: "Number of requests that failed processing.",
|
|
||||||
}, []string{"type"})
|
|
||||||
deliveryAttemptsCounter = prom.NewCounter(prom.CounterOpts{
|
|
||||||
Name: "mailserver_delivery_attempts_total",
|
|
||||||
Help: "Number of Whisper envelopes processed.",
|
|
||||||
})
|
|
||||||
requestsBatchedCounter = prom.NewCounter(prom.CounterOpts{
|
|
||||||
Name: "mailserver_requests_batched_total",
|
|
||||||
Help: "Number of processed batched requests.",
|
|
||||||
})
|
|
||||||
requestsInBundlesDuration = prom.NewHistogram(prom.HistogramOpts{
|
|
||||||
Name: "mailserver_requests_bundle_process_duration_seconds",
|
|
||||||
Help: "The time it took to process message bundles.",
|
|
||||||
})
|
|
||||||
syncFailuresCounter = prom.NewCounterVec(prom.CounterOpts{
|
|
||||||
Name: "mailserver_sync_failures_total",
|
|
||||||
Help: "Number of failures processing a sync requests.",
|
|
||||||
}, []string{"type"})
|
|
||||||
syncAttemptsCounter = prom.NewCounter(prom.CounterOpts{
|
|
||||||
Name: "mailserver_sync_attempts_total",
|
|
||||||
Help: "Number of attempts are processing a sync requests.",
|
|
||||||
})
|
|
||||||
sendRawEnvelopeDuration = prom.NewHistogram(prom.HistogramOpts{
|
|
||||||
Name: "mailserver_send_raw_envelope_duration_seconds",
|
|
||||||
Help: "The time it took to send a Whisper envelope.",
|
|
||||||
})
|
|
||||||
sentEnvelopeBatchSizeMeter = prom.NewHistogram(prom.HistogramOpts{
|
|
||||||
Name: "mailserver_sent_envelope_batch_size_bytes",
|
|
||||||
Help: "Size of processed Whisper envelopes in bytes.",
|
|
||||||
Buckets: prom.ExponentialBuckets(1024, 4, 10),
|
|
||||||
})
|
|
||||||
mailDeliveryDuration = prom.NewHistogram(prom.HistogramOpts{
|
|
||||||
Name: "mailserver_delivery_duration_seconds",
|
|
||||||
Help: "Time it takes to deliver messages to a Whisper peer.",
|
|
||||||
})
|
|
||||||
archivedErrorsCounter = prom.NewCounterVec(prom.CounterOpts{
|
|
||||||
Name: "mailserver_archived_envelopes_failures_total",
|
|
||||||
Help: "Number of failures storing a Whisper envelope.",
|
|
||||||
}, []string{"db"})
|
|
||||||
archivedEnvelopesGauge = prom.NewGaugeVec(prom.GaugeOpts{
|
|
||||||
Name: "mailserver_archived_envelopes_total",
|
|
||||||
Help: "Number of envelopes saved in the DB.",
|
|
||||||
}, []string{"db"})
|
|
||||||
archivedEnvelopeSizeMeter = prom.NewHistogramVec(prom.HistogramOpts{
|
|
||||||
Name: "mailserver_archived_envelope_size_bytes",
|
|
||||||
Help: "Size of envelopes saved.",
|
|
||||||
Buckets: prom.ExponentialBuckets(1024, 2, 11),
|
|
||||||
}, []string{"db"})
|
|
||||||
envelopeQueriesCounter = prom.NewCounterVec(prom.CounterOpts{
|
|
||||||
Name: "mailserver_envelope_queries_total",
|
|
||||||
Help: "Number of queries for envelopes in the DB.",
|
|
||||||
}, []string{"filter", "history"})
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
prom.MustRegister(envelopesCounter)
|
|
||||||
prom.MustRegister(deliveryFailuresCounter)
|
|
||||||
prom.MustRegister(deliveryAttemptsCounter)
|
|
||||||
prom.MustRegister(requestsBatchedCounter)
|
|
||||||
prom.MustRegister(requestsInBundlesDuration)
|
|
||||||
prom.MustRegister(syncFailuresCounter)
|
|
||||||
prom.MustRegister(syncAttemptsCounter)
|
|
||||||
prom.MustRegister(sendRawEnvelopeDuration)
|
|
||||||
prom.MustRegister(sentEnvelopeBatchSizeMeter)
|
|
||||||
prom.MustRegister(mailDeliveryDuration)
|
|
||||||
prom.MustRegister(archivedErrorsCounter)
|
|
||||||
prom.MustRegister(archivedEnvelopesGauge)
|
|
||||||
prom.MustRegister(archivedEnvelopeSizeMeter)
|
|
||||||
prom.MustRegister(envelopeQueriesCounter)
|
|
||||||
}
|
|
|
@ -1,319 +0,0 @@
|
||||||
// Code generated by go-bindata. DO NOT EDIT.
|
|
||||||
// sources:
|
|
||||||
// 1557732988_initialize_db.down.sql (72B)
|
|
||||||
// 1557732988_initialize_db.up.sql (278B)
|
|
||||||
// static.go (198B)
|
|
||||||
|
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func bindataRead(data []byte, name string) ([]byte, error) {
|
|
||||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, err = io.Copy(&buf, gz)
|
|
||||||
clErr := gz.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
|
||||||
}
|
|
||||||
if clErr != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type asset struct {
|
|
||||||
bytes []byte
|
|
||||||
info os.FileInfo
|
|
||||||
digest [sha256.Size]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type bindataFileInfo struct {
|
|
||||||
name string
|
|
||||||
size int64
|
|
||||||
mode os.FileMode
|
|
||||||
modTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi bindataFileInfo) Name() string {
|
|
||||||
return fi.name
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) Size() int64 {
|
|
||||||
return fi.size
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
|
||||||
return fi.mode
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) ModTime() time.Time {
|
|
||||||
return fi.modTime
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) IsDir() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var __1557732988_initialize_dbDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\xf0\xf4\x73\x71\x8d\x50\xc8\x4c\x89\x4f\xca\xc9\xcf\xcf\x8d\xcf\x4c\xa9\xb0\xe6\x42\x95\x28\xc9\x2f\xc8\x4c\x46\x92\x08\x71\x74\xf2\x71\x55\x48\xcd\x2b\x4b\xcd\xc9\x2f\x48\x2d\xb6\xe6\x02\x04\x00\x00\xff\xff\x6b\x93\xaa\x08\x48\x00\x00\x00")
|
|
||||||
|
|
||||||
func _1557732988_initialize_dbDownSqlBytes() ([]byte, error) {
|
|
||||||
return bindataRead(
|
|
||||||
__1557732988_initialize_dbDownSql,
|
|
||||||
"1557732988_initialize_db.down.sql",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _1557732988_initialize_dbDownSql() (*asset, error) {
|
|
||||||
bytes, err := _1557732988_initialize_dbDownSqlBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
info := bindataFileInfo{name: "1557732988_initialize_db.down.sql", size: 72, mode: os.FileMode(0664), modTime: time.Unix(1700000000, 0)}
|
|
||||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x77, 0x40, 0x78, 0xb7, 0x71, 0x3c, 0x20, 0x3b, 0xc9, 0xb, 0x2f, 0x49, 0xe4, 0xff, 0x1c, 0x84, 0x54, 0xa1, 0x30, 0xe3, 0x90, 0xf8, 0x73, 0xda, 0xb0, 0x2a, 0xea, 0x8e, 0xf1, 0x82, 0xe7, 0xd2}}
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var __1557732988_initialize_dbUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x0e\x72\x75\x0c\x71\x55\x08\x71\x74\xf2\x71\x55\x48\xcd\x2b\x4b\xcd\xc9\x2f\x48\x2d\x56\xd0\xc8\x4c\x51\x70\x8a\x0c\x71\x75\x54\xf0\xf3\x0f\x51\xf0\x0b\xf5\xf1\x51\x08\xf5\xf3\x0c\x0c\x75\xd5\x51\x48\x49\x2c\x49\x44\x93\xd3\x51\x28\xc9\x2f\xc8\x4c\xc6\x10\x4d\xca\xc9\xcf\xcf\x55\x70\xf2\x0c\xd1\x30\x35\x34\xd2\x84\x4b\x68\x5a\x73\x71\x41\xed\xf5\xf4\x73\x71\x8d\x50\xc8\x4c\x89\x07\x2b\x8d\xcf\x4c\xa9\x50\xf0\xf7\x43\x73\x87\x8b\x6b\xb0\x33\xd4\x2c\x4d\x6b\x0c\x8d\x60\x9b\xf1\x69\x04\x2b\x40\xd7\x88\x5d\x97\x06\x4c\x2d\x20\x00\x00\xff\xff\x0b\x7d\x91\x3e\x16\x01\x00\x00")
|
|
||||||
|
|
||||||
func _1557732988_initialize_dbUpSqlBytes() ([]byte, error) {
|
|
||||||
return bindataRead(
|
|
||||||
__1557732988_initialize_dbUpSql,
|
|
||||||
"1557732988_initialize_db.up.sql",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _1557732988_initialize_dbUpSql() (*asset, error) {
|
|
||||||
bytes, err := _1557732988_initialize_dbUpSqlBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
info := bindataFileInfo{name: "1557732988_initialize_db.up.sql", size: 278, mode: os.FileMode(0664), modTime: time.Unix(1700000000, 0)}
|
|
||||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf5, 0x85, 0x41, 0x7a, 0xba, 0x4f, 0xa3, 0x43, 0xc0, 0x63, 0xfa, 0x2c, 0xd1, 0xc5, 0xbb, 0x20, 0xa0, 0x64, 0xa8, 0x3b, 0x65, 0x82, 0xa2, 0x14, 0x28, 0x18, 0x7c, 0x8b, 0x3a, 0x7a, 0xfd, 0xe0}}
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _staticGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\xcc\x31\x4a\x06\x41\x0c\xc5\xf1\x7e\x4e\xf1\x4a\x05\x67\xa2\x95\x20\xd8\x8b\x28\x08\x7a\x81\xec\x6e\xc8\x17\xd6\x99\x2c\x99\xe8\xf9\x6d\x56\xc4\xd7\x3d\xfe\xf0\x23\xc2\x1b\xaf\x3b\xab\x60\x26\xa7\xad\x90\xbe\xc8\x36\x7f\xdf\xd5\xf3\xfb\x0d\x9e\x3e\x5e\x5f\xae\x11\x32\xfd\x2b\x56\x99\x08\xd3\x4b\xc2\x46\x3a\xf2\x22\x58\x6c\x70\x98\xcc\x72\xfc\x93\x4a\x21\x52\x7f\x50\x19\x12\x9c\x02\xf5\xba\xd8\xd8\x38\x19\xb5\xfb\x96\xd6\xe5\xf1\xee\xfe\xf6\x1c\xea\xb1\x2b\xba\x69\x70\x9a\x8f\x89\xea\x68\x8d\x5a\xa3\xce\xf6\x39\x25\xbe\x25\xe8\x2f\xd3\x49\x35\x75\xb4\xf2\x13\x00\x00\xff\xff\x9a\xab\xca\x11\xc6\x00\x00\x00")
|
|
||||||
|
|
||||||
func staticGoBytes() ([]byte, error) {
|
|
||||||
return bindataRead(
|
|
||||||
_staticGo,
|
|
||||||
"static.go",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func staticGo() (*asset, error) {
|
|
||||||
bytes, err := staticGoBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
info := bindataFileInfo{name: "static.go", size: 198, mode: os.FileMode(0664), modTime: time.Unix(1700000000, 0)}
|
|
||||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb, 0xd2, 0xfd, 0xbf, 0xe5, 0xff, 0xcb, 0x54, 0xec, 0x41, 0x23, 0x7b, 0xc0, 0xeb, 0x55, 0xb8, 0x69, 0xd7, 0x57, 0xf1, 0x83, 0x13, 0x88, 0x55, 0xd9, 0x73, 0xdc, 0x93, 0xee, 0x23, 0xe3, 0xe9}}
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asset loads and returns the asset for the given name.
|
|
||||||
// It returns an error if the asset could not be found or
|
|
||||||
// could not be loaded.
|
|
||||||
func Asset(name string) ([]byte, error) {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
if f, ok := _bindata[canonicalName]; ok {
|
|
||||||
a, err := f()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
|
||||||
}
|
|
||||||
return a.bytes, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Asset %s not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
|
||||||
func AssetString(name string) (string, error) {
|
|
||||||
data, err := Asset(name)
|
|
||||||
return string(data), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustAsset is like Asset but panics when Asset would return an error.
|
|
||||||
// It simplifies safe initialization of global variables.
|
|
||||||
func MustAsset(name string) []byte {
|
|
||||||
a, err := Asset(name)
|
|
||||||
if err != nil {
|
|
||||||
panic("asset: Asset(" + name + "): " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustAssetString is like AssetString but panics when Asset would return an
|
|
||||||
// error. It simplifies safe initialization of global variables.
|
|
||||||
func MustAssetString(name string) string {
|
|
||||||
return string(MustAsset(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetInfo loads and returns the asset info for the given name.
|
|
||||||
// It returns an error if the asset could not be found or
|
|
||||||
// could not be loaded.
|
|
||||||
func AssetInfo(name string) (os.FileInfo, error) {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
if f, ok := _bindata[canonicalName]; ok {
|
|
||||||
a, err := f()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
|
||||||
}
|
|
||||||
return a.info, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
|
||||||
// error if the asset could not be found or the digest could not be loaded.
|
|
||||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
if f, ok := _bindata[canonicalName]; ok {
|
|
||||||
a, err := f()
|
|
||||||
if err != nil {
|
|
||||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
|
|
||||||
}
|
|
||||||
return a.digest, nil
|
|
||||||
}
|
|
||||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Digests returns a map of all known files and their checksums.
|
|
||||||
func Digests() (map[string][sha256.Size]byte, error) {
|
|
||||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
|
||||||
for name := range _bindata {
|
|
||||||
a, err := _bindata[name]()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mp[name] = a.digest
|
|
||||||
}
|
|
||||||
return mp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetNames returns the names of the assets.
|
|
||||||
func AssetNames() []string {
|
|
||||||
names := make([]string, 0, len(_bindata))
|
|
||||||
for name := range _bindata {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
|
||||||
var _bindata = map[string]func() (*asset, error){
|
|
||||||
"1557732988_initialize_db.down.sql": _1557732988_initialize_dbDownSql,
|
|
||||||
|
|
||||||
"1557732988_initialize_db.up.sql": _1557732988_initialize_dbUpSql,
|
|
||||||
|
|
||||||
"static.go": staticGo,
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetDir returns the file names below a certain
|
|
||||||
// directory embedded in the file by go-bindata.
|
|
||||||
// For example if you run go-bindata on data/... and data contains the
|
|
||||||
// following hierarchy:
|
|
||||||
// data/
|
|
||||||
// foo.txt
|
|
||||||
// img/
|
|
||||||
// a.png
|
|
||||||
// b.png
|
|
||||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
|
||||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
|
||||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
|
||||||
// AssetDir("") will return []string{"data"}.
|
|
||||||
func AssetDir(name string) ([]string, error) {
|
|
||||||
node := _bintree
|
|
||||||
if len(name) != 0 {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
pathList := strings.Split(canonicalName, "/")
|
|
||||||
for _, p := range pathList {
|
|
||||||
node = node.Children[p]
|
|
||||||
if node == nil {
|
|
||||||
return nil, fmt.Errorf("Asset %s not found", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if node.Func != nil {
|
|
||||||
return nil, fmt.Errorf("Asset %s not found", name)
|
|
||||||
}
|
|
||||||
rv := make([]string, 0, len(node.Children))
|
|
||||||
for childName := range node.Children {
|
|
||||||
rv = append(rv, childName)
|
|
||||||
}
|
|
||||||
return rv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type bintree struct {
|
|
||||||
Func func() (*asset, error)
|
|
||||||
Children map[string]*bintree
|
|
||||||
}
|
|
||||||
|
|
||||||
var _bintree = &bintree{nil, map[string]*bintree{
|
|
||||||
"1557732988_initialize_db.down.sql": &bintree{_1557732988_initialize_dbDownSql, map[string]*bintree{}},
|
|
||||||
"1557732988_initialize_db.up.sql": &bintree{_1557732988_initialize_dbUpSql, map[string]*bintree{}},
|
|
||||||
"static.go": &bintree{staticGo, map[string]*bintree{}},
|
|
||||||
}}
|
|
||||||
|
|
||||||
// RestoreAsset restores an asset under the given directory.
|
|
||||||
func RestoreAsset(dir, name string) error {
|
|
||||||
data, err := Asset(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info, err := AssetInfo(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestoreAssets restores an asset under the given directory recursively.
|
|
||||||
func RestoreAssets(dir, name string) error {
|
|
||||||
children, err := AssetDir(name)
|
|
||||||
// File
|
|
||||||
if err != nil {
|
|
||||||
return RestoreAsset(dir, name)
|
|
||||||
}
|
|
||||||
// Dir
|
|
||||||
for _, child := range children {
|
|
||||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func _filePath(dir, name string) string {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package mailserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxMessagesRequestPayloadLimit = 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
// MessagesRequestPayload is a payload sent to the Mail Server.
|
|
||||||
type MessagesRequestPayload struct {
|
|
||||||
// Lower is a lower bound of time range for which messages are requested.
|
|
||||||
Lower uint32
|
|
||||||
// Upper is a lower bound of time range for which messages are requested.
|
|
||||||
Upper uint32
|
|
||||||
// Bloom is a bloom filter to filter envelopes.
|
|
||||||
Bloom []byte
|
|
||||||
// Topics is a list of topics to filter envelopes.
|
|
||||||
Topics [][]byte
|
|
||||||
// Limit is the max number of envelopes to return.
|
|
||||||
Limit uint32
|
|
||||||
// Cursor is used for pagination of the results.
|
|
||||||
Cursor []byte
|
|
||||||
// Batch set to true indicates that the client supports batched response.
|
|
||||||
Batch bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *MessagesRequestPayload) SetDefaults() {
|
|
||||||
if r.Limit == 0 {
|
|
||||||
r.Limit = maxQueryLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Upper == 0 {
|
|
||||||
r.Upper = uint32(time.Now().Unix() + whisperTTLSafeThreshold)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r MessagesRequestPayload) Validate() error {
|
|
||||||
if r.Upper < r.Lower {
|
|
||||||
return errors.New("query range is invalid: lower > upper")
|
|
||||||
}
|
|
||||||
if len(r.Bloom) == 0 && len(r.Topics) == 0 {
|
|
||||||
return errors.New("bloom filter and topics is empty")
|
|
||||||
}
|
|
||||||
if r.Limit > maxMessagesRequestPayloadLimit {
|
|
||||||
return errors.New("limit exceeds the maximum allowed value")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -432,7 +432,6 @@ func (n *StatusNode) startDiscovery() error {
|
||||||
options := peers.NewDefaultOptions()
|
options := peers.NewDefaultOptions()
|
||||||
// TODO(dshulyak) consider adding a flag to define this behaviour
|
// TODO(dshulyak) consider adding a flag to define this behaviour
|
||||||
options.AllowStop = len(n.config.RegisterTopics) == 0
|
options.AllowStop = len(n.config.RegisterTopics) == 0
|
||||||
options.TrustedMailServers = parseNodesToNodeID(n.config.ClusterConfig.TrustedMailServers)
|
|
||||||
|
|
||||||
n.peerPool = peers.NewPeerPool(
|
n.peerPool = peers.NewPeerPool(
|
||||||
n.discovery,
|
n.discovery,
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/logutils"
|
"github.com/status-im/status-go/logutils"
|
||||||
"github.com/status-im/status-go/mailserver"
|
|
||||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||||
"github.com/status-im/status-go/multiaccounts/settings"
|
"github.com/status-im/status-go/multiaccounts/settings"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
|
@ -297,13 +296,6 @@ func (b *StatusNode) wakuService(wakuCfg *params.WakuConfig, clusterCfg *params.
|
||||||
w.SetTimeSource(timesource.Now)
|
w.SetTimeSource(timesource.Now)
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable mail service
|
|
||||||
if wakuCfg.EnableMailServer {
|
|
||||||
if err := registerWakuMailServer(w, wakuCfg); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to register WakuMailServer: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if wakuCfg.LightClient {
|
if wakuCfg.LightClient {
|
||||||
emptyBloomFilter := make([]byte, 64)
|
emptyBloomFilter := make([]byte, 64)
|
||||||
if err := w.SetBloomFilter(emptyBloomFilter); err != nil {
|
if err := w.SetBloomFilter(emptyBloomFilter); err != nil {
|
||||||
|
@ -400,10 +392,7 @@ func setSettingsNotifier(db *accounts.Database, feed *event.Feed) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) *wakucommon.PeerRateLimiter {
|
func wakuRateLimiter(wakuCfg *params.WakuConfig, clusterCfg *params.ClusterConfig) *wakucommon.PeerRateLimiter {
|
||||||
enodes := append(
|
enodes := parseNodes(clusterCfg.StaticNodes)
|
||||||
parseNodes(clusterCfg.StaticNodes),
|
|
||||||
parseNodes(clusterCfg.TrustedMailServers)...,
|
|
||||||
)
|
|
||||||
var (
|
var (
|
||||||
ips []string
|
ips []string
|
||||||
peerIDs []enode.ID
|
peerIDs []enode.ID
|
||||||
|
@ -606,13 +595,6 @@ func (b *StatusNode) peerService() *peer.Service {
|
||||||
return b.peerSrvc
|
return b.peerSrvc
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerWakuMailServer(wakuService *waku.Waku, config *params.WakuConfig) (err error) {
|
|
||||||
var mailServer mailserver.WakuMailServer
|
|
||||||
wakuService.RegisterMailServer(&mailServer)
|
|
||||||
|
|
||||||
return mailServer.Init(wakuService, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendIf(condition bool, services []common.StatusService, service common.StatusService) []common.StatusService {
|
func appendIf(condition bool, services []common.StatusService, service common.StatusService) []common.StatusService {
|
||||||
if !condition {
|
if !condition {
|
||||||
return services
|
return services
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
|
|
||||||
const StaticNodes = "static"
|
const StaticNodes = "static"
|
||||||
const BootNodes = "boot"
|
const BootNodes = "boot"
|
||||||
const TrustedMailServers = "trusted_mailserver"
|
|
||||||
const PushNotificationsServers = "pushnotification"
|
const PushNotificationsServers = "pushnotification"
|
||||||
const RendezvousNodes = "rendezvous"
|
const RendezvousNodes = "rendezvous"
|
||||||
const DiscV5BootstrapNodes = "discV5boot"
|
const DiscV5BootstrapNodes = "discV5boot"
|
||||||
|
@ -39,12 +38,12 @@ func insertNodeConfig(tx *sql.Tx, c *params.NodeConfig) error {
|
||||||
max_peers, max_pending_peers, enable_status_service, enable_ntp_sync,
|
max_peers, max_pending_peers, enable_status_service, enable_ntp_sync,
|
||||||
bridge_enabled, wallet_enabled, local_notifications_enabled,
|
bridge_enabled, wallet_enabled, local_notifications_enabled,
|
||||||
browser_enabled, permissions_enabled, mailservers_enabled,
|
browser_enabled, permissions_enabled, mailservers_enabled,
|
||||||
swarm_enabled, mailserver_registry_address, web3provider_enabled, connector_enabled,
|
swarm_enabled, web3provider_enabled, connector_enabled,
|
||||||
synthetic_id
|
synthetic_id
|
||||||
) VALUES (
|
) VALUES (
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||||
?, ?, ?, ?, ?, ?, 'id'
|
?, ?, ?, ?, ?, 'id'
|
||||||
)`,
|
)`,
|
||||||
c.NetworkID, c.DataDir, c.KeyStoreDir, c.NodeKey, c.NoDiscovery, c.Rendezvous,
|
c.NetworkID, c.DataDir, c.KeyStoreDir, c.NodeKey, c.NoDiscovery, c.Rendezvous,
|
||||||
c.ListenAddr, c.AdvertiseAddr, c.Name, c.Version, c.APIModules,
|
c.ListenAddr, c.AdvertiseAddr, c.Name, c.Version, c.APIModules,
|
||||||
|
@ -52,7 +51,7 @@ func insertNodeConfig(tx *sql.Tx, c *params.NodeConfig) error {
|
||||||
c.EnableStatusService, true,
|
c.EnableStatusService, true,
|
||||||
c.BridgeConfig.Enabled, c.WalletConfig.Enabled, c.LocalNotificationsConfig.Enabled,
|
c.BridgeConfig.Enabled, c.WalletConfig.Enabled, c.LocalNotificationsConfig.Enabled,
|
||||||
c.BrowsersConfig.Enabled, c.PermissionsConfig.Enabled, c.MailserversConfig.Enabled,
|
c.BrowsersConfig.Enabled, c.PermissionsConfig.Enabled, c.MailserversConfig.Enabled,
|
||||||
c.SwarmConfig.Enabled, c.MailServerRegistryAddress, c.Web3ProviderConfig.Enabled,
|
c.SwarmConfig.Enabled, c.Web3ProviderConfig.Enabled,
|
||||||
c.ConnectorConfig.Enabled,
|
c.ConnectorConfig.Enabled,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
|
@ -264,12 +263,12 @@ func insertWakuV2ConfigPostMigration(tx *sql.Tx, c *params.NodeConfig) error {
|
||||||
func insertWakuV1Config(tx *sql.Tx, c *params.NodeConfig) error {
|
func insertWakuV1Config(tx *sql.Tx, c *params.NodeConfig) error {
|
||||||
_, err := tx.Exec(`
|
_, err := tx.Exec(`
|
||||||
INSERT OR REPLACE INTO waku_config (
|
INSERT OR REPLACE INTO waku_config (
|
||||||
enabled, light_client, full_node, enable_mailserver, data_dir, minimum_pow, mailserver_password, mailserver_rate_limit, mailserver_data_retention,
|
enabled, light_client, full_node, data_dir, minimum_pow,
|
||||||
ttl, max_message_size, enable_rate_limiter, packet_rate_limit_ip, packet_rate_limit_peer_id, bytes_rate_limit_ip, bytes_rate_limit_peer_id,
|
ttl, max_message_size, enable_rate_limiter, packet_rate_limit_ip, packet_rate_limit_peer_id, bytes_rate_limit_ip, bytes_rate_limit_peer_id,
|
||||||
rate_limit_tolerance, bloom_filter_mode, enable_confirmations, synthetic_id
|
rate_limit_tolerance, bloom_filter_mode, enable_confirmations, synthetic_id
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'id')`,
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'id')`,
|
||||||
c.WakuConfig.Enabled, c.WakuConfig.LightClient, c.WakuConfig.FullNode, c.WakuConfig.EnableMailServer, c.WakuConfig.DataDir, c.WakuConfig.MinimumPoW,
|
c.WakuConfig.Enabled, c.WakuConfig.LightClient, c.WakuConfig.FullNode, c.WakuConfig.DataDir, c.WakuConfig.MinimumPoW,
|
||||||
c.WakuConfig.MailServerPassword, c.WakuConfig.MailServerRateLimit, c.WakuConfig.MailServerDataRetention, c.WakuConfig.TTL, c.WakuConfig.MaxMessageSize,
|
c.WakuConfig.TTL, c.WakuConfig.MaxMessageSize,
|
||||||
c.WakuConfig.EnableRateLimiter, c.WakuConfig.PacketRateLimitIP, c.WakuConfig.PacketRateLimitPeerID, c.WakuConfig.BytesRateLimitIP, c.WakuConfig.BytesRateLimitPeerID,
|
c.WakuConfig.EnableRateLimiter, c.WakuConfig.PacketRateLimitIP, c.WakuConfig.PacketRateLimitPeerID, c.WakuConfig.BytesRateLimitIP, c.WakuConfig.BytesRateLimitPeerID,
|
||||||
c.WakuConfig.RateLimitTolerance, c.WakuConfig.BloomFilterMode, c.WakuConfig.EnableConfirmations,
|
c.WakuConfig.RateLimitTolerance, c.WakuConfig.BloomFilterMode, c.WakuConfig.EnableConfirmations,
|
||||||
)
|
)
|
||||||
|
@ -277,10 +276,6 @@ func insertWakuV1Config(tx *sql.Tx, c *params.NodeConfig) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := tx.Exec(`INSERT OR REPLACE INTO waku_config_db_pg (enabled, uri, synthetic_id) VALUES (?, ?, 'id')`, c.WakuConfig.DatabaseConfig.PGConfig.Enabled, c.WakuConfig.DatabaseConfig.PGConfig.URI); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := tx.Exec(`DELETE FROM waku_softblacklisted_peers WHERE synthetic_id = 'id'`); err != nil {
|
if _, err := tx.Exec(`DELETE FROM waku_softblacklisted_peers WHERE synthetic_id = 'id'`); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -311,7 +306,6 @@ func insertClusterConfigNodes(tx *sql.Tx, c *params.NodeConfig) error {
|
||||||
nodeMap := make(map[string][]string)
|
nodeMap := make(map[string][]string)
|
||||||
nodeMap[StaticNodes] = c.ClusterConfig.StaticNodes
|
nodeMap[StaticNodes] = c.ClusterConfig.StaticNodes
|
||||||
nodeMap[BootNodes] = c.ClusterConfig.BootNodes
|
nodeMap[BootNodes] = c.ClusterConfig.BootNodes
|
||||||
nodeMap[TrustedMailServers] = c.ClusterConfig.TrustedMailServers
|
|
||||||
nodeMap[PushNotificationsServers] = c.ClusterConfig.PushNotificationsServers
|
nodeMap[PushNotificationsServers] = c.ClusterConfig.PushNotificationsServers
|
||||||
nodeMap[RendezvousNodes] = c.ClusterConfig.RendezvousNodes
|
nodeMap[RendezvousNodes] = c.ClusterConfig.RendezvousNodes
|
||||||
nodeMap[DiscV5BootstrapNodes] = c.ClusterConfig.DiscV5BootstrapNodes
|
nodeMap[DiscV5BootstrapNodes] = c.ClusterConfig.DiscV5BootstrapNodes
|
||||||
|
@ -445,14 +439,14 @@ func loadNodeConfig(tx *sql.Tx) (*params.NodeConfig, error) {
|
||||||
listen_addr, advertise_addr, name, version, api_modules, tls_enabled, max_peers, max_pending_peers,
|
listen_addr, advertise_addr, name, version, api_modules, tls_enabled, max_peers, max_pending_peers,
|
||||||
enable_status_service, bridge_enabled, wallet_enabled, local_notifications_enabled,
|
enable_status_service, bridge_enabled, wallet_enabled, local_notifications_enabled,
|
||||||
browser_enabled, permissions_enabled, mailservers_enabled, swarm_enabled,
|
browser_enabled, permissions_enabled, mailservers_enabled, swarm_enabled,
|
||||||
mailserver_registry_address, web3provider_enabled, connector_enabled FROM node_config
|
web3provider_enabled, connector_enabled FROM node_config
|
||||||
WHERE synthetic_id = 'id'
|
WHERE synthetic_id = 'id'
|
||||||
`).Scan(
|
`).Scan(
|
||||||
&nodecfg.NetworkID, &nodecfg.DataDir, &nodecfg.KeyStoreDir, &nodecfg.NodeKey, &nodecfg.NoDiscovery, &nodecfg.Rendezvous,
|
&nodecfg.NetworkID, &nodecfg.DataDir, &nodecfg.KeyStoreDir, &nodecfg.NodeKey, &nodecfg.NoDiscovery, &nodecfg.Rendezvous,
|
||||||
&nodecfg.ListenAddr, &nodecfg.AdvertiseAddr, &nodecfg.Name, &nodecfg.Version, &nodecfg.APIModules, &nodecfg.TLSEnabled, &nodecfg.MaxPeers, &nodecfg.MaxPendingPeers,
|
&nodecfg.ListenAddr, &nodecfg.AdvertiseAddr, &nodecfg.Name, &nodecfg.Version, &nodecfg.APIModules, &nodecfg.TLSEnabled, &nodecfg.MaxPeers, &nodecfg.MaxPendingPeers,
|
||||||
&nodecfg.EnableStatusService, &nodecfg.BridgeConfig.Enabled, &nodecfg.WalletConfig.Enabled, &nodecfg.LocalNotificationsConfig.Enabled,
|
&nodecfg.EnableStatusService, &nodecfg.BridgeConfig.Enabled, &nodecfg.WalletConfig.Enabled, &nodecfg.LocalNotificationsConfig.Enabled,
|
||||||
&nodecfg.BrowsersConfig.Enabled, &nodecfg.PermissionsConfig.Enabled, &nodecfg.MailserversConfig.Enabled, &nodecfg.SwarmConfig.Enabled,
|
&nodecfg.BrowsersConfig.Enabled, &nodecfg.PermissionsConfig.Enabled, &nodecfg.MailserversConfig.Enabled, &nodecfg.SwarmConfig.Enabled,
|
||||||
&nodecfg.MailServerRegistryAddress, &nodecfg.Web3ProviderConfig.Enabled, &nodecfg.ConnectorConfig.Enabled,
|
&nodecfg.Web3ProviderConfig.Enabled, &nodecfg.ConnectorConfig.Enabled,
|
||||||
)
|
)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -535,7 +529,6 @@ func loadNodeConfig(tx *sql.Tx) (*params.NodeConfig, error) {
|
||||||
nodeMap := make(map[string]*[]string)
|
nodeMap := make(map[string]*[]string)
|
||||||
nodeMap[StaticNodes] = &nodecfg.ClusterConfig.StaticNodes
|
nodeMap[StaticNodes] = &nodecfg.ClusterConfig.StaticNodes
|
||||||
nodeMap[BootNodes] = &nodecfg.ClusterConfig.BootNodes
|
nodeMap[BootNodes] = &nodecfg.ClusterConfig.BootNodes
|
||||||
nodeMap[TrustedMailServers] = &nodecfg.ClusterConfig.TrustedMailServers
|
|
||||||
nodeMap[PushNotificationsServers] = &nodecfg.ClusterConfig.PushNotificationsServers
|
nodeMap[PushNotificationsServers] = &nodecfg.ClusterConfig.PushNotificationsServers
|
||||||
nodeMap[RendezvousNodes] = &nodecfg.ClusterConfig.RendezvousNodes
|
nodeMap[RendezvousNodes] = &nodecfg.ClusterConfig.RendezvousNodes
|
||||||
nodeMap[WakuNodes] = &nodecfg.ClusterConfig.WakuNodes
|
nodeMap[WakuNodes] = &nodecfg.ClusterConfig.WakuNodes
|
||||||
|
@ -709,13 +702,13 @@ func loadNodeConfig(tx *sql.Tx) (*params.NodeConfig, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.QueryRow(`
|
err = tx.QueryRow(`
|
||||||
SELECT enabled, light_client, full_node, enable_mailserver, data_dir, minimum_pow, mailserver_password, mailserver_rate_limit, mailserver_data_retention,
|
SELECT enabled, light_client, full_node, data_dir, minimum_pow,
|
||||||
ttl, max_message_size, enable_rate_limiter, packet_rate_limit_ip, packet_rate_limit_peer_id, bytes_rate_limit_ip, bytes_rate_limit_peer_id,
|
ttl, max_message_size, enable_rate_limiter, packet_rate_limit_ip, packet_rate_limit_peer_id, bytes_rate_limit_ip, bytes_rate_limit_peer_id,
|
||||||
rate_limit_tolerance, bloom_filter_mode, enable_confirmations
|
rate_limit_tolerance, bloom_filter_mode, enable_confirmations
|
||||||
FROM waku_config WHERE synthetic_id = 'id'
|
FROM waku_config WHERE synthetic_id = 'id'
|
||||||
`).Scan(
|
`).Scan(
|
||||||
&nodecfg.WakuConfig.Enabled, &nodecfg.WakuConfig.LightClient, &nodecfg.WakuConfig.FullNode, &nodecfg.WakuConfig.EnableMailServer, &nodecfg.WakuConfig.DataDir, &nodecfg.WakuConfig.MinimumPoW,
|
&nodecfg.WakuConfig.Enabled, &nodecfg.WakuConfig.LightClient, &nodecfg.WakuConfig.FullNode, &nodecfg.WakuConfig.DataDir, &nodecfg.WakuConfig.MinimumPoW,
|
||||||
&nodecfg.WakuConfig.MailServerPassword, &nodecfg.WakuConfig.MailServerRateLimit, &nodecfg.WakuConfig.MailServerDataRetention, &nodecfg.WakuConfig.TTL, &nodecfg.WakuConfig.MaxMessageSize,
|
&nodecfg.WakuConfig.TTL, &nodecfg.WakuConfig.MaxMessageSize,
|
||||||
&nodecfg.WakuConfig.EnableRateLimiter, &nodecfg.WakuConfig.PacketRateLimitIP, &nodecfg.WakuConfig.PacketRateLimitPeerID, &nodecfg.WakuConfig.BytesRateLimitIP, &nodecfg.WakuConfig.BytesRateLimitPeerID,
|
&nodecfg.WakuConfig.EnableRateLimiter, &nodecfg.WakuConfig.PacketRateLimitIP, &nodecfg.WakuConfig.PacketRateLimitPeerID, &nodecfg.WakuConfig.BytesRateLimitIP, &nodecfg.WakuConfig.BytesRateLimitPeerID,
|
||||||
&nodecfg.WakuConfig.RateLimitTolerance, &nodecfg.WakuConfig.BloomFilterMode, &nodecfg.WakuConfig.EnableConfirmations,
|
&nodecfg.WakuConfig.RateLimitTolerance, &nodecfg.WakuConfig.BloomFilterMode, &nodecfg.WakuConfig.EnableConfirmations,
|
||||||
)
|
)
|
||||||
|
@ -723,11 +716,6 @@ func loadNodeConfig(tx *sql.Tx) (*params.NodeConfig, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.QueryRow("SELECT enabled, uri FROM waku_config_db_pg WHERE synthetic_id = 'id'").Scan(&nodecfg.WakuConfig.DatabaseConfig.PGConfig.Enabled, &nodecfg.WakuConfig.DatabaseConfig.PGConfig.URI)
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err = tx.Query(`SELECT peer_id FROM waku_softblacklisted_peers WHERE synthetic_id = 'id' ORDER BY peer_id ASC`)
|
rows, err = tx.Query(`SELECT peer_id FROM waku_softblacklisted_peers WHERE synthetic_id = 'id' ORDER BY peer_id ASC`)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -14,7 +14,6 @@ const (
|
||||||
type Cluster struct {
|
type Cluster struct {
|
||||||
StaticNodes []string `json:"staticnodes"`
|
StaticNodes []string `json:"staticnodes"`
|
||||||
BootNodes []string `json:"bootnodes"`
|
BootNodes []string `json:"bootnodes"`
|
||||||
MailServers []string `json:"mailservers"` // list of trusted mail servers
|
|
||||||
RendezvousNodes []string `json:"rendezvousnodes"`
|
RendezvousNodes []string `json:"rendezvousnodes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,9 +80,6 @@ type WakuConfig struct {
|
||||||
// FullNode should be true if waku should always acta as a full node
|
// FullNode should be true if waku should always acta as a full node
|
||||||
FullNode bool
|
FullNode bool
|
||||||
|
|
||||||
// EnableMailServer is mode when node is capable of delivering expired messages on demand
|
|
||||||
EnableMailServer bool
|
|
||||||
|
|
||||||
// DataDir is the file system folder Waku should use for any data storage needs.
|
// DataDir is the file system folder Waku should use for any data storage needs.
|
||||||
// For instance, MailServer will use this directory to store its data.
|
// For instance, MailServer will use this directory to store its data.
|
||||||
DataDir string
|
DataDir string
|
||||||
|
@ -91,16 +88,6 @@ type WakuConfig struct {
|
||||||
// We enforce a minimum as a bland spam prevention mechanism.
|
// We enforce a minimum as a bland spam prevention mechanism.
|
||||||
MinimumPoW float64
|
MinimumPoW float64
|
||||||
|
|
||||||
// MailServerPassword for symmetric encryption of waku message history requests.
|
|
||||||
// (if no account file selected, then this password is used for symmetric encryption).
|
|
||||||
MailServerPassword string
|
|
||||||
|
|
||||||
// MailServerRateLimit minimum time between queries to mail server per peer.
|
|
||||||
MailServerRateLimit int
|
|
||||||
|
|
||||||
// MailServerDataRetention is a number of days data should be stored by MailServer.
|
|
||||||
MailServerDataRetention int
|
|
||||||
|
|
||||||
// TTL time to live for messages, in seconds
|
// TTL time to live for messages, in seconds
|
||||||
TTL int
|
TTL int
|
||||||
|
|
||||||
|
@ -108,9 +95,6 @@ type WakuConfig struct {
|
||||||
// not only the size of envelopes sent in that packet.
|
// not only the size of envelopes sent in that packet.
|
||||||
MaxMessageSize uint32
|
MaxMessageSize uint32
|
||||||
|
|
||||||
// DatabaseConfig is configuration for which data store we use.
|
|
||||||
DatabaseConfig DatabaseConfig
|
|
||||||
|
|
||||||
// EnableRateLimiter set to true enables IP and peer ID rate limiting.
|
// EnableRateLimiter set to true enables IP and peer ID rate limiting.
|
||||||
EnableRateLimiter bool
|
EnableRateLimiter bool
|
||||||
|
|
||||||
|
@ -258,9 +242,6 @@ type ClusterConfig struct {
|
||||||
// Deprecated: won't be used at all in wakuv2
|
// Deprecated: won't be used at all in wakuv2
|
||||||
BootNodes []string
|
BootNodes []string
|
||||||
|
|
||||||
// TrustedMailServers is a list of verified and trusted Mail Server nodes.
|
|
||||||
TrustedMailServers []string
|
|
||||||
|
|
||||||
// PushNotificationsServers is a list of default push notification servers.
|
// PushNotificationsServers is a list of default push notification servers.
|
||||||
PushNotificationsServers []string
|
PushNotificationsServers []string
|
||||||
|
|
||||||
|
@ -526,9 +507,6 @@ type NodeConfig struct {
|
||||||
// discoverable peers with the discovery limits.
|
// discoverable peers with the discovery limits.
|
||||||
RequireTopics map[discv5.Topic]Limits `json:"RequireTopics"`
|
RequireTopics map[discv5.Topic]Limits `json:"RequireTopics"`
|
||||||
|
|
||||||
// MailServerRegistryAddress is the MailServerRegistry contract address
|
|
||||||
MailServerRegistryAddress string
|
|
||||||
|
|
||||||
// PushNotificationServerConfig is the config for the push notification server
|
// PushNotificationServerConfig is the config for the push notification server
|
||||||
PushNotificationServerConfig PushNotificationServerConfig `json:"PushNotificationServerConfig"`
|
PushNotificationServerConfig PushNotificationServerConfig `json:"PushNotificationServerConfig"`
|
||||||
|
|
||||||
|
@ -769,13 +747,6 @@ func WithLES() Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithMailserver enables MailServer.
|
|
||||||
func WithMailserver() Option {
|
|
||||||
return func(c *NodeConfig) error {
|
|
||||||
return loadConfigFromAsset("../config/cli/mailserver-enabled.json", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithDiscV5BootstrapNodes(nodes []string) Option {
|
func WithDiscV5BootstrapNodes(nodes []string) Option {
|
||||||
return func(c *NodeConfig) error {
|
return func(c *NodeConfig) error {
|
||||||
c.ClusterConfig.DiscV5BootstrapNodes = nodes
|
c.ClusterConfig.DiscV5BootstrapNodes = nodes
|
||||||
|
@ -1079,14 +1050,6 @@ func (c *NodeConfig) Validate() error {
|
||||||
return fmt.Errorf("both Waku and WakuV2 are enabled and use the same data dir")
|
return fmt.Errorf("both Waku and WakuV2 are enabled and use the same data dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Waku's data directory must be relative to the main data directory
|
|
||||||
// if EnableMailServer is true.
|
|
||||||
if c.WakuConfig.Enabled && c.WakuConfig.EnableMailServer {
|
|
||||||
if !strings.HasPrefix(c.WakuConfig.DataDir, c.DataDir) {
|
|
||||||
return fmt.Errorf("WakuConfig.DataDir must start with DataDir fragment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.NoDiscovery && len(c.ClusterConfig.BootNodes) == 0 {
|
if !c.NoDiscovery && len(c.ClusterConfig.BootNodes) == 0 {
|
||||||
// No point in running discovery if we don't have bootnodes.
|
// No point in running discovery if we don't have bootnodes.
|
||||||
// In case we do have bootnodes, NoDiscovery should be true.
|
// In case we do have bootnodes, NoDiscovery should be true.
|
||||||
|
|
|
@ -23,7 +23,6 @@ func TestNewNodeConfigWithDefaults(t *testing.T) {
|
||||||
params.GoerliNetworkID,
|
params.GoerliNetworkID,
|
||||||
params.WithFleet(params.FleetProd),
|
params.WithFleet(params.FleetProd),
|
||||||
params.WithLES(),
|
params.WithLES(),
|
||||||
params.WithMailserver(),
|
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "/some/data/path", c.DataDir)
|
assert.Equal(t, "/some/data/path", c.DataDir)
|
||||||
|
@ -31,8 +30,6 @@ func TestNewNodeConfigWithDefaults(t *testing.T) {
|
||||||
// assert Whisper
|
// assert Whisper
|
||||||
assert.Equal(t, true, c.WakuConfig.Enabled)
|
assert.Equal(t, true, c.WakuConfig.Enabled)
|
||||||
assert.Equal(t, "/some/data/path/waku", c.WakuConfig.DataDir)
|
assert.Equal(t, "/some/data/path/waku", c.WakuConfig.DataDir)
|
||||||
// assert MailServer
|
|
||||||
assert.Equal(t, false, c.WakuConfig.EnableMailServer)
|
|
||||||
// assert cluster
|
// assert cluster
|
||||||
assert.Equal(t, false, c.NoDiscovery)
|
assert.Equal(t, false, c.NoDiscovery)
|
||||||
assert.Equal(t, params.FleetProd, c.ClusterConfig.Fleet)
|
assert.Equal(t, params.FleetProd, c.ClusterConfig.Fleet)
|
||||||
|
|
|
@ -55,8 +55,7 @@ var sendEnodeDiscovered = signal.SendEnodeDiscovered
|
||||||
// ConfirmAdded calls base TopicPool ConfirmAdded method and sends a signal
|
// ConfirmAdded calls base TopicPool ConfirmAdded method and sends a signal
|
||||||
// confirming the enode has been discovered.
|
// confirming the enode has been discovered.
|
||||||
func (t *cacheOnlyTopicPool) ConfirmAdded(server *p2p.Server, nodeID enode.ID) {
|
func (t *cacheOnlyTopicPool) ConfirmAdded(server *p2p.Server, nodeID enode.ID) {
|
||||||
trusted := t.verifier.VerifyNode(context.TODO(), nodeID)
|
if t.verifier == nil || t.verifier.VerifyNode(context.TODO(), nodeID) {
|
||||||
if trusted {
|
|
||||||
// add to cache only if trusted
|
// add to cache only if trusted
|
||||||
t.TopicPool.ConfirmAdded(server, nodeID)
|
t.TopicPool.ConfirmAdded(server, nodeID)
|
||||||
sendEnodeDiscovered(nodeID.String(), string(t.topic))
|
sendEnodeDiscovered(nodeID.String(), string(t.topic))
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
|
|
||||||
"github.com/status-im/status-go/discovery"
|
"github.com/status-im/status-go/discovery"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/peers/verifier"
|
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,8 +54,6 @@ type Options struct {
|
||||||
// TopicStopSearchDelay time stopSearch will be waiting for max cached peers to be
|
// TopicStopSearchDelay time stopSearch will be waiting for max cached peers to be
|
||||||
// filled before really stopping the search.
|
// filled before really stopping the search.
|
||||||
TopicStopSearchDelay time.Duration
|
TopicStopSearchDelay time.Duration
|
||||||
// TrustedMailServers is a list of trusted nodes.
|
|
||||||
TrustedMailServers []enode.ID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultOptions returns a struct with default Options.
|
// NewDefaultOptions returns a struct with default Options.
|
||||||
|
@ -155,11 +152,7 @@ func (p *PeerPool) Start(server *p2p.Server) error {
|
||||||
var topicPool TopicPoolInterface
|
var topicPool TopicPoolInterface
|
||||||
t := newTopicPool(p.discovery, topic, limits, p.opts.SlowSync, p.opts.FastSync, p.cache)
|
t := newTopicPool(p.discovery, topic, limits, p.opts.SlowSync, p.opts.FastSync, p.cache)
|
||||||
if topic == MailServerDiscoveryTopic {
|
if topic == MailServerDiscoveryTopic {
|
||||||
v, err := p.initVerifier()
|
topicPool = newCacheOnlyTopicPool(t, nil)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
topicPool = newCacheOnlyTopicPool(t, v)
|
|
||||||
} else {
|
} else {
|
||||||
topicPool = t
|
topicPool = t
|
||||||
}
|
}
|
||||||
|
@ -175,10 +168,6 @@ func (p *PeerPool) Start(server *p2p.Server) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PeerPool) initVerifier() (v Verifier, err error) {
|
|
||||||
return verifier.NewLocalVerifier(p.opts.TrustedMailServers), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PeerPool) startDiscovery() error {
|
func (p *PeerPool) startDiscovery() error {
|
||||||
if p.discovery.Running() {
|
if p.discovery.Running() {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -128,7 +128,7 @@ func (s *PeerPoolSimulationSuite) TestPeerPoolCacheEthV5() {
|
||||||
config := map[discv5.Topic]params.Limits{
|
config := map[discv5.Topic]params.Limits{
|
||||||
topic: params.NewLimits(1, 1),
|
topic: params.NewLimits(1, 1),
|
||||||
}
|
}
|
||||||
peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond, nil}
|
peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond}
|
||||||
cache, err := newInMemoryCache()
|
cache, err := newInMemoryCache()
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
|
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
|
||||||
|
@ -177,7 +177,7 @@ func TestPeerPoolMaxPeersOverflow(t *testing.T) {
|
||||||
defer func() { assert.NoError(t, discovery.Stop()) }()
|
defer func() { assert.NoError(t, discovery.Stop()) }()
|
||||||
require.True(t, discovery.Running())
|
require.True(t, discovery.Running())
|
||||||
|
|
||||||
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, 0, true, 100 * time.Millisecond, nil}
|
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, 0, true, 100 * time.Millisecond}
|
||||||
pool := NewPeerPool(discovery, nil, nil, poolOpts)
|
pool := NewPeerPool(discovery, nil, nil, poolOpts)
|
||||||
require.NoError(t, pool.Start(peer))
|
require.NoError(t, pool.Start(peer))
|
||||||
require.Equal(t, signal.EventDiscoveryStarted, <-signals)
|
require.Equal(t, signal.EventDiscoveryStarted, <-signals)
|
||||||
|
@ -230,7 +230,7 @@ func TestPeerPoolDiscV5Timeout(t *testing.T) {
|
||||||
require.True(t, discovery.Running())
|
require.True(t, discovery.Running())
|
||||||
|
|
||||||
// start PeerPool
|
// start PeerPool
|
||||||
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, true, 100 * time.Millisecond, nil}
|
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, true, 100 * time.Millisecond}
|
||||||
pool := NewPeerPool(discovery, nil, nil, poolOpts)
|
pool := NewPeerPool(discovery, nil, nil, poolOpts)
|
||||||
require.NoError(t, pool.Start(server))
|
require.NoError(t, pool.Start(server))
|
||||||
require.Equal(t, signal.EventDiscoveryStarted, <-signals)
|
require.Equal(t, signal.EventDiscoveryStarted, <-signals)
|
||||||
|
@ -277,7 +277,7 @@ func TestPeerPoolNotAllowedStopping(t *testing.T) {
|
||||||
require.True(t, discovery.Running())
|
require.True(t, discovery.Running())
|
||||||
|
|
||||||
// start PeerPool
|
// start PeerPool
|
||||||
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, false, 100 * time.Millisecond, nil}
|
poolOpts := &Options{DefaultFastSync, DefaultSlowSync, time.Millisecond * 100, false, 100 * time.Millisecond}
|
||||||
pool := NewPeerPool(discovery, nil, nil, poolOpts)
|
pool := NewPeerPool(discovery, nil, nil, poolOpts)
|
||||||
require.NoError(t, pool.Start(server))
|
require.NoError(t, pool.Start(server))
|
||||||
|
|
||||||
|
@ -294,7 +294,7 @@ func (s *PeerPoolSimulationSuite) TestUpdateTopicLimits() {
|
||||||
config := map[discv5.Topic]params.Limits{
|
config := map[discv5.Topic]params.Limits{
|
||||||
topic: params.NewLimits(1, 1),
|
topic: params.NewLimits(1, 1),
|
||||||
}
|
}
|
||||||
peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond, nil}
|
peerPoolOpts := &Options{100 * time.Millisecond, 100 * time.Millisecond, 0, true, 100 * time.Millisecond}
|
||||||
cache, err := newInMemoryCache()
|
cache, err := newInMemoryCache()
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
|
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
|
||||||
|
@ -374,7 +374,6 @@ func (s *PeerPoolSimulationSuite) TestMailServerPeersDiscovery() {
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
100 * time.Millisecond,
|
100 * time.Millisecond,
|
||||||
[]enode.ID{s.peers[0].Self().ID()},
|
|
||||||
}
|
}
|
||||||
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
|
peerPool := NewPeerPool(s.discovery[1], config, cache, peerPoolOpts)
|
||||||
s.Require().NoError(peerPool.Start(s.peers[1]))
|
s.Require().NoError(peerPool.Start(s.peers[1]))
|
||||||
|
|
|
@ -64,7 +64,6 @@ import (
|
||||||
"github.com/status-im/status-go/server"
|
"github.com/status-im/status-go/server"
|
||||||
"github.com/status-im/status-go/services/browsers"
|
"github.com/status-im/status-go/services/browsers"
|
||||||
ensservice "github.com/status-im/status-go/services/ens"
|
ensservice "github.com/status-im/status-go/services/ens"
|
||||||
"github.com/status-im/status-go/services/ext/mailservers"
|
|
||||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||||
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
||||||
"github.com/status-im/status-go/services/wallet"
|
"github.com/status-im/status-go/services/wallet"
|
||||||
|
@ -98,12 +97,9 @@ var messageCacheIntervalMs uint64 = 1000 * 60 * 60 * 48
|
||||||
// layers.
|
// layers.
|
||||||
// It needs to expose an interface to manage installations
|
// It needs to expose an interface to manage installations
|
||||||
// because installations are managed by the user.
|
// because installations are managed by the user.
|
||||||
// Similarly, it needs to expose an interface to manage
|
|
||||||
// mailservers because they can also be managed by the user.
|
|
||||||
type Messenger struct {
|
type Messenger struct {
|
||||||
node types.Node
|
node types.Node
|
||||||
server *p2p.Server
|
server *p2p.Server
|
||||||
peerStore *mailservers.PeerStore
|
|
||||||
config *config
|
config *config
|
||||||
identity *ecdsa.PrivateKey
|
identity *ecdsa.PrivateKey
|
||||||
persistence *sqlitePersistence
|
persistence *sqlitePersistence
|
||||||
|
@ -291,24 +287,11 @@ func (interceptor EnvelopeEventsInterceptor) EnvelopeExpired(identifiers [][]byt
|
||||||
interceptor.EnvelopeEventsHandler.EnvelopeExpired(identifiers, err)
|
interceptor.EnvelopeEventsHandler.EnvelopeExpired(identifiers, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailServerRequestCompleted triggered when the mailserver sends a message to notify that the request has been completed
|
|
||||||
func (interceptor EnvelopeEventsInterceptor) MailServerRequestCompleted(requestID types.Hash, lastEnvelopeHash types.Hash, cursor []byte, err error) {
|
|
||||||
//we don't track mailserver requests in Messenger, so just redirect to handler
|
|
||||||
interceptor.EnvelopeEventsHandler.MailServerRequestCompleted(requestID, lastEnvelopeHash, cursor, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MailServerRequestExpired triggered when the mailserver request expires
|
|
||||||
func (interceptor EnvelopeEventsInterceptor) MailServerRequestExpired(hash types.Hash) {
|
|
||||||
//we don't track mailserver requests in Messenger, so just redirect to handler
|
|
||||||
interceptor.EnvelopeEventsHandler.MailServerRequestExpired(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMessenger(
|
func NewMessenger(
|
||||||
nodeName string,
|
nodeName string,
|
||||||
identity *ecdsa.PrivateKey,
|
identity *ecdsa.PrivateKey,
|
||||||
node types.Node,
|
node types.Node,
|
||||||
installationID string,
|
installationID string,
|
||||||
peerStore *mailservers.PeerStore,
|
|
||||||
version string,
|
version string,
|
||||||
opts ...Option,
|
opts ...Option,
|
||||||
) (*Messenger, error) {
|
) (*Messenger, error) {
|
||||||
|
@ -594,7 +577,6 @@ func NewMessenger(
|
||||||
peersyncing: peersyncing.New(peersyncing.Config{Database: database, Timesource: transp}),
|
peersyncing: peersyncing.New(peersyncing.Config{Database: database, Timesource: transp}),
|
||||||
peersyncingOffers: make(map[string]uint64),
|
peersyncingOffers: make(map[string]uint64),
|
||||||
peersyncingRequests: make(map[string]uint64),
|
peersyncingRequests: make(map[string]uint64),
|
||||||
peerStore: peerStore,
|
|
||||||
mvdsStatusChangeEvent: make(chan datasyncnode.PeerStatusChangeEvent, 5),
|
mvdsStatusChangeEvent: make(chan datasyncnode.PeerStatusChangeEvent, 5),
|
||||||
verificationDatabase: verification.NewPersistence(database),
|
verificationDatabase: verification.NewPersistence(database),
|
||||||
mailserverCycle: mailserverCycle{
|
mailserverCycle: mailserverCycle{
|
||||||
|
|
|
@ -91,7 +91,6 @@ func newTestMessenger(waku types.Waku, config testMessengerConfig) (*Messenger,
|
||||||
config.privateKey,
|
config.privateKey,
|
||||||
&testNode{shh: waku},
|
&testNode{shh: waku},
|
||||||
uuid.New().String(),
|
uuid.New().String(),
|
||||||
nil,
|
|
||||||
"testVersion",
|
"testVersion",
|
||||||
options...,
|
options...,
|
||||||
)
|
)
|
||||||
|
|
|
@ -35,16 +35,6 @@ func (h EnvelopeSignalHandlerMock) EnvelopeExpired(identifiers [][]byte, err err
|
||||||
signal.SendEnvelopeExpired(identifiers, err)
|
signal.SendEnvelopeExpired(identifiers, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailServerRequestCompleted triggered when the mailserver sends a message to notify that the request has been completed
|
|
||||||
func (h EnvelopeSignalHandlerMock) MailServerRequestCompleted(requestID types.Hash, lastEnvelopeHash types.Hash, cursor []byte, err error) {
|
|
||||||
signal.SendMailServerRequestCompleted(requestID, lastEnvelopeHash, cursor, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MailServerRequestExpired triggered when the mailserver request expires
|
|
||||||
func (h EnvelopeSignalHandlerMock) MailServerRequestExpired(hash types.Hash) {
|
|
||||||
signal.SendMailServerRequestExpired(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
type EnvelopeEventsInterceptorMock struct {
|
type EnvelopeEventsInterceptorMock struct {
|
||||||
EnvelopeEventsInterceptor
|
EnvelopeEventsInterceptor
|
||||||
|
|
||||||
|
@ -127,7 +117,6 @@ func (s *MessengerMessagesTrackingSuite) newMessenger(waku types.Waku, logger *z
|
||||||
EnvelopeEventsHandler: EnvelopeSignalHandlerMock{},
|
EnvelopeEventsHandler: EnvelopeSignalHandlerMock{},
|
||||||
MaxAttempts: 1,
|
MaxAttempts: 1,
|
||||||
AwaitOnlyMailServerConfirmations: false,
|
AwaitOnlyMailServerConfirmations: false,
|
||||||
IsMailserver: func(peer types.EnodeID) bool { return false },
|
|
||||||
Logger: s.logger,
|
Logger: s.logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ type EnvelopesMonitorConfig struct {
|
||||||
EnvelopeEventsHandler EnvelopeEventsHandler
|
EnvelopeEventsHandler EnvelopeEventsHandler
|
||||||
MaxAttempts int
|
MaxAttempts int
|
||||||
AwaitOnlyMailServerConfirmations bool
|
AwaitOnlyMailServerConfirmations bool
|
||||||
IsMailserver func(types.EnodeID) bool
|
|
||||||
Logger *zap.Logger
|
Logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +35,6 @@ type EnvelopesMonitorConfig struct {
|
||||||
type EnvelopeEventsHandler interface {
|
type EnvelopeEventsHandler interface {
|
||||||
EnvelopeSent([][]byte)
|
EnvelopeSent([][]byte)
|
||||||
EnvelopeExpired([][]byte, error)
|
EnvelopeExpired([][]byte, error)
|
||||||
MailServerRequestCompleted(types.Hash, types.Hash, []byte, error)
|
|
||||||
MailServerRequestExpired(types.Hash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEnvelopesMonitor returns a pointer to an instance of the EnvelopesMonitor.
|
// NewEnvelopesMonitor returns a pointer to an instance of the EnvelopesMonitor.
|
||||||
|
@ -59,7 +56,6 @@ func NewEnvelopesMonitor(w types.Waku, config EnvelopesMonitorConfig) *Envelopes
|
||||||
handler: config.EnvelopeEventsHandler,
|
handler: config.EnvelopeEventsHandler,
|
||||||
awaitOnlyMailServerConfirmations: config.AwaitOnlyMailServerConfirmations,
|
awaitOnlyMailServerConfirmations: config.AwaitOnlyMailServerConfirmations,
|
||||||
maxAttempts: config.MaxAttempts,
|
maxAttempts: config.MaxAttempts,
|
||||||
isMailserver: config.IsMailserver,
|
|
||||||
logger: logger.With(zap.Namespace("EnvelopesMonitor")),
|
logger: logger.With(zap.Namespace("EnvelopesMonitor")),
|
||||||
|
|
||||||
// key is envelope hash (event.Hash)
|
// key is envelope hash (event.Hash)
|
||||||
|
@ -200,6 +196,9 @@ func (m *EnvelopesMonitor) handleEvent(event types.EnvelopeEvent) {
|
||||||
|
|
||||||
func (m *EnvelopesMonitor) handleEventEnvelopeSent(event types.EnvelopeEvent) {
|
func (m *EnvelopesMonitor) handleEventEnvelopeSent(event types.EnvelopeEvent) {
|
||||||
// Mailserver confirmations for WakuV2 are disabled
|
// Mailserver confirmations for WakuV2 are disabled
|
||||||
|
// Perhaps code might be reused?
|
||||||
|
|
||||||
|
/*
|
||||||
if (m.w == nil || m.w.Version() < 2) && m.awaitOnlyMailServerConfirmations {
|
if (m.w == nil || m.w.Version() < 2) && m.awaitOnlyMailServerConfirmations {
|
||||||
if !m.isMailserver(event.Peer) {
|
if !m.isMailserver(event.Peer) {
|
||||||
return
|
return
|
||||||
|
@ -236,6 +235,7 @@ func (m *EnvelopesMonitor) handleEventEnvelopeSent(event types.EnvelopeEvent) {
|
||||||
envelope.state = EnvelopeSent
|
envelope.state = EnvelopeSent
|
||||||
m.processMessageIDs(envelope.messageIDs)
|
m.processMessageIDs(envelope.messageIDs)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *EnvelopesMonitor) handleAcknowledgedBatch(event types.EnvelopeEvent) {
|
func (m *EnvelopesMonitor) handleAcknowledgedBatch(event types.EnvelopeEvent) {
|
||||||
|
|
|
@ -55,7 +55,6 @@ func (s *EnvelopesMonitorSuite) SetupTest() {
|
||||||
EnvelopeEventsHandler: s.eventsHandlerMock,
|
EnvelopeEventsHandler: s.eventsHandlerMock,
|
||||||
MaxAttempts: 6,
|
MaxAttempts: 6,
|
||||||
AwaitOnlyMailServerConfirmations: false,
|
AwaitOnlyMailServerConfirmations: false,
|
||||||
IsMailserver: func(types.EnodeID) bool { return false },
|
|
||||||
Logger: zap.NewNop(),
|
Logger: zap.NewNop(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,9 +3,7 @@ package ext
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -20,14 +18,12 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
|
|
||||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/images"
|
"github.com/status-im/status-go/images"
|
||||||
"github.com/status-im/status-go/mailserver"
|
|
||||||
multiaccountscommon "github.com/status-im/status-go/multiaccounts/common"
|
multiaccountscommon "github.com/status-im/status-go/multiaccounts/common"
|
||||||
"github.com/status-im/status-go/multiaccounts/settings"
|
"github.com/status-im/status-go/multiaccounts/settings"
|
||||||
"github.com/status-im/status-go/protocol"
|
"github.com/status-im/status-go/protocol"
|
||||||
|
@ -44,12 +40,6 @@ import (
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
"github.com/status-im/status-go/protocol/transport"
|
||||||
"github.com/status-im/status-go/protocol/urls"
|
"github.com/status-im/status-go/protocol/urls"
|
||||||
"github.com/status-im/status-go/protocol/verification"
|
"github.com/status-im/status-go/protocol/verification"
|
||||||
"github.com/status-im/status-go/services/ext/mailservers"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// defaultRequestTimeout is the default request timeout in seconds
|
|
||||||
defaultRequestTimeout = 10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -69,73 +59,11 @@ var (
|
||||||
// PAYLOADS
|
// PAYLOADS
|
||||||
// -----
|
// -----
|
||||||
|
|
||||||
// MessagesRequest is a RequestMessages() request payload.
|
|
||||||
type MessagesRequest struct {
|
|
||||||
// MailServerPeer is MailServer's enode address.
|
|
||||||
MailServerPeer string `json:"mailServerPeer"`
|
|
||||||
|
|
||||||
// From is a lower bound of time range (optional).
|
|
||||||
// Default is 24 hours back from now.
|
|
||||||
From uint32 `json:"from"`
|
|
||||||
|
|
||||||
// To is a upper bound of time range (optional).
|
|
||||||
// Default is now.
|
|
||||||
To uint32 `json:"to"`
|
|
||||||
|
|
||||||
// Limit determines the number of messages sent by the mail server
|
|
||||||
// for the current paginated request
|
|
||||||
Limit uint32 `json:"limit"`
|
|
||||||
|
|
||||||
// Cursor is used as starting point for paginated requests
|
|
||||||
Cursor string `json:"cursor"`
|
|
||||||
|
|
||||||
// StoreCursor is used as starting point for WAKUV2 paginatedRequests
|
|
||||||
StoreCursor *StoreRequestCursor `json:"storeCursor"`
|
|
||||||
|
|
||||||
// Topic is a regular Whisper topic.
|
|
||||||
// DEPRECATED
|
|
||||||
Topic types.TopicType `json:"topic"`
|
|
||||||
|
|
||||||
// Topics is a list of Whisper topics.
|
|
||||||
Topics []types.TopicType `json:"topics"`
|
|
||||||
|
|
||||||
// SymKeyID is an ID of a symmetric key to authenticate to MailServer.
|
|
||||||
// It's derived from MailServer password.
|
|
||||||
SymKeyID string `json:"symKeyID"`
|
|
||||||
|
|
||||||
// Timeout is the time to live of the request specified in seconds.
|
|
||||||
// Default is 10 seconds
|
|
||||||
Timeout time.Duration `json:"timeout"`
|
|
||||||
|
|
||||||
// Force ensures that requests will bypass enforced delay.
|
|
||||||
Force bool `json:"force"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type StoreRequestCursor struct {
|
type StoreRequestCursor struct {
|
||||||
Digest []byte `json:"digest"`
|
Digest []byte `json:"digest"`
|
||||||
ReceivedTime float64 `json:"receivedTime"`
|
ReceivedTime float64 `json:"receivedTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *MessagesRequest) SetDefaults(now time.Time) {
|
|
||||||
// set From and To defaults
|
|
||||||
if r.To == 0 {
|
|
||||||
r.To = uint32(now.UTC().Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.From == 0 {
|
|
||||||
oneDay := uint32(86400) // -24 hours
|
|
||||||
if r.To < oneDay {
|
|
||||||
r.From = 0
|
|
||||||
} else {
|
|
||||||
r.From = r.To - oneDay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Timeout == 0 {
|
|
||||||
r.Timeout = defaultRequestTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MessagesResponse is a response for requestMessages2 method.
|
// MessagesResponse is a response for requestMessages2 method.
|
||||||
type MessagesResponse struct {
|
type MessagesResponse struct {
|
||||||
// Cursor from the response can be used to retrieve more messages
|
// Cursor from the response can be used to retrieve more messages
|
||||||
|
@ -154,15 +82,13 @@ type MessagesResponse struct {
|
||||||
// PublicAPI extends whisper public API.
|
// PublicAPI extends whisper public API.
|
||||||
type PublicAPI struct {
|
type PublicAPI struct {
|
||||||
service *Service
|
service *Service
|
||||||
eventSub mailservers.EnvelopeEventSubscriber
|
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicAPI returns instance of the public API.
|
// NewPublicAPI returns instance of the public API.
|
||||||
func NewPublicAPI(s *Service, eventSub mailservers.EnvelopeEventSubscriber) *PublicAPI {
|
func NewPublicAPI(s *Service) *PublicAPI {
|
||||||
return &PublicAPI{
|
return &PublicAPI{
|
||||||
service: s,
|
service: s,
|
||||||
eventSub: eventSub,
|
|
||||||
log: log.New("package", "status-go/services/sshext.PublicAPI"),
|
log: log.New("package", "status-go/services/sshext.PublicAPI"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,33 +101,6 @@ type RetryConfig struct {
|
||||||
MaxRetries int
|
MaxRetries int
|
||||||
}
|
}
|
||||||
|
|
||||||
func WaitForExpiredOrCompleted(requestID types.Hash, events chan types.EnvelopeEvent, timeout time.Duration) (*types.MailServerResponse, error) {
|
|
||||||
expired := fmt.Errorf("request %x expired", requestID)
|
|
||||||
after := time.NewTimer(timeout)
|
|
||||||
defer after.Stop()
|
|
||||||
for {
|
|
||||||
var ev types.EnvelopeEvent
|
|
||||||
select {
|
|
||||||
case ev = <-events:
|
|
||||||
case <-after.C:
|
|
||||||
return nil, expired
|
|
||||||
}
|
|
||||||
if ev.Hash != requestID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch ev.Event {
|
|
||||||
case types.EventMailServerRequestCompleted:
|
|
||||||
data, ok := ev.Data.(*types.MailServerResponse)
|
|
||||||
if ok {
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("invalid event data type")
|
|
||||||
case types.EventMailServerRequestExpired:
|
|
||||||
return nil, expired
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Author struct {
|
type Author struct {
|
||||||
PublicKey types.HexBytes `json:"publicKey"`
|
PublicKey types.HexBytes `json:"publicKey"`
|
||||||
Alias string `json:"alias"`
|
Alias string `json:"alias"`
|
||||||
|
@ -1862,57 +1761,6 @@ func (api *PublicAPI) DeleteCommunityMemberMessages(request *requests.DeleteComm
|
||||||
return api.service.messenger.DeleteCommunityMemberMessages(request)
|
return api.service.messenger.DeleteCommunityMemberMessages(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----
|
|
||||||
// HELPER
|
|
||||||
// -----
|
|
||||||
|
|
||||||
// MakeMessagesRequestPayload makes a specific payload for MailServer
|
|
||||||
// to request historic messages.
|
|
||||||
// DEPRECATED
|
|
||||||
func MakeMessagesRequestPayload(r MessagesRequest) ([]byte, error) {
|
|
||||||
cursor, err := hex.DecodeString(r.Cursor)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid cursor: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cursor) > 0 && len(cursor) != mailserver.CursorLength {
|
|
||||||
return nil, fmt.Errorf("invalid cursor size: expected %d but got %d", mailserver.CursorLength, len(cursor))
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := mailserver.MessagesRequestPayload{
|
|
||||||
Lower: r.From,
|
|
||||||
Upper: r.To,
|
|
||||||
// We need to pass bloom filter for
|
|
||||||
// backward compatibility
|
|
||||||
Bloom: createBloomFilter(r),
|
|
||||||
Topics: topicsToByteArray(r.Topics),
|
|
||||||
Limit: r.Limit,
|
|
||||||
Cursor: cursor,
|
|
||||||
// Client must tell the MailServer if it supports batch responses.
|
|
||||||
// This can be removed in the future.
|
|
||||||
Batch: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return rlp.EncodeToBytes(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func topicsToByteArray(topics []types.TopicType) [][]byte {
|
|
||||||
|
|
||||||
var response [][]byte
|
|
||||||
for idx := range topics {
|
|
||||||
response = append(response, topics[idx][:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
func createBloomFilter(r MessagesRequest) []byte {
|
|
||||||
if len(r.Topics) > 0 {
|
|
||||||
return topicsToBloom(r.Topics...)
|
|
||||||
}
|
|
||||||
return types.TopicToBloom(r.Topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func topicsToBloom(topics ...types.TopicType) []byte {
|
func topicsToBloom(topics ...types.TopicType) []byte {
|
||||||
i := new(big.Int)
|
i := new(big.Int)
|
||||||
for _, topic := range topics {
|
for _, topic := range topics {
|
||||||
|
|
|
@ -1,102 +1,13 @@
|
||||||
package ext
|
package ext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
|
|
||||||
"github.com/status-im/status-go/mailserver"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMessagesRequest_setDefaults(t *testing.T) {
|
|
||||||
daysAgo := func(now time.Time, days int) uint32 {
|
|
||||||
return uint32(now.UTC().Add(-24 * time.Hour * time.Duration(days)).Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
tnow := time.Now()
|
|
||||||
now := uint32(tnow.UTC().Unix())
|
|
||||||
yesterday := daysAgo(tnow, 1)
|
|
||||||
|
|
||||||
scenarios := []struct {
|
|
||||||
given *MessagesRequest
|
|
||||||
expected *MessagesRequest
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
&MessagesRequest{From: 0, To: 0},
|
|
||||||
&MessagesRequest{From: yesterday, To: now, Timeout: defaultRequestTimeout},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
&MessagesRequest{From: 1, To: 0},
|
|
||||||
&MessagesRequest{From: uint32(1), To: now, Timeout: defaultRequestTimeout},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
&MessagesRequest{From: 0, To: yesterday},
|
|
||||||
&MessagesRequest{From: daysAgo(tnow, 2), To: yesterday, Timeout: defaultRequestTimeout},
|
|
||||||
},
|
|
||||||
// 100 - 1 day would be invalid, so we set From to 0
|
|
||||||
{
|
|
||||||
&MessagesRequest{From: 0, To: 100},
|
|
||||||
&MessagesRequest{From: 0, To: 100, Timeout: defaultRequestTimeout},
|
|
||||||
},
|
|
||||||
// set Timeout
|
|
||||||
{
|
|
||||||
&MessagesRequest{From: 0, To: 0, Timeout: 100},
|
|
||||||
&MessagesRequest{From: yesterday, To: now, Timeout: 100},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, s := range scenarios {
|
|
||||||
t.Run(fmt.Sprintf("Scenario %d", i), func(t *testing.T) {
|
|
||||||
s.given.SetDefaults(tnow)
|
|
||||||
require.Equal(t, s.expected, s.given)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeMessagesRequestPayload(t *testing.T) {
|
|
||||||
var emptyTopic types.TopicType
|
|
||||||
testCases := []struct {
|
|
||||||
Name string
|
|
||||||
Req MessagesRequest
|
|
||||||
Err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "empty cursor",
|
|
||||||
Req: MessagesRequest{Cursor: ""},
|
|
||||||
Err: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "invalid cursor size",
|
|
||||||
Req: MessagesRequest{Cursor: hex.EncodeToString([]byte{0x01, 0x02, 0x03})},
|
|
||||||
Err: fmt.Sprintf("invalid cursor size: expected %d but got 3", mailserver.CursorLength),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "valid cursor",
|
|
||||||
Req: MessagesRequest{
|
|
||||||
Cursor: hex.EncodeToString(mailserver.NewDBKey(123, emptyTopic, types.Hash{}).Cursor()),
|
|
||||||
},
|
|
||||||
Err: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.Name, func(t *testing.T) {
|
|
||||||
_, err := MakeMessagesRequestPayload(tc.Req)
|
|
||||||
if tc.Err == "" {
|
|
||||||
require.NoError(t, err)
|
|
||||||
} else {
|
|
||||||
require.EqualError(t, err, tc.Err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTopicsToBloom(t *testing.T) {
|
func TestTopicsToBloom(t *testing.T) {
|
||||||
t1 := stringToTopic("t1")
|
t1 := stringToTopic("t1")
|
||||||
b1 := types.TopicToBloom(t1)
|
b1 := types.TopicToBloom(t1)
|
||||||
|
@ -121,36 +32,6 @@ func TestTopicsToBloom(t *testing.T) {
|
||||||
assert.True(t, types.BloomFilterMatch(reqBloom, b3))
|
assert.True(t, types.BloomFilterMatch(reqBloom, b3))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateBloomFilter(t *testing.T) {
|
|
||||||
t1 := stringToTopic("t1")
|
|
||||||
t2 := stringToTopic("t2")
|
|
||||||
|
|
||||||
req := MessagesRequest{Topic: t1}
|
|
||||||
bloom := createBloomFilter(req)
|
|
||||||
assert.Equal(t, topicsToBloom(t1), bloom)
|
|
||||||
|
|
||||||
req = MessagesRequest{Topics: []types.TopicType{t1, t2}}
|
|
||||||
bloom = createBloomFilter(req)
|
|
||||||
assert.Equal(t, topicsToBloom(t1, t2), bloom)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringToTopic(s string) types.TopicType {
|
func stringToTopic(s string) types.TopicType {
|
||||||
return types.BytesToTopic([]byte(s))
|
return types.BytesToTopic([]byte(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpiredOrCompleted(t *testing.T) {
|
|
||||||
timeout := time.Millisecond
|
|
||||||
events := make(chan types.EnvelopeEvent)
|
|
||||||
errors := make(chan error, 1)
|
|
||||||
hash := types.Hash{1}
|
|
||||||
go func() {
|
|
||||||
_, err := WaitForExpiredOrCompleted(hash, events, timeout)
|
|
||||||
errors <- err
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
require.FailNow(t, "timed out waiting for waitForExpiredOrCompleted to complete")
|
|
||||||
case err := <-errors:
|
|
||||||
require.EqualError(t, err, fmt.Sprintf("request %x expired", hash))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
package ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/services/ext/mailservers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EnvelopeState in local tracker
|
|
||||||
type EnvelopeState int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NotRegistered returned if asked hash wasn't registered in the tracker.
|
|
||||||
NotRegistered EnvelopeState = -1
|
|
||||||
// MailServerRequestSent is set when p2p request is sent to the mailserver
|
|
||||||
MailServerRequestSent
|
|
||||||
)
|
|
||||||
|
|
||||||
// MailRequestMonitor is responsible for monitoring history request to mailservers.
|
|
||||||
type MailRequestMonitor struct {
|
|
||||||
eventSub mailservers.EnvelopeEventSubscriber
|
|
||||||
handler EnvelopeEventsHandler
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
cache map[types.Hash]EnvelopeState
|
|
||||||
|
|
||||||
requestsRegistry *RequestsRegistry
|
|
||||||
|
|
||||||
wg sync.WaitGroup
|
|
||||||
quit chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMailRequestMonitor(eventSub mailservers.EnvelopeEventSubscriber, h EnvelopeEventsHandler, reg *RequestsRegistry) *MailRequestMonitor {
|
|
||||||
return &MailRequestMonitor{
|
|
||||||
eventSub: eventSub,
|
|
||||||
handler: h,
|
|
||||||
cache: make(map[types.Hash]EnvelopeState),
|
|
||||||
requestsRegistry: reg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start processing events.
|
|
||||||
func (m *MailRequestMonitor) Start() {
|
|
||||||
m.quit = make(chan struct{})
|
|
||||||
m.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
m.handleEnvelopeEvents()
|
|
||||||
m.wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop process events.
|
|
||||||
func (m *MailRequestMonitor) Stop() {
|
|
||||||
close(m.quit)
|
|
||||||
m.wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MailRequestMonitor) GetState(hash types.Hash) EnvelopeState {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
state, exist := m.cache[hash]
|
|
||||||
if !exist {
|
|
||||||
return NotRegistered
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEnvelopeEvents processes whisper envelope events
|
|
||||||
func (m *MailRequestMonitor) handleEnvelopeEvents() {
|
|
||||||
events := make(chan types.EnvelopeEvent, 100) // must be buffered to prevent blocking whisper
|
|
||||||
sub := m.eventSub.SubscribeEnvelopeEvents(events)
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-m.quit:
|
|
||||||
return
|
|
||||||
case event := <-events:
|
|
||||||
m.handleEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEvent based on type of the event either triggers
|
|
||||||
// confirmation handler or removes hash from MailRequestMonitor
|
|
||||||
func (m *MailRequestMonitor) handleEvent(event types.EnvelopeEvent) {
|
|
||||||
handlers := map[types.EventType]func(types.EnvelopeEvent){
|
|
||||||
types.EventMailServerRequestSent: m.handleRequestSent,
|
|
||||||
types.EventMailServerRequestCompleted: m.handleEventMailServerRequestCompleted,
|
|
||||||
types.EventMailServerRequestExpired: m.handleEventMailServerRequestExpired,
|
|
||||||
}
|
|
||||||
|
|
||||||
if handler, ok := handlers[event.Event]; ok {
|
|
||||||
handler(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MailRequestMonitor) handleRequestSent(event types.EnvelopeEvent) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.cache[event.Hash] = MailServerRequestSent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MailRequestMonitor) handleEventMailServerRequestCompleted(event types.EnvelopeEvent) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.requestsRegistry.Unregister(event.Hash)
|
|
||||||
state, ok := m.cache[event.Hash]
|
|
||||||
if !ok || state != MailServerRequestSent {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug("mailserver response received", "hash", event.Hash)
|
|
||||||
delete(m.cache, event.Hash)
|
|
||||||
if m.handler != nil {
|
|
||||||
if resp, ok := event.Data.(*types.MailServerResponse); ok {
|
|
||||||
m.handler.MailServerRequestCompleted(event.Hash, resp.LastEnvelopeHash, resp.Cursor, resp.Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MailRequestMonitor) handleEventMailServerRequestExpired(event types.EnvelopeEvent) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.requestsRegistry.Unregister(event.Hash)
|
|
||||||
state, ok := m.cache[event.Hash]
|
|
||||||
if !ok || state != MailServerRequestSent {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug("mailserver response expired", "hash", event.Hash)
|
|
||||||
delete(m.cache, event.Hash)
|
|
||||||
if m.handler != nil {
|
|
||||||
m.handler.MailServerRequestExpired(event.Hash)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
package ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
testHash = types.Hash{0x01}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMailRequestMonitorSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(MailRequestMonitorSuite))
|
|
||||||
}
|
|
||||||
|
|
||||||
type MailRequestMonitorSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
monitor *MailRequestMonitor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailRequestMonitorSuite) SetupTest() {
|
|
||||||
s.monitor = &MailRequestMonitor{
|
|
||||||
cache: map[types.Hash]EnvelopeState{},
|
|
||||||
requestsRegistry: NewRequestsRegistry(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailRequestMonitorSuite) TestRequestCompleted() {
|
|
||||||
mock := NewHandlerMock(1)
|
|
||||||
s.monitor.handler = mock
|
|
||||||
s.monitor.cache[testHash] = MailServerRequestSent
|
|
||||||
s.monitor.handleEvent(types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestCompleted,
|
|
||||||
Hash: testHash,
|
|
||||||
Data: &types.MailServerResponse{},
|
|
||||||
})
|
|
||||||
select {
|
|
||||||
case requestID := <-mock.requestsCompleted:
|
|
||||||
s.Equal(testHash, requestID)
|
|
||||||
s.NotContains(s.monitor.cache, testHash)
|
|
||||||
case <-time.After(10 * time.Second):
|
|
||||||
s.Fail("timed out while waiting for a request to be completed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailRequestMonitorSuite) TestRequestFailed() {
|
|
||||||
mock := NewHandlerMock(1)
|
|
||||||
s.monitor.handler = mock
|
|
||||||
s.monitor.cache[testHash] = MailServerRequestSent
|
|
||||||
s.monitor.handleEvent(types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestCompleted,
|
|
||||||
Hash: testHash,
|
|
||||||
Data: &types.MailServerResponse{Error: errors.New("test error")},
|
|
||||||
})
|
|
||||||
select {
|
|
||||||
case requestID := <-mock.requestsFailed:
|
|
||||||
s.Equal(testHash, requestID)
|
|
||||||
s.NotContains(s.monitor.cache, testHash)
|
|
||||||
case <-time.After(10 * time.Second):
|
|
||||||
s.Fail("timed out while waiting for a request to be failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailRequestMonitorSuite) TestRequestExpiration() {
|
|
||||||
mock := NewHandlerMock(1)
|
|
||||||
s.monitor.handler = mock
|
|
||||||
s.monitor.cache[testHash] = MailServerRequestSent
|
|
||||||
s.monitor.handleEvent(types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestExpired,
|
|
||||||
Hash: testHash,
|
|
||||||
})
|
|
||||||
select {
|
|
||||||
case requestID := <-mock.requestsExpired:
|
|
||||||
s.Equal(testHash, requestID)
|
|
||||||
s.NotContains(s.monitor.cache, testHash)
|
|
||||||
case <-time.After(10 * time.Second):
|
|
||||||
s.Fail("timed out while waiting for request expiration")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,145 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/util"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/db"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewPeerRecord returns instance of the peer record.
|
|
||||||
func NewPeerRecord(node *enode.Node) PeerRecord {
|
|
||||||
return PeerRecord{node: node}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeerRecord is set data associated with each peer that is stored on disk.
|
|
||||||
// PeerRecord stored with a enode as a key in leveldb, and body marshalled as json.
|
|
||||||
type PeerRecord struct {
|
|
||||||
node *enode.Node
|
|
||||||
|
|
||||||
// last time it was used.
|
|
||||||
LastUsed time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode encodes PeerRecords to bytes.
|
|
||||||
func (r PeerRecord) Encode() ([]byte, error) {
|
|
||||||
return json.Marshal(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns enode identity of the node.
|
|
||||||
func (r PeerRecord) ID() enode.ID {
|
|
||||||
return r.node.ID()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node returs pointer to original object.
|
|
||||||
// enode.Node doensn't allow modification on the object.
|
|
||||||
func (r PeerRecord) Node() *enode.Node {
|
|
||||||
return r.node
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeKey returns bytes that will should be used as a key in persistent storage.
|
|
||||||
func (r PeerRecord) EncodeKey() ([]byte, error) {
|
|
||||||
return r.Node().MarshalText()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCache returns pointer to a Cache instance.
|
|
||||||
func NewCache(db *leveldb.DB) *Cache {
|
|
||||||
return &Cache{db: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache is wrapper for operations on disk with leveldb.
|
|
||||||
type Cache struct {
|
|
||||||
db *leveldb.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace deletes old and adds new records in the persistent cache.
|
|
||||||
func (c *Cache) Replace(nodes []*enode.Node) error {
|
|
||||||
batch := new(leveldb.Batch)
|
|
||||||
iter := createPeersIterator(c.db)
|
|
||||||
defer iter.Release()
|
|
||||||
newNodes := nodesToMap(nodes)
|
|
||||||
for iter.Next() {
|
|
||||||
record, err := unmarshalKeyValue(keyWithoutPrefix(iter.Key()), iter.Value())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, exist := newNodes[types.EnodeID(record.ID())]; exist {
|
|
||||||
delete(newNodes, types.EnodeID(record.ID()))
|
|
||||||
} else {
|
|
||||||
batch.Delete(iter.Key())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, n := range newNodes {
|
|
||||||
enodeKey, err := n.MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// we put nil as default value doesn't have any state associated with them.
|
|
||||||
batch.Put(db.Key(db.MailserversCache, enodeKey), nil)
|
|
||||||
}
|
|
||||||
return c.db.Write(batch, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadAll loads all records from persistent database.
|
|
||||||
func (c *Cache) LoadAll() (rst []PeerRecord, err error) {
|
|
||||||
iter := createPeersIterator(c.db)
|
|
||||||
for iter.Next() {
|
|
||||||
record, err := unmarshalKeyValue(keyWithoutPrefix(iter.Key()), iter.Value())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rst = append(rst, record)
|
|
||||||
}
|
|
||||||
return rst, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRecord updates single record.
|
|
||||||
func (c *Cache) UpdateRecord(record PeerRecord) error {
|
|
||||||
enodeKey, err := record.EncodeKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
value, err := record.Encode()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.db.Put(db.Key(db.MailserversCache, enodeKey), value, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalKeyValue(key, value []byte) (record PeerRecord, err error) {
|
|
||||||
enodeKey := key
|
|
||||||
node := new(enode.Node)
|
|
||||||
err = node.UnmarshalText(enodeKey)
|
|
||||||
if err != nil {
|
|
||||||
return record, err
|
|
||||||
}
|
|
||||||
record = PeerRecord{node: node}
|
|
||||||
if len(value) != 0 {
|
|
||||||
err = json.Unmarshal(value, &record)
|
|
||||||
}
|
|
||||||
return record, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func nodesToMap(nodes []*enode.Node) map[types.EnodeID]*enode.Node {
|
|
||||||
rst := map[types.EnodeID]*enode.Node{}
|
|
||||||
for _, n := range nodes {
|
|
||||||
rst[types.EnodeID(n.ID())] = n
|
|
||||||
}
|
|
||||||
return rst
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPeersIterator(level *leveldb.DB) iterator.Iterator {
|
|
||||||
return level.NewIterator(util.BytesPrefix([]byte{byte(db.MailserversCache)}), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyWithoutPrefix removes first byte from key.
|
|
||||||
func keyWithoutPrefix(key []byte) []byte {
|
|
||||||
return key[1:]
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newInMemCache(t *testing.T) *Cache {
|
|
||||||
db, err := leveldb.Open(storage.NewMemStorage(), nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return NewCache(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsNode(nodes []*enode.Node, node *enode.Node) error {
|
|
||||||
for _, n := range nodes {
|
|
||||||
if n.ID() == node.ID() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("nodes %+s do not contain %s", nodes, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplaceRecords(t *testing.T) {
|
|
||||||
nodesNumber := 3
|
|
||||||
cache := newInMemCache(t)
|
|
||||||
nodes := make([]*enode.Node, nodesNumber)
|
|
||||||
// First round is a sanity check that records were written.
|
|
||||||
fillWithRandomNodes(t, nodes)
|
|
||||||
require.NoError(t, cache.Replace(nodes))
|
|
||||||
records, err := cache.LoadAll()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, records, nodesNumber)
|
|
||||||
for i := range records {
|
|
||||||
require.NoError(t, containsNode(nodes, records[i].Node()))
|
|
||||||
}
|
|
||||||
// Replace all nodes and verify that length is the same and loaded records are found.
|
|
||||||
fillWithRandomNodes(t, nodes)
|
|
||||||
require.NoError(t, cache.Replace(nodes))
|
|
||||||
records, err = cache.LoadAll()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, records, nodesNumber)
|
|
||||||
for i := range records {
|
|
||||||
require.NoError(t, containsNode(nodes, records[i].Node()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsedRecord(t *testing.T) {
|
|
||||||
cache := newInMemCache(t)
|
|
||||||
node, err := RandomNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
record := PeerRecord{node: node}
|
|
||||||
require.NoError(t, cache.UpdateRecord(record))
|
|
||||||
record.LastUsed = time.Now()
|
|
||||||
require.NoError(t, cache.UpdateRecord(record))
|
|
||||||
records, err := cache.LoadAll()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, records, 1)
|
|
||||||
require.True(t, record.LastUsed.Equal(records[0].LastUsed))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimestampPreservedOnReplace(t *testing.T) {
|
|
||||||
cache := newInMemCache(t)
|
|
||||||
node, err := RandomNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
record := PeerRecord{node: node, LastUsed: time.Now()}
|
|
||||||
require.NoError(t, cache.UpdateRecord(record))
|
|
||||||
require.NoError(t, cache.Replace([]*enode.Node{node}))
|
|
||||||
records, err := cache.LoadAll()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, records, 1)
|
|
||||||
require.Equal(t, node.ID(), records[0].Node().ID())
|
|
||||||
require.False(t, records[0].LastUsed.IsZero(), "timestamp should be preserved and not equal to zero")
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,271 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
peerEventsBuffer = 10 // sufficient buffer to avoid blocking a p2p feed.
|
|
||||||
whisperEventsBuffer = 20 // sufficient buffer to avod blocking a eventSub envelopes feed.
|
|
||||||
)
|
|
||||||
|
|
||||||
// PeerAdderRemover is an interface for adding or removing peers.
|
|
||||||
type PeerAdderRemover interface {
|
|
||||||
AddPeer(node *enode.Node)
|
|
||||||
RemovePeer(node *enode.Node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeerEventsSubscriber interface to subscribe for p2p.PeerEvent's.
|
|
||||||
type PeerEventsSubscriber interface {
|
|
||||||
SubscribeEvents(chan *p2p.PeerEvent) event.Subscription
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnvelopeEventSubscriber interface to subscribe for types.EnvelopeEvent's.
|
|
||||||
type EnvelopeEventSubscriber interface {
|
|
||||||
SubscribeEnvelopeEvents(chan<- types.EnvelopeEvent) types.Subscription
|
|
||||||
}
|
|
||||||
|
|
||||||
type p2pServer interface {
|
|
||||||
PeerAdderRemover
|
|
||||||
PeerEventsSubscriber
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConnectionManager creates an instance of ConnectionManager.
|
|
||||||
func NewConnectionManager(server p2pServer, eventSub EnvelopeEventSubscriber, target, maxFailures int, timeout time.Duration) *ConnectionManager {
|
|
||||||
return &ConnectionManager{
|
|
||||||
server: server,
|
|
||||||
eventSub: eventSub,
|
|
||||||
connectedTarget: target,
|
|
||||||
maxFailures: maxFailures,
|
|
||||||
notifications: make(chan []*enode.Node),
|
|
||||||
timeoutWaitAdded: timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectionManager manages keeps target of peers connected.
|
|
||||||
type ConnectionManager struct {
|
|
||||||
wg sync.WaitGroup
|
|
||||||
quit chan struct{}
|
|
||||||
|
|
||||||
server p2pServer
|
|
||||||
eventSub EnvelopeEventSubscriber
|
|
||||||
|
|
||||||
notifications chan []*enode.Node
|
|
||||||
connectedTarget int
|
|
||||||
timeoutWaitAdded time.Duration
|
|
||||||
maxFailures int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify sends a non-blocking notification about new nodes.
|
|
||||||
func (ps *ConnectionManager) Notify(nodes []*enode.Node) {
|
|
||||||
ps.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case ps.notifications <- nodes:
|
|
||||||
case <-ps.quit:
|
|
||||||
}
|
|
||||||
ps.wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start subscribes to a p2p server and handles new peers and state updates for those peers.
|
|
||||||
func (ps *ConnectionManager) Start() {
|
|
||||||
ps.quit = make(chan struct{})
|
|
||||||
ps.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
state := newInternalState(ps.server, ps.connectedTarget, ps.timeoutWaitAdded)
|
|
||||||
events := make(chan *p2p.PeerEvent, peerEventsBuffer)
|
|
||||||
sub := ps.server.SubscribeEvents(events)
|
|
||||||
whisperEvents := make(chan types.EnvelopeEvent, whisperEventsBuffer)
|
|
||||||
whisperSub := ps.eventSub.SubscribeEnvelopeEvents(whisperEvents)
|
|
||||||
requests := map[types.Hash]struct{}{}
|
|
||||||
failuresPerServer := map[types.EnodeID]int{}
|
|
||||||
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
defer whisperSub.Unsubscribe()
|
|
||||||
defer ps.wg.Done()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ps.quit:
|
|
||||||
return
|
|
||||||
case err := <-sub.Err():
|
|
||||||
log.Error("retry after error subscribing to p2p events", "error", err)
|
|
||||||
return
|
|
||||||
case err := <-whisperSub.Err():
|
|
||||||
log.Error("retry after error suscribing to eventSub events", "error", err)
|
|
||||||
return
|
|
||||||
case newNodes := <-ps.notifications:
|
|
||||||
state.processReplacement(newNodes, events)
|
|
||||||
case ev := <-events:
|
|
||||||
processPeerEvent(state, ev)
|
|
||||||
case ev := <-whisperEvents:
|
|
||||||
// TODO treat failed requests the same way as expired
|
|
||||||
switch ev.Event {
|
|
||||||
case types.EventMailServerRequestSent:
|
|
||||||
requests[ev.Hash] = struct{}{}
|
|
||||||
case types.EventMailServerRequestCompleted:
|
|
||||||
// reset failures count on first success
|
|
||||||
failuresPerServer[ev.Peer] = 0
|
|
||||||
delete(requests, ev.Hash)
|
|
||||||
case types.EventMailServerRequestExpired:
|
|
||||||
_, exist := requests[ev.Hash]
|
|
||||||
if !exist {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
failuresPerServer[ev.Peer]++
|
|
||||||
log.Debug("request to a mail server expired, disconnect a peer", "address", ev.Peer)
|
|
||||||
if failuresPerServer[ev.Peer] >= ps.maxFailures {
|
|
||||||
state.nodeDisconnected(ev.Peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop gracefully closes all background goroutines and waits until they finish.
|
|
||||||
func (ps *ConnectionManager) Stop() {
|
|
||||||
if ps.quit == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ps.quit:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
close(ps.quit)
|
|
||||||
ps.wg.Wait()
|
|
||||||
ps.quit = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (state *internalState) processReplacement(newNodes []*enode.Node, events <-chan *p2p.PeerEvent) {
|
|
||||||
replacement := map[types.EnodeID]*enode.Node{}
|
|
||||||
for _, n := range newNodes {
|
|
||||||
replacement[types.EnodeID(n.ID())] = n
|
|
||||||
}
|
|
||||||
state.replaceNodes(replacement)
|
|
||||||
if state.ReachedTarget() {
|
|
||||||
log.Debug("already connected with required target", "target", state.target)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if state.timeout != 0 {
|
|
||||||
log.Debug("waiting defined timeout to establish connections",
|
|
||||||
"timeout", state.timeout, "target", state.target)
|
|
||||||
timer := time.NewTimer(state.timeout)
|
|
||||||
waitForConnections(state, timer.C, events)
|
|
||||||
timer.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInternalState(srv PeerAdderRemover, target int, timeout time.Duration) *internalState {
|
|
||||||
return &internalState{
|
|
||||||
options: options{target: target, timeout: timeout},
|
|
||||||
srv: srv,
|
|
||||||
connected: map[types.EnodeID]struct{}{},
|
|
||||||
currentNodes: map[types.EnodeID]*enode.Node{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type options struct {
|
|
||||||
target int
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type internalState struct {
|
|
||||||
options
|
|
||||||
srv PeerAdderRemover
|
|
||||||
|
|
||||||
connected map[types.EnodeID]struct{}
|
|
||||||
currentNodes map[types.EnodeID]*enode.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (state *internalState) ReachedTarget() bool {
|
|
||||||
return len(state.connected) >= state.target
|
|
||||||
}
|
|
||||||
|
|
||||||
func (state *internalState) replaceNodes(new map[types.EnodeID]*enode.Node) {
|
|
||||||
for nid, n := range state.currentNodes {
|
|
||||||
if _, exist := new[nid]; !exist {
|
|
||||||
delete(state.connected, nid)
|
|
||||||
state.srv.RemovePeer(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !state.ReachedTarget() {
|
|
||||||
for _, n := range new {
|
|
||||||
state.srv.AddPeer(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.currentNodes = new
|
|
||||||
}
|
|
||||||
|
|
||||||
func (state *internalState) nodeAdded(peer types.EnodeID) {
|
|
||||||
n, exist := state.currentNodes[peer]
|
|
||||||
if !exist {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if state.ReachedTarget() {
|
|
||||||
state.srv.RemovePeer(n)
|
|
||||||
} else {
|
|
||||||
state.connected[types.EnodeID(n.ID())] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (state *internalState) nodeDisconnected(peer types.EnodeID) {
|
|
||||||
n, exist := state.currentNodes[peer] // unrelated event
|
|
||||||
if !exist {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, exist = state.connected[peer] // check if already disconnected
|
|
||||||
if !exist {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(state.currentNodes) == 1 { // keep node connected if we don't have another choice
|
|
||||||
return
|
|
||||||
}
|
|
||||||
state.srv.RemovePeer(n) // remove peer permanently, otherwise p2p.Server will try to reconnect
|
|
||||||
delete(state.connected, peer)
|
|
||||||
if !state.ReachedTarget() { // try to connect with any other selected (but not connected) node
|
|
||||||
for nid, n := range state.currentNodes {
|
|
||||||
_, exist := state.connected[nid]
|
|
||||||
if exist || peer == nid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
state.srv.AddPeer(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func processPeerEvent(state *internalState, ev *p2p.PeerEvent) {
|
|
||||||
switch ev.Type {
|
|
||||||
case p2p.PeerEventTypeAdd:
|
|
||||||
log.Debug("connected to a mailserver", "address", ev.Peer)
|
|
||||||
state.nodeAdded(types.EnodeID(ev.Peer))
|
|
||||||
case p2p.PeerEventTypeDrop:
|
|
||||||
log.Debug("mailserver disconnected", "address", ev.Peer)
|
|
||||||
state.nodeDisconnected(types.EnodeID(ev.Peer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForConnections(state *internalState, timeout <-chan time.Time, events <-chan *p2p.PeerEvent) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-events:
|
|
||||||
processPeerEvent(state, ev)
|
|
||||||
if state.ReachedTarget() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-timeout:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,400 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/t/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakePeerEvents struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
nodes map[types.EnodeID]struct{}
|
|
||||||
input chan *p2p.PeerEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakePeerEvents) Nodes() []types.EnodeID {
|
|
||||||
f.mu.Lock()
|
|
||||||
rst := make([]types.EnodeID, 0, len(f.nodes))
|
|
||||||
for n := range f.nodes {
|
|
||||||
rst = append(rst, n)
|
|
||||||
}
|
|
||||||
f.mu.Unlock()
|
|
||||||
return rst
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakePeerEvents) AddPeer(node *enode.Node) {
|
|
||||||
f.mu.Lock()
|
|
||||||
f.nodes[types.EnodeID(node.ID())] = struct{}{}
|
|
||||||
f.mu.Unlock()
|
|
||||||
if f.input == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.input <- &p2p.PeerEvent{
|
|
||||||
Peer: node.ID(),
|
|
||||||
Type: p2p.PeerEventTypeAdd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakePeerEvents) RemovePeer(node *enode.Node) {
|
|
||||||
f.mu.Lock()
|
|
||||||
delete(f.nodes, types.EnodeID(node.ID()))
|
|
||||||
f.mu.Unlock()
|
|
||||||
if f.input == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.input <- &p2p.PeerEvent{
|
|
||||||
Peer: node.ID(),
|
|
||||||
Type: p2p.PeerEventTypeDrop,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakePeerAdderRemover() *fakePeerEvents {
|
|
||||||
return &fakePeerEvents{nodes: map[types.EnodeID]struct{}{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakePeerEvents) SubscribeEvents(output chan *p2p.PeerEvent) event.Subscription {
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
case ev := <-f.input:
|
|
||||||
// will block the same way as in any feed
|
|
||||||
output <- ev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeServer() *fakePeerEvents {
|
|
||||||
srv := newFakePeerAdderRemover()
|
|
||||||
srv.input = make(chan *p2p.PeerEvent, 20)
|
|
||||||
return srv
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeEnvelopeEvents struct {
|
|
||||||
input chan types.EnvelopeEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeEnvelopeEvents) SubscribeEnvelopeEvents(output chan<- types.EnvelopeEvent) types.Subscription {
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
case ev := <-f.input:
|
|
||||||
// will block the same way as in any feed
|
|
||||||
output <- ev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFakeEnvelopesEvents() *fakeEnvelopeEvents {
|
|
||||||
return &fakeEnvelopeEvents{
|
|
||||||
input: make(chan types.EnvelopeEvent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fillWithRandomNodes(t *testing.T, nodes []*enode.Node) {
|
|
||||||
var err error
|
|
||||||
for i := range nodes {
|
|
||||||
nodes[i], err = RandomNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMapWithRandomNodes(t *testing.T, n int) map[types.EnodeID]*enode.Node {
|
|
||||||
nodes := make([]*enode.Node, n)
|
|
||||||
fillWithRandomNodes(t, nodes)
|
|
||||||
return nodesToMap(nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeOldIntoNew(old, new map[types.EnodeID]*enode.Node) {
|
|
||||||
for n := range old {
|
|
||||||
new[n] = old[n]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplaceNodes(t *testing.T) {
|
|
||||||
type testCase struct {
|
|
||||||
description string
|
|
||||||
old map[types.EnodeID]*enode.Node
|
|
||||||
new map[types.EnodeID]*enode.Node
|
|
||||||
target int
|
|
||||||
}
|
|
||||||
for _, tc := range []testCase{
|
|
||||||
{
|
|
||||||
"InitialReplace",
|
|
||||||
getMapWithRandomNodes(t, 0),
|
|
||||||
getMapWithRandomNodes(t, 3),
|
|
||||||
2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"FullReplace",
|
|
||||||
getMapWithRandomNodes(t, 3),
|
|
||||||
getMapWithRandomNodes(t, 3),
|
|
||||||
2,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
|
||||||
peers := newFakePeerAdderRemover()
|
|
||||||
state := newInternalState(peers, tc.target, 0)
|
|
||||||
state.replaceNodes(tc.old)
|
|
||||||
require.Len(t, peers.nodes, len(tc.old))
|
|
||||||
for n := range peers.nodes {
|
|
||||||
require.Contains(t, tc.old, n)
|
|
||||||
}
|
|
||||||
state.replaceNodes(tc.new)
|
|
||||||
require.Len(t, peers.nodes, len(tc.new))
|
|
||||||
for n := range peers.nodes {
|
|
||||||
require.Contains(t, tc.new, n)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPartialReplaceNodesBelowTarget(t *testing.T) {
|
|
||||||
peers := newFakePeerAdderRemover()
|
|
||||||
old := getMapWithRandomNodes(t, 1)
|
|
||||||
new := getMapWithRandomNodes(t, 2)
|
|
||||||
state := newInternalState(peers, 2, 0)
|
|
||||||
state.replaceNodes(old)
|
|
||||||
mergeOldIntoNew(old, new)
|
|
||||||
state.replaceNodes(new)
|
|
||||||
require.Len(t, peers.nodes, len(new))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPartialReplaceNodesAboveTarget(t *testing.T) {
|
|
||||||
peers := newFakePeerAdderRemover()
|
|
||||||
initial, err := RandomNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
old := nodesToMap([]*enode.Node{initial})
|
|
||||||
new := getMapWithRandomNodes(t, 2)
|
|
||||||
state := newInternalState(peers, 1, 0)
|
|
||||||
state.replaceNodes(old)
|
|
||||||
state.nodeAdded(types.EnodeID(initial.ID()))
|
|
||||||
mergeOldIntoNew(old, new)
|
|
||||||
state.replaceNodes(new)
|
|
||||||
require.Len(t, peers.nodes, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConnectionManagerAddDrop(t *testing.T) {
|
|
||||||
server := newFakeServer()
|
|
||||||
whisper := newFakeEnvelopesEvents()
|
|
||||||
target := 1
|
|
||||||
connmanager := NewConnectionManager(server, whisper, target, 1, 0)
|
|
||||||
connmanager.Start()
|
|
||||||
defer connmanager.Stop()
|
|
||||||
nodes := []*enode.Node{}
|
|
||||||
for _, n := range getMapWithRandomNodes(t, 3) {
|
|
||||||
nodes = append(nodes, n)
|
|
||||||
}
|
|
||||||
// Send 3 random nodes to connection manager.
|
|
||||||
connmanager.Notify(nodes)
|
|
||||||
var initial enode.ID
|
|
||||||
// Wait till connection manager establishes connection with 1 peer.
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
nodes := server.Nodes()
|
|
||||||
if len(nodes) != target {
|
|
||||||
return fmt.Errorf("unexpected number of connected servers: %d", len(nodes))
|
|
||||||
}
|
|
||||||
initial = enode.ID(nodes[0])
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
// Send an event that peer was dropped.
|
|
||||||
select {
|
|
||||||
case server.input <- &p2p.PeerEvent{Peer: initial, Type: p2p.PeerEventTypeDrop}:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
require.FailNow(t, "can't send a drop event")
|
|
||||||
}
|
|
||||||
// Connection manager should establish connection with any other peer from initial list.
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
nodes := server.Nodes()
|
|
||||||
if len(nodes) != target {
|
|
||||||
return fmt.Errorf("unexpected number of connected servers: %d", len(nodes))
|
|
||||||
}
|
|
||||||
if enode.ID(nodes[0]) == initial {
|
|
||||||
return fmt.Errorf("connected node wasn't changed from %s", initial)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConnectionManagerReplace(t *testing.T) {
|
|
||||||
server := newFakeServer()
|
|
||||||
whisper := newFakeEnvelopesEvents()
|
|
||||||
target := 1
|
|
||||||
connmanager := NewConnectionManager(server, whisper, target, 1, 0)
|
|
||||||
connmanager.Start()
|
|
||||||
defer connmanager.Stop()
|
|
||||||
nodes := []*enode.Node{}
|
|
||||||
for _, n := range getMapWithRandomNodes(t, 3) {
|
|
||||||
nodes = append(nodes, n)
|
|
||||||
}
|
|
||||||
// Send a single node to connection manager.
|
|
||||||
connmanager.Notify(nodes[:1])
|
|
||||||
// Wait until this node will get connected.
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
connected := server.Nodes()
|
|
||||||
if len(connected) != target {
|
|
||||||
return fmt.Errorf("unexpected number of connected servers: %d", len(connected))
|
|
||||||
}
|
|
||||||
if types.EnodeID(nodes[0].ID()) != connected[0] {
|
|
||||||
return fmt.Errorf("connected with a wrong peer. expected %s, got %s", nodes[0].ID(), connected[0])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
// Replace previously sent node with 2 different nodes.
|
|
||||||
connmanager.Notify(nodes[1:])
|
|
||||||
// Wait until connection manager replaces node connected in the first round.
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
connected := server.Nodes()
|
|
||||||
if len(connected) != target {
|
|
||||||
return fmt.Errorf("unexpected number of connected servers: %d", len(connected))
|
|
||||||
}
|
|
||||||
switch enode.ID(connected[0]) {
|
|
||||||
case nodes[1].ID():
|
|
||||||
case nodes[2].ID():
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("connected with unexpected peer. got %s, expected %+v", connected[0], nodes[1:])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTestConnectionAfterExpiry(t *testing.T, server *fakePeerEvents, whisperMock *fakeEnvelopeEvents, target, maxFailures int, hash types.Hash) (*ConnectionManager, types.EnodeID) {
|
|
||||||
connmanager := NewConnectionManager(server, whisperMock, target, maxFailures, 0)
|
|
||||||
connmanager.Start()
|
|
||||||
nodes := []*enode.Node{}
|
|
||||||
for _, n := range getMapWithRandomNodes(t, 2) {
|
|
||||||
nodes = append(nodes, n)
|
|
||||||
}
|
|
||||||
// Send two random nodes to connection manager.
|
|
||||||
connmanager.Notify(nodes)
|
|
||||||
var initial types.EnodeID
|
|
||||||
// Wait until connection manager establishes connection with one node.
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
nodes := server.Nodes()
|
|
||||||
if len(nodes) != target {
|
|
||||||
return fmt.Errorf("unexpected number of connected servers: %d", len(nodes))
|
|
||||||
}
|
|
||||||
initial = nodes[0]
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
// Send event that history request for connected peer was sent.
|
|
||||||
select {
|
|
||||||
case whisperMock.input <- types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestSent, Peer: initial, Hash: hash}:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
require.FailNow(t, "can't send a 'sent' event")
|
|
||||||
}
|
|
||||||
return connmanager, initial
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConnectionChangedAfterExpiry(t *testing.T) {
|
|
||||||
server := newFakeServer()
|
|
||||||
whisperMock := newFakeEnvelopesEvents()
|
|
||||||
target := 1
|
|
||||||
maxFailures := 1
|
|
||||||
hash := types.Hash{1}
|
|
||||||
connmanager, initial := setupTestConnectionAfterExpiry(t, server, whisperMock, target, maxFailures, hash)
|
|
||||||
defer connmanager.Stop()
|
|
||||||
|
|
||||||
// And eventually expired.
|
|
||||||
select {
|
|
||||||
case whisperMock.input <- types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestExpired, Peer: initial, Hash: hash}:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
require.FailNow(t, "can't send an 'expiry' event")
|
|
||||||
}
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
nodes := server.Nodes()
|
|
||||||
if len(nodes) != target {
|
|
||||||
return fmt.Errorf("unexpected number of connected servers: %d", len(nodes))
|
|
||||||
}
|
|
||||||
if nodes[0] == initial {
|
|
||||||
return fmt.Errorf("connected node wasn't changed from %s", initial)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConnectionChangedAfterSecondExpiry(t *testing.T) {
|
|
||||||
server := newFakeServer()
|
|
||||||
whisperMock := newFakeEnvelopesEvents()
|
|
||||||
target := 1
|
|
||||||
maxFailures := 2
|
|
||||||
hash := types.Hash{1}
|
|
||||||
connmanager, initial := setupTestConnectionAfterExpiry(t, server, whisperMock, target, maxFailures, hash)
|
|
||||||
defer connmanager.Stop()
|
|
||||||
|
|
||||||
// First expired is sent. Nothing should happen.
|
|
||||||
select {
|
|
||||||
case whisperMock.input <- types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestExpired, Peer: initial, Hash: hash}:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
require.FailNow(t, "can't send an 'expiry' event")
|
|
||||||
}
|
|
||||||
|
|
||||||
// we use 'eventually' as 'consistently' because this function will retry for a given timeout while error is received
|
|
||||||
require.EqualError(t, utils.Eventually(func() error {
|
|
||||||
nodes := server.Nodes()
|
|
||||||
if len(nodes) != target {
|
|
||||||
return fmt.Errorf("unexpected number of connected servers: %d", len(nodes))
|
|
||||||
}
|
|
||||||
if nodes[0] == initial {
|
|
||||||
return fmt.Errorf("connected node wasn't changed from %s", initial)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond), fmt.Sprintf("connected node wasn't changed from %s", initial))
|
|
||||||
|
|
||||||
// second expiry event
|
|
||||||
select {
|
|
||||||
case whisperMock.input <- types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestExpired, Peer: initial, Hash: hash}:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
require.FailNow(t, "can't send an 'expiry' event")
|
|
||||||
}
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
nodes := server.Nodes()
|
|
||||||
if len(nodes) != target {
|
|
||||||
return fmt.Errorf("unexpected number of connected servers: %d", len(nodes))
|
|
||||||
}
|
|
||||||
if nodes[0] == initial {
|
|
||||||
return fmt.Errorf("connected node wasn't changed from %s", initial)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProcessReplacementWaitsForConnections(t *testing.T) {
|
|
||||||
srv := newFakePeerAdderRemover()
|
|
||||||
target := 1
|
|
||||||
timeout := time.Second
|
|
||||||
nodes := make([]*enode.Node, 2)
|
|
||||||
fillWithRandomNodes(t, nodes)
|
|
||||||
events := make(chan *p2p.PeerEvent)
|
|
||||||
state := newInternalState(srv, target, timeout)
|
|
||||||
state.currentNodes = nodesToMap(nodes)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case events <- &p2p.PeerEvent{Peer: nodes[0].ID(), Type: p2p.PeerEventTypeAdd}:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
assert.FailNow(t, "can't send a drop event")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
state.processReplacement(nodes, events)
|
|
||||||
require.Len(t, state.connected, 1)
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewLastUsedConnectionMonitor returns pointer to the instance of LastUsedConnectionMonitor.
|
|
||||||
func NewLastUsedConnectionMonitor(ps *PeerStore, cache *Cache, eventSub EnvelopeEventSubscriber) *LastUsedConnectionMonitor {
|
|
||||||
return &LastUsedConnectionMonitor{
|
|
||||||
ps: ps,
|
|
||||||
cache: cache,
|
|
||||||
eventSub: eventSub,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastUsedConnectionMonitor watches relevant events and reflects it in cache.
|
|
||||||
type LastUsedConnectionMonitor struct {
|
|
||||||
ps *PeerStore
|
|
||||||
cache *Cache
|
|
||||||
|
|
||||||
eventSub EnvelopeEventSubscriber
|
|
||||||
|
|
||||||
quit chan struct{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start spins a separate goroutine to watch connections.
|
|
||||||
func (mon *LastUsedConnectionMonitor) Start() {
|
|
||||||
mon.quit = make(chan struct{})
|
|
||||||
mon.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
events := make(chan types.EnvelopeEvent, whisperEventsBuffer)
|
|
||||||
sub := mon.eventSub.SubscribeEnvelopeEvents(events)
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
defer mon.wg.Done()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-mon.quit:
|
|
||||||
return
|
|
||||||
case err := <-sub.Err():
|
|
||||||
log.Error("retry after error suscribing to eventSub events", "error", err)
|
|
||||||
return
|
|
||||||
case ev := <-events:
|
|
||||||
node := mon.ps.Get(ev.Peer)
|
|
||||||
if node == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ev.Event == types.EventMailServerRequestCompleted {
|
|
||||||
err := mon.updateRecord(ev.Peer)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("unable to update storage", "peer", ev.Peer, "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mon *LastUsedConnectionMonitor) updateRecord(nodeID types.EnodeID) error {
|
|
||||||
node := mon.ps.Get(nodeID)
|
|
||||||
if node == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return mon.cache.UpdateRecord(PeerRecord{node: node, LastUsed: time.Now()})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop closes channel to signal a quit and waits until all goroutines are stoppped.
|
|
||||||
func (mon *LastUsedConnectionMonitor) Stop() {
|
|
||||||
if mon.quit == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-mon.quit:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
close(mon.quit)
|
|
||||||
mon.wg.Wait()
|
|
||||||
mon.quit = nil
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/t/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUsedConnectionPersisted(t *testing.T) {
|
|
||||||
nodes := make([]*enode.Node, 2)
|
|
||||||
fillWithRandomNodes(t, nodes)
|
|
||||||
|
|
||||||
cache := newInMemCache(t)
|
|
||||||
store := NewPeerStore(cache)
|
|
||||||
require.NoError(t, store.Update(nodes))
|
|
||||||
whisperMock := newFakeEnvelopesEvents()
|
|
||||||
monitor := NewLastUsedConnectionMonitor(store, cache, whisperMock)
|
|
||||||
monitor.Start()
|
|
||||||
|
|
||||||
// Send a confirmation that we received history from one of the peers.
|
|
||||||
select {
|
|
||||||
case whisperMock.input <- types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestCompleted, Peer: types.EnodeID(nodes[0].ID())}:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
require.FailNow(t, "can't send a 'completed' event")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until records will be updated in the cache.
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
records, err := cache.LoadAll()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if lth := len(records); lth != 2 {
|
|
||||||
return fmt.Errorf("unexpected length of all records stored in the cache. expected %d got %d", 2, lth)
|
|
||||||
}
|
|
||||||
var used bool
|
|
||||||
for _, r := range records {
|
|
||||||
if r.Node().ID() == nodes[0].ID() {
|
|
||||||
used = !r.LastUsed.IsZero()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !used {
|
|
||||||
return fmt.Errorf("record %s is not marked as used", types.EnodeID(nodes[0].ID()))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
|
|
||||||
// Use different peer, first will be marked as unused.
|
|
||||||
select {
|
|
||||||
case whisperMock.input <- types.EnvelopeEvent{
|
|
||||||
Event: types.EventMailServerRequestCompleted, Peer: types.EnodeID(nodes[1].ID())}:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
require.FailNow(t, "can't send a 'completed' event")
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
|
||||||
records, err := cache.LoadAll()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if lth := len(records); lth != 2 {
|
|
||||||
return fmt.Errorf("unexpected length of all records stored in the cache. expected %d got %d", 2, lth)
|
|
||||||
}
|
|
||||||
sort.Slice(records, func(i, j int) bool {
|
|
||||||
return records[i].LastUsed.After(records[j].LastUsed)
|
|
||||||
})
|
|
||||||
if records[0].Node().ID() != nodes[1].ID() {
|
|
||||||
return fmt.Errorf("record wasn't updated after previous event")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, time.Second, 100*time.Millisecond))
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNoConnected returned when mail servers are not connected.
|
|
||||||
ErrNoConnected = errors.New("no connected mail servers")
|
|
||||||
)
|
|
||||||
|
|
||||||
// PeersProvider is an interface for requesting list of peers.
|
|
||||||
type PeersProvider interface {
|
|
||||||
Peers() []*p2p.Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPeerStore returns an instance of PeerStore.
|
|
||||||
func NewPeerStore(cache *Cache) *PeerStore {
|
|
||||||
return &PeerStore{
|
|
||||||
nodes: map[types.EnodeID]*enode.Node{},
|
|
||||||
cache: cache,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeerStore stores list of selected mail servers and keeps N of them connected.
|
|
||||||
type PeerStore struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
nodes map[types.EnodeID]*enode.Node
|
|
||||||
|
|
||||||
cache *Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exist confirms that peers was added to a store.
|
|
||||||
func (ps *PeerStore) Exist(nodeID types.EnodeID) bool {
|
|
||||||
ps.mu.RLock()
|
|
||||||
defer ps.mu.RUnlock()
|
|
||||||
_, exist := ps.nodes[nodeID]
|
|
||||||
return exist
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns instance of the node with requested ID or nil if ID is not found.
|
|
||||||
func (ps *PeerStore) Get(nodeID types.EnodeID) *enode.Node {
|
|
||||||
ps.mu.RLock()
|
|
||||||
defer ps.mu.RUnlock()
|
|
||||||
return ps.nodes[nodeID]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates peers locally.
|
|
||||||
func (ps *PeerStore) Update(nodes []*enode.Node) error {
|
|
||||||
ps.mu.Lock()
|
|
||||||
ps.nodes = map[types.EnodeID]*enode.Node{}
|
|
||||||
for _, n := range nodes {
|
|
||||||
ps.nodes[types.EnodeID(n.ID())] = n
|
|
||||||
}
|
|
||||||
ps.mu.Unlock()
|
|
||||||
return ps.cache.Replace(nodes)
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RandomNode() (*enode.Node, error) {
|
|
||||||
pkey, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return enode.NewV4(&pkey.PublicKey, nil, 0, 0), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateResetsInternalStorage(t *testing.T) {
|
|
||||||
store := NewPeerStore(newInMemCache(t))
|
|
||||||
r1, err := RandomNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
r2, err := RandomNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, store.Update([]*enode.Node{r1, r2}))
|
|
||||||
require.True(t, store.Exist(types.EnodeID(r1.ID())))
|
|
||||||
require.True(t, store.Exist(types.EnodeID(r2.ID())))
|
|
||||||
require.NoError(t, store.Update([]*enode.Node{r2}))
|
|
||||||
require.False(t, store.Exist(types.EnodeID(r1.ID())))
|
|
||||||
require.True(t, store.Exist(types.EnodeID(r2.ID())))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetNodeByID(t *testing.T) {
|
|
||||||
store := NewPeerStore(newInMemCache(t))
|
|
||||||
r1, err := RandomNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, store.Update([]*enode.Node{r1}))
|
|
||||||
require.Equal(t, r1, store.Get(types.EnodeID(r1.ID())))
|
|
||||||
require.Nil(t, store.Get(types.EnodeID{1}))
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakePeerProvider struct {
|
|
||||||
peers []*p2p.Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fakePeerProvider) Peers() []*p2p.Peer {
|
|
||||||
return f.peers
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoConnected(t *testing.T) {
|
|
||||||
provider := fakePeerProvider{}
|
|
||||||
store := NewPeerStore(newInMemCache(t))
|
|
||||||
_, err := GetFirstConnected(provider, store)
|
|
||||||
require.EqualError(t, ErrNoConnected, err.Error())
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetFirstConnected returns first connected peer that is also added to a peer store.
|
|
||||||
// Raises ErrNoConnected if no peers are added to a peer store.
|
|
||||||
func GetFirstConnected(provider PeersProvider, store *PeerStore) (*enode.Node, error) {
|
|
||||||
peers := provider.Peers()
|
|
||||||
for _, p := range peers {
|
|
||||||
if store.Exist(types.EnodeID(p.ID())) {
|
|
||||||
return p.Node(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, ErrNoConnected
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodesNotifee interface to be notified when new nodes are received.
|
|
||||||
type NodesNotifee interface {
|
|
||||||
Notify([]*enode.Node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnsureUsedRecordsAddedFirst checks if any nodes were marked as connected before app went offline.
|
|
||||||
func EnsureUsedRecordsAddedFirst(ps *PeerStore, conn NodesNotifee) error {
|
|
||||||
records, err := ps.cache.LoadAll()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(records) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sort.Slice(records, func(i, j int) bool {
|
|
||||||
return records[i].LastUsed.After(records[j].LastUsed)
|
|
||||||
})
|
|
||||||
all := recordsToNodes(records)
|
|
||||||
if !records[0].LastUsed.IsZero() {
|
|
||||||
conn.Notify(all[:1])
|
|
||||||
}
|
|
||||||
conn.Notify(all)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func recordsToNodes(records []PeerRecord) []*enode.Node {
|
|
||||||
nodes := make([]*enode.Node, len(records))
|
|
||||||
for i := range records {
|
|
||||||
nodes[i] = records[i].Node()
|
|
||||||
}
|
|
||||||
return nodes
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package mailservers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetFirstConnected(t *testing.T) {
|
|
||||||
numPeers := 3
|
|
||||||
nodes := make([]*enode.Node, numPeers)
|
|
||||||
peers := make([]*p2p.Peer, numPeers)
|
|
||||||
nodesMap := getMapWithRandomNodes(t, numPeers)
|
|
||||||
i := 0
|
|
||||||
for _, node := range nodesMap {
|
|
||||||
nodes[i] = node
|
|
||||||
peers[i] = p2p.NewPeer(node.ID(), node.ID().String(), nil)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
store := NewPeerStore(newInMemCache(t))
|
|
||||||
provider := fakePeerProvider{peers}
|
|
||||||
_, err := GetFirstConnected(provider, store)
|
|
||||||
require.EqualError(t, ErrNoConnected, err.Error())
|
|
||||||
require.NoError(t, store.Update(nodes))
|
|
||||||
node, err := GetFirstConnected(provider, store)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Contains(t, nodesMap, types.EnodeID(node.ID()))
|
|
||||||
}
|
|
||||||
|
|
||||||
type trackingNodeNotifee struct {
|
|
||||||
calls [][]*enode.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *trackingNodeNotifee) Notify(nodes []*enode.Node) {
|
|
||||||
t.calls = append(t.calls, nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnsureNewRecordsAddedFirst(t *testing.T) {
|
|
||||||
notifee := new(trackingNodeNotifee)
|
|
||||||
store := NewPeerStore(newInMemCache(t))
|
|
||||||
nodes := make([]*enode.Node, 3)
|
|
||||||
fillWithRandomNodes(t, nodes)
|
|
||||||
require.NoError(t, store.Update(nodes))
|
|
||||||
record := NewPeerRecord(nodes[0])
|
|
||||||
record.LastUsed = time.Now()
|
|
||||||
require.NoError(t, store.cache.UpdateRecord(record))
|
|
||||||
require.NoError(t, EnsureUsedRecordsAddedFirst(store, notifee))
|
|
||||||
require.Len(t, notifee.calls, 2)
|
|
||||||
require.Len(t, notifee.calls[0], 1)
|
|
||||||
require.Equal(t, nodes[0].ID(), notifee.calls[0][0].ID())
|
|
||||||
require.Len(t, notifee.calls[1], 3)
|
|
||||||
}
|
|
|
@ -49,7 +49,6 @@ import (
|
||||||
"github.com/status-im/status-go/server"
|
"github.com/status-im/status-go/server"
|
||||||
"github.com/status-im/status-go/services/browsers"
|
"github.com/status-im/status-go/services/browsers"
|
||||||
"github.com/status-im/status-go/services/communitytokens"
|
"github.com/status-im/status-go/services/communitytokens"
|
||||||
"github.com/status-im/status-go/services/ext/mailservers"
|
|
||||||
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
||||||
"github.com/status-im/status-go/services/wallet"
|
"github.com/status-im/status-go/services/wallet"
|
||||||
"github.com/status-im/status-go/services/wallet/collectibles"
|
"github.com/status-im/status-go/services/wallet/collectibles"
|
||||||
|
@ -65,8 +64,6 @@ const providerID = "community"
|
||||||
type EnvelopeEventsHandler interface {
|
type EnvelopeEventsHandler interface {
|
||||||
EnvelopeSent([][]byte)
|
EnvelopeSent([][]byte)
|
||||||
EnvelopeExpired([][]byte, error)
|
EnvelopeExpired([][]byte, error)
|
||||||
MailServerRequestCompleted(types.Hash, types.Hash, []byte, error)
|
|
||||||
MailServerRequestExpired(types.Hash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service is a service that provides some additional API to whisper-based protocols like Whisper or Waku.
|
// Service is a service that provides some additional API to whisper-based protocols like Whisper or Waku.
|
||||||
|
@ -78,9 +75,7 @@ type Service struct {
|
||||||
n types.Node
|
n types.Node
|
||||||
rpcClient *rpc.Client
|
rpcClient *rpc.Client
|
||||||
config params.NodeConfig
|
config params.NodeConfig
|
||||||
mailMonitor *MailRequestMonitor
|
|
||||||
server *p2p.Server
|
server *p2p.Server
|
||||||
peerStore *mailservers.PeerStore
|
|
||||||
accountsDB *accounts.Database
|
accountsDB *accounts.Database
|
||||||
multiAccountsDB *multiaccounts.Database
|
multiAccountsDB *multiaccounts.Database
|
||||||
account *multiaccounts.Account
|
account *multiaccounts.Account
|
||||||
|
@ -94,18 +89,12 @@ func New(
|
||||||
n types.Node,
|
n types.Node,
|
||||||
rpcClient *rpc.Client,
|
rpcClient *rpc.Client,
|
||||||
ldb *leveldb.DB,
|
ldb *leveldb.DB,
|
||||||
mailMonitor *MailRequestMonitor,
|
|
||||||
eventSub mailservers.EnvelopeEventSubscriber,
|
|
||||||
) *Service {
|
) *Service {
|
||||||
cache := mailservers.NewCache(ldb)
|
|
||||||
peerStore := mailservers.NewPeerStore(cache)
|
|
||||||
return &Service{
|
return &Service{
|
||||||
storage: db.NewLevelDBStorage(ldb),
|
storage: db.NewLevelDBStorage(ldb),
|
||||||
n: n,
|
n: n,
|
||||||
rpcClient: rpcClient,
|
rpcClient: rpcClient,
|
||||||
config: config,
|
config: config,
|
||||||
mailMonitor: mailMonitor,
|
|
||||||
peerStore: peerStore,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,9 +106,6 @@ func (s *Service) NodeID() *ecdsa.PrivateKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetPeer(rawURL string) (*enode.Node, error) {
|
func (s *Service) GetPeer(rawURL string) (*enode.Node, error) {
|
||||||
if len(rawURL) == 0 {
|
|
||||||
return mailservers.GetFirstConnected(s.server, s.peerStore)
|
|
||||||
}
|
|
||||||
return enode.ParseV4(rawURL)
|
return enode.ParseV4(rawURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,9 +136,6 @@ func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, appD
|
||||||
envelopesMonitorConfig := &transport.EnvelopesMonitorConfig{
|
envelopesMonitorConfig := &transport.EnvelopesMonitorConfig{
|
||||||
MaxAttempts: s.config.ShhextConfig.MaxMessageDeliveryAttempts,
|
MaxAttempts: s.config.ShhextConfig.MaxMessageDeliveryAttempts,
|
||||||
AwaitOnlyMailServerConfirmations: s.config.ShhextConfig.MailServerConfirmations,
|
AwaitOnlyMailServerConfirmations: s.config.ShhextConfig.MailServerConfirmations,
|
||||||
IsMailserver: func(peer types.EnodeID) bool {
|
|
||||||
return s.peerStore.Exist(peer)
|
|
||||||
},
|
|
||||||
EnvelopeEventsHandler: EnvelopeSignalHandler{},
|
EnvelopeEventsHandler: EnvelopeSignalHandler{},
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
}
|
}
|
||||||
|
@ -173,7 +156,6 @@ func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, appD
|
||||||
identity,
|
identity,
|
||||||
s.n,
|
s.n,
|
||||||
s.config.ShhextConfig.InstallationID,
|
s.config.ShhextConfig.InstallationID,
|
||||||
s.peerStore,
|
|
||||||
params.Version,
|
params.Version,
|
||||||
options...,
|
options...,
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,16 +22,6 @@ func (h EnvelopeSignalHandler) EnvelopeExpired(identifiers [][]byte, err error)
|
||||||
signal.SendEnvelopeExpired(identifiers, err)
|
signal.SendEnvelopeExpired(identifiers, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailServerRequestCompleted triggered when the mailserver sends a message to notify that the request has been completed
|
|
||||||
func (h EnvelopeSignalHandler) MailServerRequestCompleted(requestID types.Hash, lastEnvelopeHash types.Hash, cursor []byte, err error) {
|
|
||||||
signal.SendMailServerRequestCompleted(requestID, lastEnvelopeHash, cursor, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MailServerRequestExpired triggered when the mailserver request expires
|
|
||||||
func (h EnvelopeSignalHandler) MailServerRequestExpired(hash types.Hash) {
|
|
||||||
signal.SendMailServerRequestExpired(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublisherSignalHandler sends signals on protocol events
|
// PublisherSignalHandler sends signals on protocol events
|
||||||
type PublisherSignalHandler struct{}
|
type PublisherSignalHandler struct{}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ type PublicAPI struct {
|
||||||
// NewPublicAPI returns instance of the public API.
|
// NewPublicAPI returns instance of the public API.
|
||||||
func NewPublicAPI(s *Service) *PublicAPI {
|
func NewPublicAPI(s *Service) *PublicAPI {
|
||||||
return &PublicAPI{
|
return &PublicAPI{
|
||||||
PublicAPI: ext.NewPublicAPI(s.Service, s.w),
|
PublicAPI: ext.NewPublicAPI(s.Service),
|
||||||
service: s,
|
service: s,
|
||||||
publicAPI: s.w.PublicWakuAPI(),
|
publicAPI: s.w.PublicWakuAPI(),
|
||||||
log: log.New("package", "status-go/services/wakuext.PublicAPI"),
|
log: log.New("package", "status-go/services/wakuext.PublicAPI"),
|
||||||
|
|
|
@ -21,14 +21,8 @@ func New(config params.NodeConfig, n types.Node, rpcClient *rpc.Client, handler
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
delay := ext.DefaultRequestsDelay
|
|
||||||
if config.ShhextConfig.RequestsDelay != 0 {
|
|
||||||
delay = config.ShhextConfig.RequestsDelay
|
|
||||||
}
|
|
||||||
requestsRegistry := ext.NewRequestsRegistry(delay)
|
|
||||||
mailMonitor := ext.NewMailRequestMonitor(w, handler, requestsRegistry)
|
|
||||||
return &Service{
|
return &Service{
|
||||||
Service: ext.New(config, n, rpcClient, ldb, mailMonitor, w),
|
Service: ext.New(config, n, rpcClient, ldb),
|
||||||
w: w,
|
w: w,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ type PublicAPI struct {
|
||||||
// NewPublicAPI returns instance of the public API.
|
// NewPublicAPI returns instance of the public API.
|
||||||
func NewPublicAPI(s *Service) *PublicAPI {
|
func NewPublicAPI(s *Service) *PublicAPI {
|
||||||
return &PublicAPI{
|
return &PublicAPI{
|
||||||
PublicAPI: ext.NewPublicAPI(s.Service, s.w),
|
PublicAPI: ext.NewPublicAPI(s.Service),
|
||||||
service: s,
|
service: s,
|
||||||
publicAPI: s.w.PublicWakuAPI(),
|
publicAPI: s.w.PublicWakuAPI(),
|
||||||
log: log.New("package", "status-go/services/wakuext.PublicAPI"),
|
log: log.New("package", "status-go/services/wakuext.PublicAPI"),
|
||||||
|
|
|
@ -20,14 +20,8 @@ func New(config params.NodeConfig, n types.Node, rpcClient *rpc.Client, handler
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
delay := ext.DefaultRequestsDelay
|
|
||||||
if config.ShhextConfig.RequestsDelay != 0 {
|
|
||||||
delay = config.ShhextConfig.RequestsDelay
|
|
||||||
}
|
|
||||||
requestsRegistry := ext.NewRequestsRegistry(delay)
|
|
||||||
mailMonitor := ext.NewMailRequestMonitor(w, handler, requestsRegistry)
|
|
||||||
return &Service{
|
return &Service{
|
||||||
Service: ext.New(config, n, rpcClient, ldb, mailMonitor, w),
|
Service: ext.New(config, n, rpcClient, ldb),
|
||||||
w: w,
|
w: w,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,6 @@ const (
|
||||||
// to any peer
|
// to any peer
|
||||||
EventEnvelopeExpired = "envelope.expired"
|
EventEnvelopeExpired = "envelope.expired"
|
||||||
|
|
||||||
// EventMailServerRequestCompleted is triggered when whisper receives a message ack from the mailserver
|
|
||||||
EventMailServerRequestCompleted = "mailserver.request.completed"
|
|
||||||
|
|
||||||
// EventMailServerRequestExpired is triggered when request TTL ends
|
|
||||||
EventMailServerRequestExpired = "mailserver.request.expired"
|
|
||||||
|
|
||||||
// EventEnodeDiscovered is tiggered when enode has been discovered.
|
// EventEnodeDiscovered is tiggered when enode has been discovered.
|
||||||
EventEnodeDiscovered = "enode.discovered"
|
EventEnodeDiscovered = "enode.discovered"
|
||||||
|
|
||||||
|
@ -165,26 +159,6 @@ func SendUpdateAvailable(available bool, latestVersion string, url string) {
|
||||||
send(EventUpdateAvailable, UpdateAvailableSignal{Available: available, Version: latestVersion, URL: url})
|
send(EventUpdateAvailable, UpdateAvailableSignal{Available: available, Version: latestVersion, URL: url})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMailServerRequestCompleted triggered when mail server response has been received
|
|
||||||
func SendMailServerRequestCompleted(requestID types.Hash, lastEnvelopeHash types.Hash, cursor []byte, err error) {
|
|
||||||
errorMsg := ""
|
|
||||||
if err != nil {
|
|
||||||
errorMsg = err.Error()
|
|
||||||
}
|
|
||||||
sig := MailServerResponseSignal{
|
|
||||||
RequestID: requestID,
|
|
||||||
LastEnvelopeHash: lastEnvelopeHash,
|
|
||||||
Cursor: hex.EncodeToString(cursor),
|
|
||||||
ErrorMsg: errorMsg,
|
|
||||||
}
|
|
||||||
send(EventMailServerRequestCompleted, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMailServerRequestExpired triggered when mail server request expires
|
|
||||||
func SendMailServerRequestExpired(hash types.Hash) {
|
|
||||||
send(EventMailServerRequestExpired, EnvelopeSignal{Hash: hash})
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnodeDiscoveredSignal includes enode address and topic
|
// EnodeDiscoveredSignal includes enode address and topic
|
||||||
type EnodeDiscoveredSignal struct {
|
type EnodeDiscoveredSignal struct {
|
||||||
Enode string `json:"enode"`
|
Enode string `json:"enode"`
|
||||||
|
|
|
@ -43,23 +43,6 @@ const (
|
||||||
|
|
||||||
// EventEnvelopeAvailable fires when envelop is available for filters
|
// EventEnvelopeAvailable fires when envelop is available for filters
|
||||||
EventEnvelopeAvailable EventType = "envelope.available"
|
EventEnvelopeAvailable EventType = "envelope.available"
|
||||||
|
|
||||||
// EventMailServerRequestSent fires when such request is sent.
|
|
||||||
EventMailServerRequestSent EventType = "mailserver.request.sent"
|
|
||||||
|
|
||||||
// EventMailServerRequestCompleted fires after mailserver sends all the requested messages
|
|
||||||
EventMailServerRequestCompleted EventType = "mailserver.request.completed"
|
|
||||||
|
|
||||||
// EventMailServerRequestExpired fires after mailserver the request TTL ends.
|
|
||||||
// This event is independent and concurrent to EventMailServerRequestCompleted.
|
|
||||||
// Request should be considered as expired only if expiry event was received first.
|
|
||||||
EventMailServerRequestExpired EventType = "mailserver.request.expired"
|
|
||||||
|
|
||||||
// EventMailServerEnvelopeArchived fires after an envelope has been archived
|
|
||||||
EventMailServerEnvelopeArchived EventType = "mailserver.envelope.archived"
|
|
||||||
|
|
||||||
// EventMailServerSyncFinished fires when the sync of messages is finished.
|
|
||||||
EventMailServerSyncFinished EventType = "mailserver.sync.finished"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnvelopeEvent represents an envelope event.
|
// EnvelopeEvent represents an envelope event.
|
||||||
|
|
|
@ -31,8 +31,6 @@ type Peer interface {
|
||||||
// SetRWWriter sets the socket to read/write
|
// SetRWWriter sets the socket to read/write
|
||||||
SetRWWriter(p2p.MsgReadWriter)
|
SetRWWriter(p2p.MsgReadWriter)
|
||||||
|
|
||||||
RequestHistoricMessages(*Envelope) error
|
|
||||||
SendHistoricMessageResponse([]byte) error
|
|
||||||
SendP2PMessages([]*Envelope) error
|
SendP2PMessages([]*Envelope) error
|
||||||
SendRawP2PDirect([]rlp.RawValue) error
|
SendRawP2PDirect([]rlp.RawValue) error
|
||||||
|
|
||||||
|
@ -61,8 +59,6 @@ type WakuHost interface {
|
||||||
MaxMessageSize() uint32
|
MaxMessageSize() uint32
|
||||||
// LightClientMode returns whether the host is running in light client mode
|
// LightClientMode returns whether the host is running in light client mode
|
||||||
LightClientMode() bool
|
LightClientMode() bool
|
||||||
// Mailserver returns whether the host is running a mailserver
|
|
||||||
Mailserver() bool
|
|
||||||
// LightClientModeConnectionRestricted indicates that connection to light client in light client mode not allowed
|
// LightClientModeConnectionRestricted indicates that connection to light client in light client mode not allowed
|
||||||
LightClientModeConnectionRestricted() bool
|
LightClientModeConnectionRestricted() bool
|
||||||
// ConfirmationsEnabled returns true if message confirmations are enabled.
|
// ConfirmationsEnabled returns true if message confirmations are enabled.
|
||||||
|
@ -92,14 +88,5 @@ type WakuHost interface {
|
||||||
// OnMessagesResponse handles when the peer receive a message response
|
// OnMessagesResponse handles when the peer receive a message response
|
||||||
// from a mailserver
|
// from a mailserver
|
||||||
OnMessagesResponse(MessagesResponse, Peer) error
|
OnMessagesResponse(MessagesResponse, Peer) error
|
||||||
// OnMessagesRequest handles when the peer receive a message request
|
|
||||||
// this only works if the peer is a mailserver
|
|
||||||
OnMessagesRequest(MessagesRequest, Peer) error
|
|
||||||
// OnDeprecatedMessagesRequest handles when the peer receive a message request
|
|
||||||
// using the *Envelope format. Currently the only production client (status-mobile)
|
|
||||||
// is exclusively using this one.
|
|
||||||
OnDeprecatedMessagesRequest(*Envelope, Peer) error
|
|
||||||
|
|
||||||
OnBatchAcknowledged(common.Hash, Peer) error
|
OnBatchAcknowledged(common.Hash, Peer) error
|
||||||
OnP2PRequestCompleted([]byte, Peer) error
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
// Copyright 2019 The Waku Library Authors.
|
|
||||||
//
|
|
||||||
// The Waku library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The Waku library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty off
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the Waku library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
//
|
|
||||||
// This software uses the go-ethereum library, which is licensed
|
|
||||||
// under the GNU Lesser General Public Library, version 3 or any later.
|
|
||||||
|
|
||||||
package waku
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/waku/common"
|
|
||||||
|
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
mailServerFailedPayloadPrefix = "ERROR="
|
|
||||||
cursorSize = 36
|
|
||||||
)
|
|
||||||
|
|
||||||
// MailServer represents a mail server, capable of
|
|
||||||
// archiving the old messages for subsequent delivery
|
|
||||||
// to the peers. Any implementation must ensure that both
|
|
||||||
// functions are thread-safe. Also, they must return ASAP.
|
|
||||||
// DeliverMail should use p2pMessageCode for delivery,
|
|
||||||
// in order to bypass the expiry checks.
|
|
||||||
type MailServer interface {
|
|
||||||
Archive(env *common.Envelope)
|
|
||||||
DeliverMail(peerID []byte, request *common.Envelope) // DEPRECATED; use Deliver()
|
|
||||||
Deliver(peerID []byte, request common.MessagesRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MailServerResponse is the response payload sent by the mailserver.
|
|
||||||
type MailServerResponse struct {
|
|
||||||
LastEnvelopeHash gethcommon.Hash
|
|
||||||
Cursor []byte
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
func invalidResponseSizeError(size int) error {
|
|
||||||
return fmt.Errorf("unexpected payload size: %d", size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMailServerRequestCompletedPayload creates a payload representing
|
|
||||||
// a successful request to mailserver
|
|
||||||
func CreateMailServerRequestCompletedPayload(requestID, lastEnvelopeHash gethcommon.Hash, cursor []byte) []byte {
|
|
||||||
payload := make([]byte, len(requestID))
|
|
||||||
copy(payload, requestID[:])
|
|
||||||
payload = append(payload, lastEnvelopeHash[:]...)
|
|
||||||
payload = append(payload, cursor...)
|
|
||||||
return payload
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMailServerRequestFailedPayload creates a payload representing
|
|
||||||
// a failed request to a mailserver
|
|
||||||
func CreateMailServerRequestFailedPayload(requestID gethcommon.Hash, err error) []byte {
|
|
||||||
payload := []byte(mailServerFailedPayloadPrefix)
|
|
||||||
payload = append(payload, requestID[:]...)
|
|
||||||
payload = append(payload, []byte(err.Error())...)
|
|
||||||
return payload
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMailServerEvent returns EnvelopeEvent with correct data
|
|
||||||
// if payload corresponds to any of the know mailserver events:
|
|
||||||
// * request completed successfully
|
|
||||||
// * request failed
|
|
||||||
// If the payload is unknown/unparseable, it returns `nil`
|
|
||||||
func CreateMailServerEvent(nodeID enode.ID, payload []byte) (*common.EnvelopeEvent, error) {
|
|
||||||
if len(payload) < gethcommon.HashLength {
|
|
||||||
return nil, invalidResponseSizeError(len(payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
event, err := tryCreateMailServerRequestFailedEvent(nodeID, payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if event != nil {
|
|
||||||
return event, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return tryCreateMailServerRequestCompletedEvent(nodeID, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryCreateMailServerRequestFailedEvent(nodeID enode.ID, payload []byte) (*common.EnvelopeEvent, error) {
|
|
||||||
if len(payload) < gethcommon.HashLength+len(mailServerFailedPayloadPrefix) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix, remainder := extractPrefix(payload, len(mailServerFailedPayloadPrefix))
|
|
||||||
|
|
||||||
if !bytes.Equal(prefix, []byte(mailServerFailedPayloadPrefix)) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
requestID gethcommon.Hash
|
|
||||||
errorMsg string
|
|
||||||
)
|
|
||||||
|
|
||||||
requestID, remainder = extractHash(remainder)
|
|
||||||
errorMsg = string(remainder)
|
|
||||||
|
|
||||||
event := common.EnvelopeEvent{
|
|
||||||
Peer: nodeID,
|
|
||||||
Hash: requestID,
|
|
||||||
Event: common.EventMailServerRequestCompleted,
|
|
||||||
Data: &MailServerResponse{
|
|
||||||
Error: errors.New(errorMsg),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &event, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryCreateMailServerRequestCompletedEvent(nodeID enode.ID, payload []byte) (*common.EnvelopeEvent, error) {
|
|
||||||
// check if payload is
|
|
||||||
// - requestID or
|
|
||||||
// - requestID + lastEnvelopeHash or
|
|
||||||
// - requestID + lastEnvelopeHash + cursor
|
|
||||||
// requestID is the hash of the request envelope.
|
|
||||||
// lastEnvelopeHash is the last envelope sent by the mail server
|
|
||||||
// cursor is the db key, 36 bytes: 4 for the timestamp + 32 for the envelope hash.
|
|
||||||
if len(payload) > gethcommon.HashLength*2+cursorSize {
|
|
||||||
return nil, invalidResponseSizeError(len(payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
requestID gethcommon.Hash
|
|
||||||
lastEnvelopeHash gethcommon.Hash
|
|
||||||
cursor []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
requestID, remainder := extractHash(payload)
|
|
||||||
|
|
||||||
if len(remainder) >= gethcommon.HashLength {
|
|
||||||
lastEnvelopeHash, remainder = extractHash(remainder)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(remainder) >= cursorSize {
|
|
||||||
cursor = remainder
|
|
||||||
}
|
|
||||||
|
|
||||||
event := common.EnvelopeEvent{
|
|
||||||
Peer: nodeID,
|
|
||||||
Hash: requestID,
|
|
||||||
Event: common.EventMailServerRequestCompleted,
|
|
||||||
Data: &MailServerResponse{
|
|
||||||
LastEnvelopeHash: lastEnvelopeHash,
|
|
||||||
Cursor: cursor,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &event, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractHash(payload []byte) (gethcommon.Hash, []byte) {
|
|
||||||
prefix, remainder := extractPrefix(payload, gethcommon.HashLength)
|
|
||||||
return gethcommon.BytesToHash(prefix), remainder
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractPrefix(payload []byte, size int) ([]byte, []byte) {
|
|
||||||
return payload[:size], payload[size:]
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
@ -227,16 +226,6 @@ func (p *Peer) handlePacket(packet p2p.Msg) error {
|
||||||
p.logger.Warn("failed to decode direct message, peer will be disconnected", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(err))
|
p.logger.Warn("failed to decode direct message, peer will be disconnected", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case p2pRequestCode:
|
|
||||||
if err := p.handleP2PRequestCode(packet); err != nil {
|
|
||||||
p.logger.Warn("failed to decode p2p request message, peer will be disconnected", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case p2pRequestCompleteCode:
|
|
||||||
if err := p.handleP2PRequestCompleteCode(packet); err != nil {
|
|
||||||
p.logger.Warn("failed to decode p2p request complete message, peer will be disconnected", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
// New message common might be implemented in the future versions of Waku.
|
// New message common might be implemented in the future versions of Waku.
|
||||||
// For forward compatibility, just ignore.
|
// For forward compatibility, just ignore.
|
||||||
|
@ -289,43 +278,6 @@ func (p *Peer) handleMessageResponseCode(packet p2p.Msg) error {
|
||||||
return p.host.OnMessagesResponse(response, p)
|
return p.host.OnMessagesResponse(response, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) handleP2PRequestCode(packet p2p.Msg) error {
|
|
||||||
// Must be processed if mail server is implemented. Otherwise ignore.
|
|
||||||
if !p.host.Mailserver() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all data as we will try to decode it possibly twice.
|
|
||||||
data, err := ioutil.ReadAll(packet.Payload)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid p2p request messages: %v", err)
|
|
||||||
}
|
|
||||||
r := bytes.NewReader(data)
|
|
||||||
packet.Payload = r
|
|
||||||
|
|
||||||
var requestDeprecated common.Envelope
|
|
||||||
errDepReq := packet.Decode(&requestDeprecated)
|
|
||||||
if errDepReq == nil {
|
|
||||||
return p.host.OnDeprecatedMessagesRequest(&requestDeprecated, p)
|
|
||||||
}
|
|
||||||
p.logger.Info("failed to decode p2p request message (deprecated)", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(errDepReq))
|
|
||||||
|
|
||||||
// As we failed to decode the request, let's set the offset
|
|
||||||
// to the beginning and try decode it again.
|
|
||||||
if _, err := r.Seek(0, io.SeekStart); err != nil {
|
|
||||||
return fmt.Errorf("invalid p2p request message: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var request common.MessagesRequest
|
|
||||||
errReq := packet.Decode(&request)
|
|
||||||
if errReq == nil {
|
|
||||||
return p.host.OnMessagesRequest(request, p)
|
|
||||||
}
|
|
||||||
p.logger.Info("failed to decode p2p request message", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(errReq))
|
|
||||||
|
|
||||||
return errors.New("invalid p2p request message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Peer) handleBatchAcknowledgeCode(packet p2p.Msg) error {
|
func (p *Peer) handleBatchAcknowledgeCode(packet p2p.Msg) error {
|
||||||
var batchHash gethcommon.Hash
|
var batchHash gethcommon.Hash
|
||||||
if err := packet.Decode(&batchHash); err != nil {
|
if err := packet.Decode(&batchHash); err != nil {
|
||||||
|
@ -368,18 +320,6 @@ func (p *Peer) handleP2PMessageCode(packet p2p.Msg) error {
|
||||||
return p.host.OnNewP2PEnvelopes(envelopes)
|
return p.host.OnNewP2PEnvelopes(envelopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) handleP2PRequestCompleteCode(packet p2p.Msg) error {
|
|
||||||
if !p.trusted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var payload []byte
|
|
||||||
if err := packet.Decode(&payload); err != nil {
|
|
||||||
return fmt.Errorf("invalid p2p request complete message: %v", err)
|
|
||||||
}
|
|
||||||
return p.host.OnP2PRequestCompleted(payload, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendConfirmation sends messageResponseCode and batchAcknowledgedCode messages.
|
// sendConfirmation sends messageResponseCode and batchAcknowledgedCode messages.
|
||||||
func (p *Peer) sendConfirmation(data []byte, envelopeErrors []common.EnvelopeError) (err error) {
|
func (p *Peer) sendConfirmation(data []byte, envelopeErrors []common.EnvelopeError) (err error) {
|
||||||
batchHash := crypto.Keccak256Hash(data)
|
batchHash := crypto.Keccak256Hash(data)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
@ -259,16 +258,6 @@ func (p *Peer) handlePacket(packet p2p.Msg) error {
|
||||||
p.logger.Warn("failed to decode direct message, peer will be disconnected", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(err))
|
p.logger.Warn("failed to decode direct message, peer will be disconnected", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case p2pRequestCode:
|
|
||||||
if err := p.handleP2PRequestCode(packet); err != nil {
|
|
||||||
p.logger.Warn("failed to decode p2p request message, peer will be disconnected", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case p2pRequestCompleteCode:
|
|
||||||
if err := p.handleP2PRequestCompleteCode(packet); err != nil {
|
|
||||||
p.logger.Warn("failed to decode p2p request complete message, peer will be disconnected", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
// New message common might be implemented in the future versions of Waku.
|
// New message common might be implemented in the future versions of Waku.
|
||||||
// For forward compatibility, just ignore.
|
// For forward compatibility, just ignore.
|
||||||
|
@ -321,43 +310,6 @@ func (p *Peer) handleMessageResponseCode(packet p2p.Msg) error {
|
||||||
return p.host.OnMessagesResponse(response, p)
|
return p.host.OnMessagesResponse(response, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) handleP2PRequestCode(packet p2p.Msg) error {
|
|
||||||
// Must be processed if mail server is implemented. Otherwise ignore.
|
|
||||||
if !p.host.Mailserver() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all data as we will try to decode it possibly twice.
|
|
||||||
data, err := ioutil.ReadAll(packet.Payload)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid p2p request messages: %v", err)
|
|
||||||
}
|
|
||||||
r := bytes.NewReader(data)
|
|
||||||
packet.Payload = r
|
|
||||||
|
|
||||||
var requestDeprecated common.Envelope
|
|
||||||
errDepReq := packet.Decode(&requestDeprecated)
|
|
||||||
if errDepReq == nil {
|
|
||||||
return p.host.OnDeprecatedMessagesRequest(&requestDeprecated, p)
|
|
||||||
}
|
|
||||||
p.logger.Info("failed to decode p2p request message (deprecated)", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(errDepReq))
|
|
||||||
|
|
||||||
// As we failed to decode the request, let's set the offset
|
|
||||||
// to the beginning and try decode it again.
|
|
||||||
if _, err := r.Seek(0, io.SeekStart); err != nil {
|
|
||||||
return fmt.Errorf("invalid p2p request message: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var request common.MessagesRequest
|
|
||||||
errReq := packet.Decode(&request)
|
|
||||||
if errReq == nil {
|
|
||||||
return p.host.OnMessagesRequest(request, p)
|
|
||||||
}
|
|
||||||
p.logger.Info("failed to decode p2p request message", zap.String("peerID", types.EncodeHex(p.ID())), zap.Error(errReq))
|
|
||||||
|
|
||||||
return errors.New("invalid p2p request message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Peer) handleBatchAcknowledgeCode(packet p2p.Msg) error {
|
func (p *Peer) handleBatchAcknowledgeCode(packet p2p.Msg) error {
|
||||||
var batchHash gethcommon.Hash
|
var batchHash gethcommon.Hash
|
||||||
if err := packet.Decode(&batchHash); err != nil {
|
if err := packet.Decode(&batchHash); err != nil {
|
||||||
|
@ -400,18 +352,6 @@ func (p *Peer) handleP2PMessageCode(packet p2p.Msg) error {
|
||||||
return p.host.OnNewP2PEnvelopes(envelopes)
|
return p.host.OnNewP2PEnvelopes(envelopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) handleP2PRequestCompleteCode(packet p2p.Msg) error {
|
|
||||||
if !p.trusted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var payload []byte
|
|
||||||
if err := packet.Decode(&payload); err != nil {
|
|
||||||
return fmt.Errorf("invalid p2p request complete message: %v", err)
|
|
||||||
}
|
|
||||||
return p.host.OnP2PRequestCompleted(payload, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendConfirmation sends messageResponseCode and batchAcknowledgedCode messages.
|
// sendConfirmation sends messageResponseCode and batchAcknowledgedCode messages.
|
||||||
func (p *Peer) sendConfirmation(data []byte, envelopeErrors []common.EnvelopeError) (err error) {
|
func (p *Peer) sendConfirmation(data []byte, envelopeErrors []common.EnvelopeError) (err error) {
|
||||||
batchHash := crypto.Keccak256Hash(data)
|
batchHash := crypto.Keccak256Hash(data)
|
||||||
|
|
48
waku/waku.go
48
waku/waku.go
|
@ -102,8 +102,6 @@ type Waku struct {
|
||||||
settings settings // Holds configuration settings that can be dynamically changed
|
settings settings // Holds configuration settings that can be dynamically changed
|
||||||
settingsMu sync.RWMutex // Mutex to sync the settings access
|
settingsMu sync.RWMutex // Mutex to sync the settings access
|
||||||
|
|
||||||
mailServer MailServer
|
|
||||||
|
|
||||||
rateLimiter *common.PeerRateLimiter
|
rateLimiter *common.PeerRateLimiter
|
||||||
|
|
||||||
envelopeFeed event.Feed
|
envelopeFeed event.Feed
|
||||||
|
@ -484,12 +482,6 @@ func (w *Waku) Protocols() []p2p.Protocol {
|
||||||
return w.protocols
|
return w.protocols
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterMailServer registers MailServer interface.
|
|
||||||
// MailServer will process all the incoming messages with p2pRequestCode.
|
|
||||||
func (w *Waku) RegisterMailServer(server MailServer) {
|
|
||||||
w.mailServer = server
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRateLimiter registers a rate limiter.
|
// SetRateLimiter registers a rate limiter.
|
||||||
func (w *Waku) RegisterRateLimiter(r *common.PeerRateLimiter) {
|
func (w *Waku) RegisterRateLimiter(r *common.PeerRateLimiter) {
|
||||||
w.rateLimiter = r
|
w.rateLimiter = r
|
||||||
|
@ -640,14 +632,6 @@ func (w *Waku) AllowP2PMessagesFromPeer(peerID []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Waku) SendHistoricMessageResponse(peerID []byte, payload []byte) error {
|
|
||||||
peer, err := w.getPeer(peerID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return peer.SendHistoricMessageResponse(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendP2PMessage sends a peer-to-peer message to a specific peer.
|
// SendP2PMessage sends a peer-to-peer message to a specific peer.
|
||||||
// It sends one or more envelopes in a single batch.
|
// It sends one or more envelopes in a single batch.
|
||||||
func (w *Waku) SendP2PMessages(peerID []byte, envelopes ...*common.Envelope) error {
|
func (w *Waku) SendP2PMessages(peerID []byte, envelopes ...*common.Envelope) error {
|
||||||
|
@ -1126,30 +1110,6 @@ func (w *Waku) OnNewP2PEnvelopes(envelopes []*common.Envelope) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Waku) Mailserver() bool {
|
|
||||||
return w.mailServer != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Waku) OnMessagesRequest(request common.MessagesRequest, p common.Peer) error {
|
|
||||||
w.mailServer.Deliver(p.ID(), request)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Waku) OnDeprecatedMessagesRequest(request *common.Envelope, p common.Peer) error {
|
|
||||||
w.mailServer.DeliverMail(p.ID(), request)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Waku) OnP2PRequestCompleted(payload []byte, p common.Peer) error {
|
|
||||||
msEvent, err := CreateMailServerEvent(p.EnodeID(), payload)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid p2p request complete payload: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.postP2P(*msEvent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Waku) OnMessagesResponse(response common.MessagesResponse, p common.Peer) error {
|
func (w *Waku) OnMessagesResponse(response common.MessagesResponse, p common.Peer) error {
|
||||||
w.envelopeFeed.Send(common.EnvelopeEvent{
|
w.envelopeFeed.Send(common.EnvelopeEvent{
|
||||||
Batch: response.Hash,
|
Batch: response.Hash,
|
||||||
|
@ -1338,14 +1298,6 @@ func (w *Waku) addAndBridge(envelope *common.Envelope, isP2P bool, bridged bool)
|
||||||
common.EnvelopesCachedCounter.WithLabelValues("miss").Inc()
|
common.EnvelopesCachedCounter.WithLabelValues("miss").Inc()
|
||||||
common.EnvelopesSizeMeter.Observe(float64(envelope.Size()))
|
common.EnvelopesSizeMeter.Observe(float64(envelope.Size()))
|
||||||
w.postEvent(envelope, isP2P) // notify the local node about the new message
|
w.postEvent(envelope, isP2P) // notify the local node about the new message
|
||||||
if w.mailServer != nil {
|
|
||||||
w.mailServer.Archive(envelope)
|
|
||||||
w.envelopeFeed.Send(common.EnvelopeEvent{
|
|
||||||
Topic: envelope.Topic,
|
|
||||||
Hash: envelope.Hash(),
|
|
||||||
Event: common.EventMailServerEnvelopeArchived,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Bridge only envelopes that are not p2p messages.
|
// Bridge only envelopes that are not p2p messages.
|
||||||
// In particular, if a node is a lightweight node,
|
// In particular, if a node is a lightweight node,
|
||||||
// it should not bridge any envelopes.
|
// it should not bridge any envelopes.
|
||||||
|
|
|
@ -323,23 +323,6 @@ func (s *WakuTestSuite) TestWakuTimeDesyncEnvelopeIgnored() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockMailserver struct {
|
|
||||||
deliverMail func([]byte, *common.Envelope)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*MockMailserver) Archive(e *common.Envelope) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*MockMailserver) Deliver(peerID []byte, r common.MessagesRequest) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMailserver) DeliverMail(peerID []byte, e *common.Envelope) {
|
|
||||||
|
|
||||||
if m.deliverMail != nil {
|
|
||||||
m.deliverMail(peerID, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuTestSuite) TestRateLimiterIntegration() {
|
func (s *WakuTestSuite) TestRateLimiterIntegration() {
|
||||||
conf := &Config{
|
conf := &Config{
|
||||||
MinimumAcceptedPoW: 0,
|
MinimumAcceptedPoW: 0,
|
||||||
|
@ -370,61 +353,6 @@ func (s *WakuTestSuite) TestRateLimiterIntegration() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WakuTestSuite) TestMailserverCompletionEvent() {
|
|
||||||
w1 := New(nil, nil)
|
|
||||||
s.Require().NoError(w1.Start())
|
|
||||||
defer func() { handleError(s.T(), w1.Stop()) }()
|
|
||||||
|
|
||||||
rw1, rw2 := p2p.MsgPipe()
|
|
||||||
errorc := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
err := w1.HandlePeer(s.newPeer(w1, p2p.NewPeer(enode.ID{}, "1", []p2p.Cap{}), rw1, nil, s.stats), rw1)
|
|
||||||
errorc <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
w2 := New(nil, nil)
|
|
||||||
s.Require().NoError(w2.Start())
|
|
||||||
defer func() { handleError(s.T(), w2.Stop()) }()
|
|
||||||
|
|
||||||
peer2 := s.newPeer(w2, p2p.NewPeer(enode.ID{1}, "1", nil), rw2, nil, s.stats)
|
|
||||||
peer2.SetPeerTrusted(true)
|
|
||||||
|
|
||||||
events := make(chan common.EnvelopeEvent)
|
|
||||||
sub := w1.SubscribeEnvelopeEvents(events)
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
|
|
||||||
envelopes := []*common.Envelope{{Data: []byte{1}}, {Data: []byte{2}}}
|
|
||||||
s.Require().NoError(peer2.Start())
|
|
||||||
// Set peer trusted, we know the peer has been added as handshake was successful
|
|
||||||
w1.getPeers()[0].SetPeerTrusted(true)
|
|
||||||
|
|
||||||
s.Require().NoError(peer2.SendP2PMessages(envelopes))
|
|
||||||
s.Require().NoError(peer2.SendHistoricMessageResponse(make([]byte, 100)))
|
|
||||||
s.Require().NoError(rw2.Close())
|
|
||||||
|
|
||||||
// Wait for all messages to be read
|
|
||||||
err := <-errorc
|
|
||||||
s.Require().EqualError(err, "p2p: read or write on closed message pipe")
|
|
||||||
|
|
||||||
after := time.After(2 * time.Second)
|
|
||||||
count := 0
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-after:
|
|
||||||
s.Require().FailNow("timed out waiting for all events")
|
|
||||||
case ev := <-events:
|
|
||||||
switch ev.Event {
|
|
||||||
case common.EventEnvelopeAvailable:
|
|
||||||
count++
|
|
||||||
case common.EventMailServerRequestCompleted:
|
|
||||||
s.Require().Equal(count, len(envelopes),
|
|
||||||
"all envelope.avaiable events mut be recevied before request is compelted")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// two generic waku node handshake
|
// two generic waku node handshake
|
||||||
func (s *WakuTestSuite) TestPeerHandshakeWithTwoFullNode() {
|
func (s *WakuTestSuite) TestPeerHandshakeWithTwoFullNode() {
|
||||||
rw1, rw2 := p2p.MsgPipe()
|
rw1, rw2 := p2p.MsgPipe()
|
||||||
|
|
Loading…
Reference in New Issue