Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vote module test #221

Merged
merged 2 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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