Skip to content

Commit

Permalink
eth: rewardservice unit testing
Browse files Browse the repository at this point in the history
  • Loading branch information
kyriediculous committed Dec 10, 2020
1 parent 63d2145 commit 93ca0e2
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 19 deletions.
35 changes: 16 additions & 19 deletions eth/eventservices/rewardservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package eventservices
import (
"context"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/golang/glog"
"github.com/livepeer/go-livepeer/eth"
"github.com/livepeer/go-livepeer/eth/watchers"
Expand All @@ -19,7 +21,12 @@ type RewardService struct {
client eth.LivepeerEthClient
working bool
cancelWorker context.CancelFunc
tw *watchers.TimeWatcher
tw roundsWatcher
}

type roundsWatcher interface {
SubscribeRounds(sink chan<- types.Log) event.Subscription
LastInitializedRound() *big.Int
}

func NewRewardService(client eth.LivepeerEthClient, tw *watchers.TimeWatcher) *RewardService {
Expand Down Expand Up @@ -55,7 +62,7 @@ func (s *RewardService) Start(ctx context.Context) error {
case <-rounds:
err := s.tryReward()
if err != nil {
glog.Errorf("Error trying to call reward: %v", err)
glog.Errorf("Error trying to call reward err=%v", err)
}
case <-cancelCtx.Done():
glog.V(5).Infof("Reward service done")
Expand All @@ -80,27 +87,14 @@ func (s *RewardService) IsWorking() bool {
}

func (s *RewardService) tryReward() error {
currentRound, err := s.client.CurrentRound()
if err != nil {
return err
}

initialized, err := s.client.CurrentRoundInitialized()
if err != nil {
return err
}
currentRound := s.tw.LastInitializedRound()

t, err := s.client.GetTranscoder(s.client.Account().Address)
if err != nil {
return err
}

active, err := s.client.IsActiveTranscoder()
if err != nil {
return err
}

if t.LastRewardRound.Cmp(currentRound) == -1 && initialized && active {
if t.LastRewardRound.Cmp(currentRound) == -1 && t.Active {
tx, err := s.client.Reward()
if err != nil {
return err
Expand All @@ -116,9 +110,12 @@ func (s *RewardService) tryReward() error {
if err != nil {
return err
}
if err := s.client.CheckTx(tx); err != nil {
return err
}
} else {
return err
}

return err
}

tp, err := s.client.GetTranscoderEarningsPoolForRound(s.client.Account().Address, currentRound)
Expand Down
183 changes: 183 additions & 0 deletions eth/eventservices/rewardservice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package eventservices

import (
"context"
"math/big"
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/golang/glog"
"github.com/livepeer/go-livepeer/eth"
lpTypes "github.com/livepeer/go-livepeer/eth/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestStart(t *testing.T) {
assert := assert.New(t)
rs := RewardService{
working: true,
}
assert.EqualError(rs.Start(context.Background()), ErrRewardServiceStarted.Error())

ctx, cancel := context.WithCancel(context.Background())
rs = RewardService{
tw: &stubTimeWatcher{},
cancelWorker: cancel,
}
errC := make(chan error)
go func() { errC <- rs.Start(ctx) }()
time.Sleep(1 * time.Second)
assert.True(rs.working)
cancel()
err := <-errC
assert.Nil(err)
}

func TestStop(t *testing.T) {
assert := assert.New(t)
rs := RewardService{
working: false,
}
assert.EqualError(rs.Stop(), ErrRewardServiceStopped.Error())

ctx, cancel := context.WithCancel(context.Background())
rs = RewardService{
tw: &stubTimeWatcher{},
cancelWorker: cancel,
}
go rs.Start(ctx)
time.Sleep(1 * time.Second)
require.True(t, rs.working)
rs.Stop()
assert.False(rs.working)
}

func TestIsWorking(t *testing.T) {
assert := assert.New(t)
rs := RewardService{
working: false,
}
assert.False(rs.IsWorking())
rs.working = true
assert.True(rs.IsWorking())
}

func TestReceiveRoundEvent_TryReward(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
eth := &eth.MockClient{}
tw := &stubTimeWatcher{
lastInitializedRound: big.NewInt(100),
}
ctx := context.Background()
rs := RewardService{
client: eth,
tw: tw,
}

go rs.Start(ctx)
defer rs.Stop()
time.Sleep(1 * time.Second)
require.True(rs.IsWorking())

// Happy case , check that reward was called
// Assert that no error was logged
eth.On("Account").Return(accounts.Account{})
eth.On("GetTranscoder").Return(&lpTypes.Transcoder{
LastRewardRound: big.NewInt(1),
Active: true,
}, nil)
eth.On("Reward").Return(&types.Transaction{}, nil).Times(1)
eth.On("CheckTx").Return(nil).Times(1)
eth.On("GetTranscoderEarningsPoolForRound").Return(&lpTypes.TokenPools{}, nil)

errorLogsBefore := glog.Stats.Error.Lines()
infoLogsBefore := glog.Stats.Info.Lines()

tw.roundSink <- types.Log{}
time.Sleep(1 * time.Second)

eth.AssertNumberOfCalls(t, "Reward", 1)
eth.AssertNumberOfCalls(t, "CheckTx", 1)
eth.AssertNotCalled(t, "ReplaceTransaction")

errorLogsAfter := glog.Stats.Error.Lines()
infoLogsAfter := glog.Stats.Info.Lines()
assert.Equal(int64(0), errorLogsAfter-errorLogsBefore)
assert.Equal(int64(1), infoLogsAfter-infoLogsBefore)

// Test for transaction time out error
// Call replace transaction
eth.On("Reward").Return(&types.Transaction{}, nil).Once()
eth.On("CheckTx").Return(context.DeadlineExceeded).Once()
eth.On("ReplaceTransaction").Return(&types.Transaction{}, nil)
eth.On("CheckTx").Return(nil).Once()

errorLogsBefore = glog.Stats.Error.Lines()
infoLogsBefore = glog.Stats.Info.Lines()

tw.roundSink <- types.Log{}
time.Sleep(1 * time.Second)

eth.AssertNumberOfCalls(t, "Reward", 2)
eth.AssertNumberOfCalls(t, "CheckTx", 3)
eth.AssertNumberOfCalls(t, "ReplaceTransaction", 1)

errorLogsAfter = glog.Stats.Error.Lines()
infoLogsAfter = glog.Stats.Info.Lines()
assert.Equal(int64(0), errorLogsAfter-errorLogsBefore)
assert.Equal(int64(2), infoLogsAfter-infoLogsBefore)

// Test replacement timeout error
eth.On("Reward").Return(&types.Transaction{}, nil).Once()
eth.On("CheckTx").Return(context.DeadlineExceeded)
eth.On("ReplaceTransaction").Return(&types.Transaction{}, nil)

errorLogsBefore = glog.Stats.Error.Lines()
infoLogsBefore = glog.Stats.Info.Lines()

tw.roundSink <- types.Log{}
time.Sleep(1 * time.Second)

eth.AssertNumberOfCalls(t, "Reward", 3)
eth.AssertNumberOfCalls(t, "CheckTx", 5)
eth.AssertNumberOfCalls(t, "ReplaceTransaction", 2)

errorLogsAfter = glog.Stats.Error.Lines()
infoLogsAfter = glog.Stats.Info.Lines()
assert.Equal(int64(1), errorLogsAfter-errorLogsBefore)
assert.Equal(int64(1), infoLogsAfter-infoLogsBefore)
}

type stubTimeWatcher struct {
lastInitializedRound *big.Int
roundSink chan<- types.Log
roundSub event.Subscription
}

func (m *stubTimeWatcher) SubscribeRounds(sink chan<- types.Log) event.Subscription {
m.roundSink = sink
m.roundSub = &stubSubscription{errCh: make(<-chan error)}
return m.roundSub
}

func (m *stubTimeWatcher) LastInitializedRound() *big.Int {
return m.lastInitializedRound
}

type stubSubscription struct {
errCh <-chan error
unsubscribed bool
}

func (s *stubSubscription) Unsubscribe() {
s.unsubscribed = true
}

func (s *stubSubscription) Err() <-chan error {
return s.errCh
}
25 changes: 25 additions & 0 deletions eth/stubclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ func (m *MockClient) GetTranscoderPoolMaxSize() (*big.Int, error) {
return mockBigInt(args, 0), args.Error(1)
}

func (m *MockClient) GetTranscoder(address common.Address) (*lpTypes.Transcoder, error) {
args := m.Called()
return args.Get(0).(*lpTypes.Transcoder), args.Error(1)
}

func (m *MockClient) IsActiveTranscoder() (bool, error) {
args := m.Called()
return args.Get(0).(bool), args.Error(1)
}

func (m *MockClient) Reward() (*types.Transaction, error) {
args := m.Called()
return mockTransaction(args, 0), args.Error(1)
}

func (m *MockClient) GetTranscoderEarningsPoolForRound(address common.Address, round *big.Int) (*lpTypes.TokenPools, error) {
args := m.Called()
return args.Get(0).(*lpTypes.TokenPools), args.Error(1)
}

// RoundsManager

// InitializeRound submits a round initialization transaction
Expand Down Expand Up @@ -158,6 +178,11 @@ func (m *MockClient) CheckTx(tx *types.Transaction) error {
return args.Error(0)
}

func (m *MockClient) ReplaceTransaction(tx *types.Transaction, method string, gasPrice *big.Int) (*types.Transaction, error) {
args := m.Called()
return mockTransaction(args, 0), args.Error(1)
}

func (m *MockClient) Vote(pollAddr ethcommon.Address, choiceID *big.Int) (*types.Transaction, error) {
args := m.Called()
return mockTransaction(args, 0), args.Error(1)
Expand Down

0 comments on commit 93ca0e2

Please sign in to comment.