test leveldb panics on mailserver (#1065)

* test leveldb panics on mailserver

* refactoring

* rename DB interface to dbImpl and add documentation
This commit is contained in:
Andrea Franz 2018-06-27 14:22:09 +02:00 committed by GitHub
parent b913c98573
commit 29d90b651d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 15 deletions

View File

@ -11,12 +11,12 @@ const batchSize = 1000
// Cleaner removes old messages from a db // Cleaner removes old messages from a db
type Cleaner struct { type Cleaner struct {
db *leveldb.DB db dbImpl
batchSize int batchSize int
} }
// NewCleanerWithDB returns a new Cleaner for db // NewCleanerWithDB returns a new Cleaner for db
func NewCleanerWithDB(db *leveldb.DB) *Cleaner { func NewCleanerWithDB(db dbImpl) *Cleaner {
return &Cleaner{ return &Cleaner{
db: db, db: db,
batchSize: batchSize, batchSize: batchSize,

View File

@ -95,7 +95,7 @@ func testMessagesCount(t *testing.T, expected int, s *WMailServer) {
require.Equal(t, expected, count, fmt.Sprintf("expected %d message, got: %d", expected, count)) require.Equal(t, expected, count, fmt.Sprintf("expected %d message, got: %d", expected, count))
} }
func countMessages(t *testing.T, db *leveldb.DB) int { func countMessages(t *testing.T, db dbImpl) int {
var ( var (
count int count int
zero common.Hash zero common.Hash

View File

@ -31,6 +31,8 @@ import (
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/util" "github.com/syndtr/goleveldb/leveldb/util"
) )
@ -53,9 +55,23 @@ var (
archivedErrorsCounter = metrics.NewRegisteredCounter("mailserver/archiveErrors", nil) archivedErrorsCounter = metrics.NewRegisteredCounter("mailserver/archiveErrors", nil)
) )
// dbImpl is an interface introduced to be able to test some unexpected
// panics from leveldb that are difficult to reproduce.
// normally the db implementation is leveldb.DB, but in TestMailServerDBPanicSuite
// we use panicDB to test panics from the db.
// more info about the panic errors:
// https://github.com/syndtr/goleveldb/issues/224
type dbImpl interface {
Close() error
Write(*leveldb.Batch, *opt.WriteOptions) error
Put([]byte, []byte, *opt.WriteOptions) error
Get([]byte, *opt.ReadOptions) ([]byte, error)
NewIterator(*util.Range, *opt.ReadOptions) iterator.Iterator
}
// WMailServer whisper mailserver. // WMailServer whisper mailserver.
type WMailServer struct { type WMailServer struct {
db *leveldb.DB db dbImpl
w *whisper.Whisper w *whisper.Whisper
pow float64 pow float64
key []byte key []byte
@ -94,11 +110,12 @@ func (s *WMailServer) Init(shh *whisper.Whisper, config *params.WhisperConfig) e
return errPasswordNotProvided return errPasswordNotProvided
} }
s.db, err = leveldb.OpenFile(config.DataDir, nil) db, err := leveldb.OpenFile(config.DataDir, nil)
if err != nil { if err != nil {
return fmt.Errorf("open DB: %s", err) return fmt.Errorf("open DB: %s", err)
} }
s.db = db
s.w = shh s.w = shh
s.pow = config.MinimumPoW s.pow = config.MinimumPoW
@ -202,7 +219,12 @@ func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope)
defer recoverLevelDBPanics("DeliverMail") defer recoverLevelDBPanics("DeliverMail")
if ok, lower, upper, bloom := s.validateRequest(peer.ID(), request); ok { if ok, lower, upper, bloom := s.validateRequest(peer.ID(), request); ok {
s.processRequest(peer, lower, upper, bloom) _, err := s.processRequest(peer, lower, upper, bloom)
if err != nil {
log.Error(fmt.Sprintf("error in DeliverMail: %s", err))
return
}
if err := s.sendHistoricMessageResponse(peer, request); err != nil { if err := s.sendHistoricMessageResponse(peer, request); err != nil {
log.Error(fmt.Sprintf("SendHistoricMessageResponse error: %s", err)) log.Error(fmt.Sprintf("SendHistoricMessageResponse error: %s", err))
} }
@ -225,9 +247,14 @@ func (s *WMailServer) exceedsPeerRequests(peer []byte) bool {
// processRequest processes the current request and re-sends all stored messages // processRequest processes the current request and re-sends all stored messages
// accomplishing lower and upper limits. // accomplishing lower and upper limits.
func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bloom []byte) []*whisper.Envelope { func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bloom []byte) (ret []*whisper.Envelope, err error) {
ret := make([]*whisper.Envelope, 0) // Recover from possible goleveldb panics
var err error defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic in processRequest: %v", r)
}
}()
var zero common.Hash var zero common.Hash
kl := NewDbKey(lower, zero) kl := NewDbKey(lower, zero)
ku := NewDbKey(upper+1, zero) // LevelDB is exclusive, while the Whisper API is inclusive ku := NewDbKey(upper+1, zero) // LevelDB is exclusive, while the Whisper API is inclusive
@ -243,9 +270,10 @@ func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bl
for i.Next() { for i.Next() {
var envelope whisper.Envelope var envelope whisper.Envelope
err = rlp.DecodeBytes(i.Value(), &envelope) decodeErr := rlp.DecodeBytes(i.Value(), &envelope)
if err != nil { if decodeErr != nil {
log.Error(fmt.Sprintf("RLP decoding failed: %s", err)) log.Error(fmt.Sprintf("RLP decoding failed: %s", decodeErr))
continue
} }
if whisper.BloomFilterMatch(bloom, envelope.Bloom()) { if whisper.BloomFilterMatch(bloom, envelope.Bloom()) {
@ -256,7 +284,7 @@ func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bl
err = s.w.SendP2PDirect(peer, &envelope) err = s.w.SendP2PDirect(peer, &envelope)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("Failed to send direct message to peer: %s", err)) log.Error(fmt.Sprintf("Failed to send direct message to peer: %s", err))
return nil return
} }
} }
sentEnvelopes++ sentEnvelopes++
@ -273,7 +301,7 @@ func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bl
log.Error(fmt.Sprintf("Level DB iterator error: %s", err)) log.Error(fmt.Sprintf("Level DB iterator error: %s", err))
} }
return ret return
} }
func (s *WMailServer) sendHistoricMessageResponse(peer *whisper.Peer, request *whisper.Envelope) error { func (s *WMailServer) sendHistoricMessageResponse(peer *whisper.Peer, request *whisper.Envelope) error {

View File

@ -0,0 +1,66 @@
package mailserver
import (
"testing"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"github.com/stretchr/testify/suite"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/util"
)
type panicDB struct{}
func (db *panicDB) Close() error {
panic("panicDB panic on Close")
}
func (db *panicDB) Write(b *leveldb.Batch, opts *opt.WriteOptions) error {
panic("panicDB panic on Write")
}
func (db *panicDB) Put(k []byte, v []byte, opts *opt.WriteOptions) error {
panic("panicDB panic on Put")
}
func (db *panicDB) Get(k []byte, opts *opt.ReadOptions) ([]byte, error) {
panic("panicDB panic on Get")
}
func (db *panicDB) NewIterator(r *util.Range, opts *opt.ReadOptions) iterator.Iterator {
panic("panicDB panic on NewIterator")
}
func TestMailServerDBPanicSuite(t *testing.T) {
suite.Run(t, new(MailServerDBPanicSuite))
}
type MailServerDBPanicSuite struct {
suite.Suite
server *WMailServer
}
func (s *MailServerDBPanicSuite) SetupTest() {
s.server = &WMailServer{}
s.server.db = &panicDB{}
}
func (s *MailServerDBPanicSuite) TestArchive() {
defer s.testPanicRecover("Archive")
s.server.Archive(&whisper.Envelope{})
}
func (s *MailServerDBPanicSuite) TestDeliverMail() {
defer s.testPanicRecover("DeliverMail")
_, err := s.server.processRequest(nil, 10, 20, []byte{})
s.Error(err)
s.Equal("recovered from panic in processRequest: panicDB panic on NewIterator", err.Error())
}
func (s *MailServerDBPanicSuite) testPanicRecover(method string) {
if r := recover(); r != nil {
s.Failf("error recovering panic", "expected recover to return nil, got: %+v", r)
}
}

View File

@ -251,7 +251,8 @@ func (s *MailserverSuite) TestMailServer() {
func (s *MailserverSuite) messageExists(envelope *whisper.Envelope, low, upp uint32, bloom []byte) bool { func (s *MailserverSuite) messageExists(envelope *whisper.Envelope, low, upp uint32, bloom []byte) bool {
var exist bool var exist bool
mail := s.server.processRequest(nil, low, upp, bloom) mail, err := s.server.processRequest(nil, low, upp, bloom)
s.NoError(err)
for _, msg := range mail { for _, msg := range mail {
if msg.Hash() == envelope.Hash() { if msg.Hash() == envelope.Hash() {
exist = true exist = true