2018-04-09 08:18:22 +00:00
|
|
|
package sign
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2018-04-17 14:13:32 +00:00
|
|
|
"sync/atomic"
|
2018-04-09 08:18:22 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
|
|
|
|
|
|
"github.com/status-im/status-go/geth/account"
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
correctPassword = "password-correct"
|
|
|
|
wrongPassword = "password-wrong"
|
|
|
|
)
|
|
|
|
|
|
|
|
func testVerifyFunc(password string) (*account.SelectedExtKey, error) {
|
|
|
|
if password == correctPassword {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, keystore.ErrDecrypt
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPendingRequestsSuite(t *testing.T) {
|
|
|
|
suite.Run(t, new(PendingRequestsSuite))
|
|
|
|
}
|
|
|
|
|
|
|
|
type PendingRequestsSuite struct {
|
|
|
|
suite.Suite
|
|
|
|
pendingRequests *PendingRequests
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PendingRequestsSuite) SetupTest() {
|
|
|
|
s.pendingRequests = NewPendingRequests()
|
|
|
|
}
|
|
|
|
|
2018-04-10 10:02:54 +00:00
|
|
|
func (s *PendingRequestsSuite) defaultCompleteFunc() CompleteFunc {
|
2018-04-09 08:18:22 +00:00
|
|
|
hash := gethcommon.Hash{1}
|
2018-04-10 10:02:54 +00:00
|
|
|
return func(acc *account.SelectedExtKey, password string) (Response, error) {
|
2018-04-09 08:18:22 +00:00
|
|
|
s.Nil(acc, "account should be `nil`")
|
2018-04-10 10:02:54 +00:00
|
|
|
s.Equal(correctPassword, password)
|
|
|
|
return hash.Bytes(), nil
|
2018-04-09 08:18:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-10 10:02:54 +00:00
|
|
|
func (s *PendingRequestsSuite) delayedCompleteFunc() CompleteFunc {
|
2018-04-09 08:18:22 +00:00
|
|
|
hash := gethcommon.Hash{1}
|
2018-04-10 10:02:54 +00:00
|
|
|
return func(acc *account.SelectedExtKey, password string) (Response, error) {
|
2018-04-09 08:18:22 +00:00
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
s.Nil(acc, "account should be `nil`")
|
2018-04-10 10:02:54 +00:00
|
|
|
s.Equal(correctPassword, password)
|
|
|
|
return hash.Bytes(), nil
|
2018-04-09 08:18:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-10 10:02:54 +00:00
|
|
|
func (s *PendingRequestsSuite) errorCompleteFunc(err error) CompleteFunc {
|
2018-04-09 08:18:22 +00:00
|
|
|
hash := gethcommon.Hash{1}
|
2018-04-10 10:02:54 +00:00
|
|
|
return func(acc *account.SelectedExtKey, password string) (Response, error) {
|
2018-04-09 08:18:22 +00:00
|
|
|
s.Nil(acc, "account should be `nil`")
|
2018-04-10 10:02:54 +00:00
|
|
|
return hash.Bytes(), err
|
2018-04-09 08:18:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PendingRequestsSuite) TestGet() {
|
2018-04-10 10:02:54 +00:00
|
|
|
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
2018-04-09 08:18:22 +00:00
|
|
|
s.NoError(err)
|
|
|
|
for i := 2; i > 0; i-- {
|
|
|
|
actualRequest, err := s.pendingRequests.Get(req.ID)
|
|
|
|
s.NoError(err)
|
|
|
|
s.Equal(req, actualRequest)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-10 10:02:54 +00:00
|
|
|
func (s *PendingRequestsSuite) testComplete(password string, hash gethcommon.Hash, completeFunc CompleteFunc) (string, error) {
|
|
|
|
req, err := s.pendingRequests.Add(context.Background(), "", nil, completeFunc)
|
2018-04-09 08:18:22 +00:00
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
|
|
|
|
2018-04-10 10:02:54 +00:00
|
|
|
result := s.pendingRequests.Approve(req.ID, password, testVerifyFunc)
|
2018-04-09 08:18:22 +00:00
|
|
|
|
2018-04-10 10:02:54 +00:00
|
|
|
if s.pendingRequests.Has(req.ID) {
|
|
|
|
// transient error
|
|
|
|
s.Equal(EmptyResponse, result.Response, "no hash should be sent")
|
|
|
|
} else {
|
|
|
|
s.Equal(hash.Bytes(), result.Response.Bytes(), "hashes should match")
|
|
|
|
}
|
|
|
|
|
|
|
|
return req.ID, result.Error
|
2018-04-09 08:18:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PendingRequestsSuite) TestCompleteSuccess() {
|
|
|
|
id, err := s.testComplete(correctPassword, gethcommon.Hash{1}, s.defaultCompleteFunc())
|
|
|
|
s.NoError(err, "no errors should be there")
|
|
|
|
|
|
|
|
s.False(s.pendingRequests.Has(id), "sign request should not exist")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PendingRequestsSuite) TestCompleteTransientError() {
|
|
|
|
hash := gethcommon.Hash{}
|
|
|
|
id, err := s.testComplete(wrongPassword, hash, s.errorCompleteFunc(keystore.ErrDecrypt))
|
|
|
|
s.Equal(keystore.ErrDecrypt, err, "error value should be preserved")
|
|
|
|
|
|
|
|
s.True(s.pendingRequests.Has(id))
|
|
|
|
// verify that you are able to re-approve it after a transient error
|
|
|
|
_, err = s.pendingRequests.tryLock(id)
|
|
|
|
s.NoError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PendingRequestsSuite) TestCompleteError() {
|
|
|
|
hash := gethcommon.Hash{1}
|
|
|
|
expectedError := errors.New("test")
|
|
|
|
|
|
|
|
id, err := s.testComplete(correctPassword, hash, s.errorCompleteFunc(expectedError))
|
|
|
|
|
|
|
|
s.Equal(expectedError, err, "error value should be preserved")
|
|
|
|
|
|
|
|
s.False(s.pendingRequests.Has(id))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s PendingRequestsSuite) TestMultipleComplete() {
|
|
|
|
id, err := s.testComplete(correctPassword, gethcommon.Hash{1}, s.defaultCompleteFunc())
|
|
|
|
s.NoError(err, "no errors should be there")
|
|
|
|
|
2018-04-10 10:02:54 +00:00
|
|
|
result := s.pendingRequests.Approve(id, correctPassword, testVerifyFunc)
|
2018-04-09 08:18:22 +00:00
|
|
|
|
2018-04-10 10:02:54 +00:00
|
|
|
s.Equal(ErrSignReqNotFound, result.Error)
|
2018-04-09 08:18:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s PendingRequestsSuite) TestConcurrentComplete() {
|
2018-04-10 10:02:54 +00:00
|
|
|
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.delayedCompleteFunc())
|
2018-04-09 08:18:22 +00:00
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
|
|
|
|
2018-04-17 14:13:32 +00:00
|
|
|
var approved int32
|
|
|
|
var tried int32
|
2018-04-09 08:18:22 +00:00
|
|
|
|
|
|
|
for i := 10; i > 0; i-- {
|
|
|
|
go func() {
|
2018-04-10 10:02:54 +00:00
|
|
|
result := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
|
|
|
if result.Error == nil {
|
2018-04-17 14:13:32 +00:00
|
|
|
atomic.AddInt32(&approved, 1)
|
2018-04-09 08:18:22 +00:00
|
|
|
}
|
2018-04-17 14:13:32 +00:00
|
|
|
atomic.AddInt32(&tried, 1)
|
2018-04-09 08:18:22 +00:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
s.pendingRequests.Wait(req.ID, 10*time.Second)
|
|
|
|
|
|
|
|
s.False(s.pendingRequests.Has(req.ID), "sign request should exist")
|
|
|
|
|
2018-04-17 14:13:32 +00:00
|
|
|
s.EqualValues(atomic.LoadInt32(&approved), 1, "request should be approved only once")
|
|
|
|
s.EqualValues(atomic.LoadInt32(&tried), 10, "request should be tried to approve 10 times")
|
2018-04-09 08:18:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s PendingRequestsSuite) TestWaitSuccess() {
|
2018-04-10 10:02:54 +00:00
|
|
|
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
2018-04-09 08:18:22 +00:00
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
|
|
|
|
|
|
|
go func() {
|
2018-04-10 10:02:54 +00:00
|
|
|
result := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
|
|
|
s.NoError(result.Error)
|
2018-04-09 08:18:22 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
result := s.pendingRequests.Wait(req.ID, 1*time.Second)
|
|
|
|
s.NoError(result.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s PendingRequestsSuite) TestDiscard() {
|
2018-04-10 10:02:54 +00:00
|
|
|
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
2018-04-09 08:18:22 +00:00
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
|
|
|
|
|
|
|
s.Equal(ErrSignReqNotFound, s.pendingRequests.Discard(""))
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
// enough to make it be called after Wait
|
|
|
|
time.Sleep(time.Millisecond)
|
|
|
|
s.NoError(s.pendingRequests.Discard(req.ID))
|
|
|
|
}()
|
|
|
|
|
|
|
|
result := s.pendingRequests.Wait(req.ID, 1*time.Second)
|
|
|
|
s.Equal(ErrSignReqDiscarded, result.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s PendingRequestsSuite) TestWaitFail() {
|
|
|
|
expectedError := errors.New("test-wait-fail")
|
2018-04-10 10:02:54 +00:00
|
|
|
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.errorCompleteFunc(expectedError))
|
2018-04-09 08:18:22 +00:00
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
|
|
|
|
|
|
|
go func() {
|
2018-04-10 10:02:54 +00:00
|
|
|
result := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
|
|
|
s.Equal(expectedError, result.Error)
|
2018-04-09 08:18:22 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
result := s.pendingRequests.Wait(req.ID, 1*time.Second)
|
|
|
|
s.Equal(expectedError, result.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s PendingRequestsSuite) TestWaitTimeout() {
|
2018-04-17 14:02:48 +00:00
|
|
|
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
2018-04-09 08:18:22 +00:00
|
|
|
s.NoError(err)
|
|
|
|
|
|
|
|
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
|
|
|
|
|
|
|
result := s.pendingRequests.Wait(req.ID, 0*time.Second)
|
2018-04-17 14:02:48 +00:00
|
|
|
s.Equal(ErrSignReqTimedOut, result.Error)
|
2018-04-17 14:13:32 +00:00
|
|
|
|
|
|
|
// Try approving the timeouted request, it will fail
|
|
|
|
result = s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
|
|
|
s.NotNil(result.Error)
|
2018-04-09 08:18:22 +00:00
|
|
|
}
|