Skip to content

Commit

Permalink
Add vote module tests (#221)
Browse files Browse the repository at this point in the history
* Init commen test

* Add vote module tests
  • Loading branch information
dadamu authored Apr 27, 2022
1 parent 0365c52 commit 35e4ca1
Show file tree
Hide file tree
Showing 12 changed files with 568 additions and 134 deletions.
2 changes: 1 addition & 1 deletion db/migration/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func Up(db db.ExcecutorDb) error {
$$
SELECT * FROM transaction WHERE
(slot < "end_slot" AND slot >= "start_slot") AND
involved_accounts @> addresses
involved_accounts @> addresses ORDER BY slot+0 DESC
$$ LANGUAGE sql STABLE;
`)
return err
Expand Down
2 changes: 1 addition & 1 deletion db/schema/00-solana.sql
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ CREATE FUNCTION transactions_by_address(
$$
SELECT * FROM transaction WHERE
(slot <= "end_slot" AND slot >= "start_slot") AND
involved_accounts @> addresses
involved_accounts @> addresses ORDER BY slot+0 DESC
$$ LANGUAGE sql STABLE;

/**
Expand Down
10 changes: 4 additions & 6 deletions modules/bank/handle_periodic_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bank
import (
"fmt"

"github.com/forbole/soljuno/modules/utils"
"github.com/go-co-op/gocron"
"github.com/rs/zerolog/log"
)
Expand All @@ -12,24 +13,21 @@ import (
func (m *Module) RegisterPeriodicOperations(scheduler *gocron.Scheduler) error {
log.Debug().Str("module", m.Name()).Msg("setting up periodic tasks")
if _, err := scheduler.Every(10).Second().Do(func() {
m.HandlePeriodicOperations()
utils.WatchMethod(m, m.HandlePeriodicOperations)
}); err != nil {
return fmt.Errorf("error while setting up bank periodic operation: %s", err)
}
return nil
}

func (m *Module) HandlePeriodicOperations() {
func (m *Module) HandlePeriodicOperations() error {
m.mtx.Lock()
defer m.mtx.Unlock()
balances := m.BalanceEntries
tokenBalances := m.TokenBalanceEntries
m.BalanceEntries = nil
m.TokenBalanceEntries = nil
err := m.updateBalances(balances, tokenBalances)
if err != nil {
log.Error().Str("module", m.Name()).Err(err).Send()
}
return m.updateBalances(balances, tokenBalances)
}

func (m *Module) updateBalances(balances []AccountBalanceEntry, tokenBalances []TokenAccountBalanceEntry) error {
Expand Down
108 changes: 108 additions & 0 deletions modules/vote/commen_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package vote_test

import (
"testing"

"github.com/stretchr/testify/suite"

"github.com/forbole/soljuno/db"
dbtypes "github.com/forbole/soljuno/db/types"
"github.com/forbole/soljuno/modules/vote"
clienttypes "github.com/forbole/soljuno/solana/client/types"
)

var _ db.VoteDb = &MockDb{}

type MockDb struct {
isLatest bool
err error
}

func NewDefaultMockDb() *MockDb {
return &MockDb{isLatest: true}
}

func (db *MockDb) SaveValidator(account dbtypes.VoteAccountRow) error { return db.err }
func (db *MockDb) SaveValidatorStatuses(statuses []dbtypes.ValidatorStatusRow) error { return db.err }
func (db *MockDb) GetEpochProducedBlocks(epoch uint64) ([]uint64, error) { return []uint64{0}, db.err }
func (db *MockDb) SaveValidatorSkipRates(skipRates []dbtypes.ValidatorSkipRateRow) error {
return db.err
}
func (db *MockDb) SaveHistoryValidatorSkipRates(skipRates []dbtypes.ValidatorSkipRateRow) error {
return db.err
}

func (db *MockDb) CheckValidatorLatest(address string, currentSlot uint64) bool {
return db.isLatest
}

func (m MockDb) GetCached() MockDb {
return m
}

func (m *MockDb) WithError(err error) {
m.err = err
}

func (m *MockDb) WithLatest(isLatest bool) {
m.isLatest = isLatest
}

// ----------------------------------------------------------------

var _ vote.ClientProxy = &MockClient{}

type MockClient struct {
account clienttypes.AccountInfo
err error
}

func NewDefaultMockClient() *MockClient {
return &MockClient{}
}

func (m MockClient) GetCached() MockClient {
return m
}

func (m *MockClient) WithError(err error) {
m.err = err
}

func (m *MockClient) WithAccount(account clienttypes.AccountInfo) {
m.account = account
}

func (m *MockClient) GetAccountInfo(address string) (clienttypes.AccountInfo, error) {
return m.account, m.err
}

func (m *MockClient) GetVoteAccountsWithSlot() (uint64, clienttypes.VoteAccounts, error) {
return 0, clienttypes.VoteAccounts{
Current: []clienttypes.VoteAccount{{VotePubkey: "current"}},
Delinquent: []clienttypes.VoteAccount{{VotePubkey: "delinquent"}},
}, m.err
}

func (m *MockClient) GetLeaderSchedule(uint64) (clienttypes.LeaderSchedule, error) {
return clienttypes.LeaderSchedule{"address": []int{0, 1}}, m.err
}

// ----------------------------------------------------------------

type ModuleTestSuite struct {
suite.Suite
module *vote.Module
db *MockDb
client *MockClient
}

func TestModuleTestSuite(t *testing.T) {
suite.Run(t, new(ModuleTestSuite))
}

func (suite *ModuleTestSuite) SetupTest() {
suite.module = vote.NewModule(NewDefaultMockDb(), NewDefaultMockClient())
suite.db = NewDefaultMockDb()
suite.client = NewDefaultMockClient()
}
63 changes: 1 addition & 62 deletions modules/vote/epoch_service.go
Original file line number Diff line number Diff line change
@@ -1,66 +1,5 @@
package vote

import (
"fmt"

dbtypes "github.com/forbole/soljuno/db/types"
solanatypes "github.com/forbole/soljuno/solana/types"
)

func (m *Module) ExecEpoch(epoch uint64) error {
return m.updateValidatorSkipRates(epoch - 1)
}

// updateValidatorSkipRates properly stores the skip rates of all validators inside the database
func (m *Module) updateValidatorSkipRates(epoch uint64) error {
slots, err := m.db.GetEpochProducedBlocks(epoch)
if err != nil {
return err
}
if len(slots) == 0 {
return fmt.Errorf("%d epoch blocks does not exist", epoch)
}

endSlot := slots[len(slots)-1]
schedules, err := m.client.GetLeaderSchedule(epoch * solanatypes.SlotsInEpoch)
if err != nil {
return err
}

produced := make(map[int]bool)
for _, slot := range slots {
produced[int(slot%solanatypes.SlotsInEpoch)] = true
}

skipRateRows := make([]dbtypes.ValidatorSkipRateRow, len(schedules))
count := 0
end := int(endSlot % solanatypes.SlotsInEpoch)
for validator, schedule := range schedules {
total, skip := getSkipRateReference(end, produced, schedule)
skipRate := float64(skip) / float64(total)
skipRateRows[count] = dbtypes.NewValidatorSkipRateRow(validator, epoch, skipRate, total, skip)
count++
}

err = m.db.SaveValidatorSkipRates(skipRateRows)
if err != nil {
return err
}
return m.db.SaveHistoryValidatorSkipRates(skipRateRows)
}

// getSkipRateReference returns the total and skip amount in a epoch of the validator from the given produced map and the validator schedule
func getSkipRateReference(end int, produced map[int]bool, schedule []int) (int, int) {
var skip int = 0
var total int = 0
for _, slotInEpoch := range schedule {
total++
if slotInEpoch > end {
break
}
if ok := produced[slotInEpoch]; !ok {
skip++
}
}
return total, skip
return UpdateValidatorSkipRates(epoch-1, m.db, m.client)
}
39 changes: 19 additions & 20 deletions modules/vote/handle_instruction.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,89 @@ import (

"github.com/forbole/soljuno/db"
dbtypes "github.com/forbole/soljuno/db/types"
"github.com/forbole/soljuno/solana/client"
"github.com/forbole/soljuno/solana/program/vote"
"github.com/forbole/soljuno/types"
)

// HandleInstruction allows to handle different instructions types for the vote module
func HandleInstruction(instruction types.Instruction, tx types.Tx, db db.VoteDb, client client.ClientProxy) error {
func HandleInstruction(instruction types.Instruction, db db.VoteDb, client ClientProxy) error {
switch instruction.Parsed.Type {
case "initialize":
return handleInitialize(instruction, tx, db)
return handleInitialize(instruction, db)
case "authorize":
return handleAuthorize(instruction, tx, db, client)
return handleAuthorize(instruction, db, client)
case "withdraw":
return handleWithdraw(instruction, tx, db, client)
return handleWithdraw(instruction, db, client)
case "updateValidatorIdentity":
return handleUpdateValidatorIdentity(instruction, tx, db, client)
return handleUpdateValidatorIdentity(instruction, db, client)
case "updateCommission":
return handleUpdateCommission(instruction, tx, db, client)
return handleUpdateCommission(instruction, db, client)
case "authorizeChecked":
return handleAuthorizeChecked(instruction, tx, db, client)
return handleAuthorizeChecked(instruction, db, client)
}
return nil
}

// handleInitialize handles a instruction of Initialize
func handleInitialize(instruction types.Instruction, tx types.Tx, db db.VoteDb) error {
func handleInitialize(instruction types.Instruction, db db.VoteDb) error {
parsed, ok := instruction.Parsed.Value.(vote.ParsedInitializeAccount)
if !ok {
return fmt.Errorf("instruction does not match %s type: %s", "initialize", instruction.Parsed.Type)

}
return db.SaveValidator(
dbtypes.NewVoteAccountRow(
parsed.VoteAccount, tx.Slot, parsed.Node, parsed.AuthorizedVoter, parsed.AuthorizedWithdrawer, parsed.Commission,
parsed.VoteAccount, instruction.Slot, parsed.Node, parsed.AuthorizedVoter, parsed.AuthorizedWithdrawer, parsed.Commission,
),
)
}

// handleAuthorize handles a instruction of Authorize
func handleAuthorize(instruction types.Instruction, tx types.Tx, db db.VoteDb, client client.ClientProxy) error {
func handleAuthorize(instruction types.Instruction, db db.VoteDb, client ClientProxy) error {
parsed, ok := instruction.Parsed.Value.(vote.ParsedAuthorize)
if !ok {
return fmt.Errorf("instruction does not match %s type: %s", "authorize", instruction.Parsed.Type)

}
return updateVoteAccount(parsed.VoteAccount, tx.Slot, db, client)
return UpdateVoteAccount(parsed.VoteAccount, instruction.Slot, db, client)
}

// handleWithdraw handles a instruction of Withdraw
func handleWithdraw(instruction types.Instruction, tx types.Tx, db db.VoteDb, client client.ClientProxy) error {
func handleWithdraw(instruction types.Instruction, db db.VoteDb, client ClientProxy) error {
parsed, ok := instruction.Parsed.Value.(vote.ParsedWithdraw)
if !ok {
return fmt.Errorf("instruction does not match %s type: %s", "withdraw", instruction.Parsed.Type)

}
return updateVoteAccount(parsed.VoteAccount, tx.Slot, db, client)
return UpdateVoteAccount(parsed.VoteAccount, instruction.Slot, db, client)
}

// handleUpdateValidatorIdentity handles a instruction of UpdateValidatorIdentity
func handleUpdateValidatorIdentity(instruction types.Instruction, tx types.Tx, db db.VoteDb, client client.ClientProxy) error {
func handleUpdateValidatorIdentity(instruction types.Instruction, db db.VoteDb, client ClientProxy) error {
parsed, ok := instruction.Parsed.Value.(vote.ParsedUpdateValidatorIdentity)
if !ok {
return fmt.Errorf("instruction does not match %s type: %s", "updateValidatorIdentity", instruction.Parsed.Type)

}
return updateVoteAccount(parsed.VoteAccount, tx.Slot, db, client)
return UpdateVoteAccount(parsed.VoteAccount, instruction.Slot, db, client)
}

// handleUpdateCommission handles a instruction of UpdateCommission
func handleUpdateCommission(instruction types.Instruction, tx types.Tx, db db.VoteDb, client client.ClientProxy) error {
func handleUpdateCommission(instruction types.Instruction, db db.VoteDb, client ClientProxy) error {
parsed, ok := instruction.Parsed.Value.(vote.ParsedUpdateCommission)
if !ok {
return fmt.Errorf("instruction does not match %s type: %s", "updateCommission", instruction.Parsed.Type)

}
return updateVoteAccount(parsed.VoteAccount, tx.Slot, db, client)
return UpdateVoteAccount(parsed.VoteAccount, instruction.Slot, db, client)
}

// handleAuthorizeChecked handles a instruction of AuthorizeChecked
func handleAuthorizeChecked(instruction types.Instruction, tx types.Tx, db db.VoteDb, client client.ClientProxy) error {
func handleAuthorizeChecked(instruction types.Instruction, db db.VoteDb, client ClientProxy) error {
parsed, ok := instruction.Parsed.Value.(vote.ParsedAuthorizeChecked)
if !ok {
return fmt.Errorf("instruction does not match %s type: %s", "authorizeChecked", instruction.Parsed.Type)

}
return updateVoteAccount(parsed.VoteAccount, tx.Slot, db, client)
return UpdateVoteAccount(parsed.VoteAccount, instruction.Slot, db, client)
}
Loading

0 comments on commit 35e4ca1

Please sign in to comment.