Skip to content

Commit

Permalink
Merge pull request #12 from flare-foundation/staking-clients
Browse files Browse the repository at this point in the history
Test for uptime voting cronjob
  • Loading branch information
mboben authored Sep 7, 2023
2 parents 6090c08 + 43289fd commit ecad231
Show file tree
Hide file tree
Showing 20 changed files with 1,449 additions and 890 deletions.
6 changes: 6 additions & 0 deletions database/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,9 @@ func FetchUptimes(db *gorm.DB, nodeIDs []string, start time.Time, end time.Time)
err := query.Find(&uptimes).Error
return uptimes, err
}

func FetchAggregations(db *gorm.DB) ([]*UptimeAggregation, error) {
var aggregations []*UptimeAggregation
err := db.Find(&aggregations).Error
return aggregations, err
}
4 changes: 2 additions & 2 deletions indexer/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ type VotingConfig struct {
}

type EpochConfig struct {
Period time.Duration `toml:"epoch_period" envconfig:"EPOCH_PERIOD"`
Start utils.Timestamp `toml:"epoch_time" envconfig:"EPOCH_TIME"`
Period time.Duration `toml:"period" envconfig:"EPOCH_PERIOD"`
Start utils.Timestamp `toml:"start" envconfig:"EPOCH_TIME"`
}

type UptimeConfig struct {
Expand Down
12 changes: 12 additions & 0 deletions indexer/cronjob/.snapshots/TestUptimeVoting
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
([]string) (len=4) {
(string) (len=40) "NodeID-GWPcbFJZFfZreETSoWjPimr846mXEKCtu",
(string) (len=40) "NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ",
(string) (len=40) "NodeID-NFBbbJ4qCmNaCzeW7sxErhvWqvEQMnYcN",
(string) (len=40) "NodeID-P7oB2McjBGgW2NXXWVYjV8JEDFoW9xDE5"
}
([]int64) (len=4) {
(int64) 90,
(int64) 90,
(int64) 90,
(int64) 40
}
4 changes: 4 additions & 0 deletions indexer/cronjob/.snapshots/TestVoting
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
([32]uint8) (len=32) {
00000000 2b 2e 37 55 43 1e 55 53 ee 31 d3 cd 2d 5c 57 20 |+.7UC.US.1..-\W |
00000010 31 1d aa f5 c7 d2 a2 fd 43 07 da bf c3 44 07 c4 |1.......C....D..|
}
16 changes: 16 additions & 0 deletions indexer/cronjob/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import (
"testing"
)

const (
privateKey1 = "0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb"
privateKey2 = "0x23c601ae397441f3ef6f1075dcb0031ff17fb079837beadaf3c84d96c6f3e569"
)

var (
testClient *chain.RecordedIndexerClient //:= chain.PChainTestClient(t)
testRPCClient *chain.RecordedRPCClient //:= chain.PChainTestRPCClient(t)
testUptimeClient *chain.RecordedUptimeClient
)

Expand All @@ -16,5 +23,14 @@ func TestMain(m *testing.M) {
if err != nil {
log.Fatal(err)
}
testClient, err = chain.PChainTestClient()
if err != nil {
log.Fatal(err)
}

testRPCClient, err = chain.PChainTestRPCClient()
if err != nil {
log.Fatal(err)
}
m.Run()
}
82 changes: 39 additions & 43 deletions indexer/cronjob/mirror.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cronjob

import (
"context"
"flare-indexer/database"
"flare-indexer/indexer/config"
indexerctx "flare-indexer/indexer/context"
Expand All @@ -16,7 +15,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"gorm.io/gorm"
)

Expand Down Expand Up @@ -179,47 +177,23 @@ type epochRange struct {

var errNoEpochsToMirror = errors.New("no epochs to mirror")

func (e *epochRange) validate() error {
if e.start > e.end {
return errNoEpochsToMirror
}

return nil
}

func (c *mirrorCronJob) getEpochRange() (*epochRange, error) {
epochRange := new(epochRange)
eg, ctx := errgroup.WithContext(context.Background())

eg.Go(func() error {
startEpoch, err := c.getStartEpoch()
if err != nil {
return err
}

epochRange.start = startEpoch
return nil
})

eg.Go(func() error {
endEpoch, err := c.getEndEpoch(ctx)
if err != nil {
return err
}

epochRange.end = endEpoch
return nil
})

if err := eg.Wait(); err != nil {
startEpoch, err := c.getStartEpoch()
if err != nil {
return nil, err
}

if err := epochRange.validate(); err != nil {
logger.Debug("start epoch: %d", startEpoch)

endEpoch, err := c.getEndEpoch(startEpoch)
if err != nil {
return nil, err
}

return epochRange, nil
return &epochRange{
start: startEpoch,
end: endEpoch,
}, nil
}

func (c *mirrorCronJob) getStartEpoch() (int64, error) {
Expand All @@ -231,11 +205,12 @@ func (c *mirrorCronJob) getStartEpoch() (int64, error) {
return int64(jobState.NextDBIndex), nil
}

func (c *mirrorCronJob) getEndEpoch(ctx context.Context) (int64, error) {
func (c *mirrorCronJob) getEndEpoch(startEpoch int64) (int64, error) {
currEpoch := c.epochs.getCurrentEpoch()
logger.Debug("current epoch: %d", currEpoch)

for epoch := currEpoch; epoch > 0; epoch-- {
confirmed, err := c.isEpochConfirmed(ctx, epoch)
for epoch := currEpoch; epoch > startEpoch; epoch-- {
confirmed, err := c.isEpochConfirmed(epoch)
if err != nil {
return 0, err
}
Expand All @@ -248,9 +223,8 @@ func (c *mirrorCronJob) getEndEpoch(ctx context.Context) (int64, error) {
return 0, errNoEpochsToMirror
}

func (c *mirrorCronJob) isEpochConfirmed(ctx context.Context, epoch int64) (bool, error) {
opts := &bind.CallOpts{Context: ctx}
merkleRoot, err := c.votingContract.GetMerkleRoot(opts, big.NewInt(epoch))
func (c *mirrorCronJob) isEpochConfirmed(epoch int64) (bool, error) {
merkleRoot, err := c.votingContract.GetMerkleRoot(new(bind.CallOpts), big.NewInt(epoch))
if err != nil {
return false, errors.Wrap(err, "votingContract.GetMerkleRoot")
}
Expand Down Expand Up @@ -299,6 +273,10 @@ func (c *mirrorCronJob) mirrorTxs(txs []database.PChainTxData, epochID int64) er
return err
}

if err := c.checkMerkleRoot(merkleTree, epochID); err != nil {
return err
}

for i := range txs {
in := mirrorTxInput{
epochID: big.NewInt(epochID),
Expand All @@ -314,6 +292,24 @@ func (c *mirrorCronJob) mirrorTxs(txs []database.PChainTxData, epochID int64) er
return nil
}

func (c *mirrorCronJob) checkMerkleRoot(tree merkle.Tree, epoch int64) error {
root, err := tree.Root()
if err != nil {
return err
}

contractRoot, err := c.votingContract.GetMerkleRoot(new(bind.CallOpts), big.NewInt(epoch))
if err != nil {
return errors.Wrap(err, "votingContract.GetMerkleRoot")
}

if root != contractRoot {
return errors.Errorf("merkle root mismatch: got %x, expected %x", root, contractRoot)
}

return nil
}

type mirrorTxInput struct {
epochID *big.Int
merkleTree merkle.Tree
Expand All @@ -331,7 +327,7 @@ func (c *mirrorCronJob) mirrorTx(in *mirrorTxInput) error {
return err
}

_, err = c.mirroringContract.VerifyStake(c.txOpts, *stakeData, merkleProof)
_, err = c.mirroringContract.MirrorStake(c.txOpts, *stakeData, merkleProof)
if err != nil {
return errors.Wrap(err, "mirroringContract.VerifyStake")
}
Expand Down
7 changes: 3 additions & 4 deletions indexer/cronjob/uptime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"flare-indexer/database"
"flare-indexer/indexer/config"
"flare-indexer/indexer/context"
"flare-indexer/utils"
"testing"
"time"
)
Expand Down Expand Up @@ -53,7 +52,7 @@ func TestUptime(t *testing.T) {
t.Fatal(err)
}

now := utils.ParseTime("2023-02-02T14:29:50Z")
now := time.Unix(1675348249, 0)
testUptimeClient.SetNow(now)

for i := 0; i < 100; i++ {
Expand All @@ -67,7 +66,7 @@ func TestUptime(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(uptimes) != 6 {
t.Fatalf("expected 6 uptimes, got %d", len(uptimes))
if len(uptimes) != 8 {
t.Fatalf("expected 8 uptimes, got %d", len(uptimes))
}
}
7 changes: 5 additions & 2 deletions indexer/cronjob/uptime_voting.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ type uptimeVotingCronjob struct {
txOpts *bind.TransactOpts

db *gorm.DB

// For testing to set "now" to some past date
time utils.ShiftedTime
}

func NewUptimeVotingCronjob(ctx context.IndexerContext) (Cronjob, error) {
func NewUptimeVotingCronjob(ctx context.IndexerContext) (*uptimeVotingCronjob, error) {
cfg := ctx.Config()

if !cfg.UptimeCronjob.EnableVoting {
Expand Down Expand Up @@ -93,7 +96,7 @@ func (c *uptimeVotingCronjob) OnStart() error {
}

func (c *uptimeVotingCronjob) Call() error {
now := time.Now()
now := c.time.Now()
firstEpochToAggregate, lastEpochToAggregate, err := c.aggregationRange(now)
if err != nil {
if err == errNoEpochsToAggregate {
Expand Down
129 changes: 129 additions & 0 deletions indexer/cronjob/uptime_voting_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package cronjob

import (
globalConfig "flare-indexer/config"
"flare-indexer/database"
"flare-indexer/indexer/config"
"flare-indexer/indexer/context"
"flare-indexer/indexer/pchain"
"flare-indexer/indexer/shared"
"flare-indexer/utils"
"sort"
"testing"
"time"

"github.com/bradleyjkemp/cupaloy"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func uptimeVotingCronjobTestConfig(epochStart time.Time) *config.Config {
cfg := &config.Config{
Chain: globalConfig.ChainConfig{
ChainAddressHRP: "localflare",
ChainID: 31337,
EthRPCURL: "http://127.0.0.1:8545",
PrivateKey: "0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb",
},
UptimeCronjob: config.UptimeConfig{
CronjobConfig: config.CronjobConfig{
Enabled: true,
TimeoutSeconds: 30,
},
EpochConfig: config.EpochConfig{
Start: utils.Timestamp{Time: epochStart},
Period: 90 * time.Second,
},
VotingInterval: 60 * time.Second,
EnableVoting: true,
UptimeThreshold: 0.8,
},
VotingCronjob: config.VotingConfig{
ContractAddress: common.HexToAddress("0x7c2C195CD6D34B8F845992d380aADB2730bB9C6F"),
},
PChainIndexer: config.IndexerConfig{
Enabled: true,
TimeoutMillis: 3000,
BatchSize: 200,
StartIndex: 0,
},
DB: globalConfig.DBConfig{
Username: database.MysqlTestUser,
Password: database.MysqlTestPassword,
Host: database.MysqlTestHost,
Port: database.MysqlTestPort,
Database: "flare_indexer_indexer",
LogQueries: false,
},
}
return cfg

}

func createTestUptimeVotingCronjob(epochStart time.Time) (*uptimeVotingCronjob, *shared.ChainIndexerBase, error) {
ctx, err := context.BuildTestContext(uptimeVotingCronjobTestConfig(epochStart))
if err != nil {
return nil, nil, err
}
cronjob, err := NewUptimeVotingCronjob(ctx)
if err != nil {
return nil, nil, err
}

indexer := &shared.ChainIndexerBase{
StateName: pchain.StateName,
IndexerName: "P-chain Blocks Test",
Client: testClient,
DB: ctx.DB(),
Config: ctx.Config().PChainIndexer,
BatchIndexer: pchain.NewPChainBatchIndexer(ctx, testClient, testRPCClient),
}
return cronjob, indexer, nil
}

// Requires a running hardhat node
// from the flare-smart-contracts project, branch origin/staking-tests
// with yarn staking_test
func TestUptimeVoting(t *testing.T) {
now := time.Unix(1675348249, 0)

// Epoch starts "now"
votingCronjob, indexer, err := createTestUptimeVotingCronjob(now)
require.NoError(t, err)

uptimeCronjob, err := createTestUptimeCronjob()
require.NoError(t, err)

// Run indexer to allow uptime client test to fetch validator data
err = indexer.IndexBatch()
require.NoError(t, err)

testUptimeClient.SetNow(now)
votingCronjob.time.SetNow(now)
for i := 0; i < 10; i++ {
if err := uptimeCronjob.Call(); err != nil {
t.Fatal(err)
}
if err := votingCronjob.Call(); err != nil {
t.Fatal(err)
}
testUptimeClient.Time.AdvanceNow(10 * time.Second)
votingCronjob.time.AdvanceNow(10 * time.Second)
}
aggr, err := database.FetchAggregations(votingCronjob.db)
require.NoError(t, err)
assert.Equal(t, 4, len(aggr))

// Sort by nodeID and compare to snapshots
sort.Slice(aggr, func(i, j int) bool {
return aggr[i].NodeID < aggr[j].NodeID
})
aggrNodeIDs := utils.Map(aggr, func(a *database.UptimeAggregation) string {
return a.NodeID
})
aggrValue := utils.Map(aggr, func(a *database.UptimeAggregation) int64 {
return a.Value
})
cupaloy.SnapshotT(t, aggrNodeIDs, aggrValue)
}
Loading

0 comments on commit ecad231

Please sign in to comment.