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

feat: store rotation period #26

Merged
merged 5 commits into from
Jan 8, 2024
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
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ test:
./scripts/tests.sh

genmocks:
mockgen -source=./chains/evm/listener/events/handlers/deposit.go -destination=./mock/deposit.go -package mock
mockgen -source=./chains/evm/listener/events/handlers/rotate.go -destination=./mock/rotate.go -package mock
mockgen -source=./chains/evm/listener/handlers/step.go -destination=./mock/step.go -package mock
mockgen -source=./chains/evm/listener/handlers/rotate.go -destination=./mock/rotate.go -package mock
mockgen -source=./chains/evm/listener/listener.go -destination=./mock/listener.go -package mock
mockgen -source=./chains/evm/executor/executor.go -destination=./mock/executor.go -package mock
mockgen -source=./chains/evm/prover/prover.go -destination=./mock/prover.go -package mock

mockgen -destination=./mock/store.go -package mock github.com/sygmaprotocol/sygma-core/store KeyValueReaderWriter

PLATFORMS := linux/amd64 darwin/amd64 darwin/arm64 linux/arm
temp = $(subst /, ,$@)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package handlers

import (
"context"
"math/big"

"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
Expand All @@ -19,32 +20,47 @@ type SyncCommitteeFetcher interface {
SyncCommittee(ctx context.Context, opts *api.SyncCommitteeOpts) (*api.Response[*apiv1.SyncCommittee], error)
}

type PeriodStorer interface {
Period(domainID uint8) (*big.Int, error)
StorePeriod(domainID uint8, period *big.Int) error
}

type RotateHandler struct {
domainID uint8
domains []uint8
msgChan chan []*message.Message

prover Prover
prover Prover
periodStorer PeriodStorer

syncCommitteeFetcher SyncCommitteeFetcher
currentSyncCommittee *apiv1.SyncCommittee
committeePeriodLength uint64
}

func NewRotateHandler(msgChan chan []*message.Message, syncCommitteeFetcher SyncCommitteeFetcher, prover Prover, domainID uint8, domains []uint8) *RotateHandler {
func NewRotateHandler(msgChan chan []*message.Message, periodStorer PeriodStorer, prover Prover, domainID uint8, domains []uint8, committeePeriodLenght uint64) *RotateHandler {
return &RotateHandler{
syncCommitteeFetcher: syncCommitteeFetcher,
prover: prover,
domainID: domainID,
domains: domains,
msgChan: msgChan,
currentSyncCommittee: &apiv1.SyncCommittee{},
prover: prover,
periodStorer: periodStorer,
domainID: domainID,
domains: domains,
msgChan: msgChan,
committeePeriodLength: committeePeriodLenght,
}
}

// HandleEvents checks if there is a new sync committee and sends a rotate message
// if there is
// HandleEvents checks if the current period is newer than the last stored
// period and rotates the committee if it is
func (h *RotateHandler) HandleEvents(checkpoint *apiv1.Finality) error {
args, err := h.prover.RotateArgs(uint64(checkpoint.Finalized.Epoch))
latestPeriod, err := h.periodStorer.Period(h.domainID)
if err != nil {
return err
}
currentPeriod := uint64(checkpoint.Finalized.Epoch) / h.committeePeriodLength
if currentPeriod <= latestPeriod.Uint64() {
return nil
}

targetPeriod := latestPeriod.Add(latestPeriod, big.NewInt(1))
args, err := h.prover.RotateArgs(targetPeriod.Uint64())
if err != nil {
return err
}
Expand All @@ -61,17 +77,7 @@ func (h *RotateHandler) HandleEvents(checkpoint *apiv1.Finality) error {
Spec: args.Spec,
}

syncCommittee, err := h.syncCommitteeFetcher.SyncCommittee(context.Background(), &api.SyncCommitteeOpts{
State: "finalized",
})
if err != nil {
return err
}
if syncCommittee.Data.String() == h.currentSyncCommittee.String() {
return nil
}

log.Info().Uint8("domainID", h.domainID).Msgf("Rotating committee")
log.Info().Uint8("domainID", h.domainID).Uint64("period", targetPeriod.Uint64()).Msgf("Rotating committee")

rotateProof, err := h.prover.RotateProof(args)
if err != nil {
Expand All @@ -98,5 +104,5 @@ func (h *RotateHandler) HandleEvents(checkpoint *apiv1.Finality) error {
}
}

return nil
return h.periodStorer.StorePeriod(h.domainID, targetPeriod)
}
116 changes: 116 additions & 0 deletions chains/evm/listener/handlers/rotate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// The Licensed Work is (c) 2023 Sygma
// SPDX-License-Identifier: LGPL-3.0-only

package handlers_test

import (
"fmt"
"math/big"
"testing"

apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/stretchr/testify/suite"
"github.com/sygmaprotocol/spectre-node/chains/evm/listener/handlers"
evmMessage "github.com/sygmaprotocol/spectre-node/chains/evm/message"
"github.com/sygmaprotocol/spectre-node/chains/evm/prover"
"github.com/sygmaprotocol/spectre-node/mock"
"github.com/sygmaprotocol/sygma-core/relayer/message"
consensus "github.com/umbracle/go-eth-consensus"
"go.uber.org/mock/gomock"
)

func readFromChannel(msgChan chan []*message.Message) ([]*message.Message, error) {
select {
case msgs := <-msgChan:
return msgs, nil
default:
return make([]*message.Message, 0), fmt.Errorf("no message sent")
}
}

type RotateHandlerTestSuite struct {
suite.Suite

handler *handlers.RotateHandler

msgChan chan []*message.Message
mockProver *mock.MockProver
mockPeriodStorer *mock.MockPeriodStorer
}

func TestRunRotateTestSuite(t *testing.T) {
suite.Run(t, new(RotateHandlerTestSuite))
}

func (s *RotateHandlerTestSuite) SetupTest() {
ctrl := gomock.NewController(s.T())
s.mockProver = mock.NewMockProver(ctrl)
s.mockPeriodStorer = mock.NewMockPeriodStorer(ctrl)
s.msgChan = make(chan []*message.Message, 2)
s.handler = handlers.NewRotateHandler(
s.msgChan,
s.mockPeriodStorer,
s.mockProver,
1,
[]uint8{2, 3},
256,
)
}

func (s *RotateHandlerTestSuite) Test_HandleEvents_PeriodFetchingFails() {
s.mockPeriodStorer.EXPECT().Period(uint8(1)).Return(nil, fmt.Errorf("error"))

err := s.handler.HandleEvents(&apiv1.Finality{})
s.NotNil(err)

_, err = readFromChannel(s.msgChan)
s.NotNil(err)
}

func (s *RotateHandlerTestSuite) Test_HandleEvents_CurrentPeriodOlderThanLatest() {
s.mockPeriodStorer.EXPECT().Period(uint8(1)).Return(big.NewInt(2), nil)

err := s.handler.HandleEvents(&apiv1.Finality{
Finalized: &phase0.Checkpoint{
Epoch: phase0.Epoch(300),
},
})
s.Nil(err)

_, err = readFromChannel(s.msgChan)
s.NotNil(err)
}

func (s *RotateHandlerTestSuite) Test_HandleEvents_ValidPeriod() {
s.mockPeriodStorer.EXPECT().Period(uint8(1)).Return(big.NewInt(2), nil)
s.mockPeriodStorer.EXPECT().StorePeriod(uint8(1), big.NewInt(3)).Return(nil)
s.mockProver.EXPECT().RotateArgs(uint64(3)).Return(&prover.RotateArgs{
Update: &consensus.LightClientUpdateCapella{},
Domain: phase0.Domain{},
Spec: "mainnet",
Pubkeys: [512][48]byte{},
}, nil)
s.mockProver.EXPECT().RotateProof(gomock.Any()).Return(&prover.EvmProof[evmMessage.RotateInput]{
Proof: []byte{},
Input: evmMessage.RotateInput{},
}, nil)
s.mockProver.EXPECT().StepProof(gomock.Any()).Return(&prover.EvmProof[evmMessage.SyncStepInput]{
Proof: []byte{},
Input: evmMessage.SyncStepInput{},
}, nil)

err := s.handler.HandleEvents(&apiv1.Finality{
Finalized: &phase0.Checkpoint{
Epoch: phase0.Epoch(1500),
},
})
s.Nil(err)

msg1, err := readFromChannel(s.msgChan)
s.Nil(err)
s.Equal(len(msg1), 1)
msg2, err := readFromChannel(s.msgChan)
s.Nil(err)
s.Equal(len(msg2), 1)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type BlockFetcher interface {
SignedBeaconBlock(ctx context.Context, opts *api.SignedBeaconBlockOpts) (*api.Response[*spec.VersionedSignedBeaconBlock], error)
}

type DepositEventHandler struct {
type StepEventHandler struct {
msgChan chan []*message.Message

eventFetcher EventFetcher
Expand All @@ -50,7 +50,7 @@ type DepositEventHandler struct {
routerAddress common.Address
}

func NewDepositEventHandler(
func NewStepEventHandler(
msgChan chan []*message.Message,
eventFetcher EventFetcher,
blockFetcher BlockFetcher,
Expand All @@ -59,9 +59,9 @@ func NewDepositEventHandler(
domainID uint8,
domains []uint8,
blockInterval uint64,
) *DepositEventHandler {
) *StepEventHandler {
routerABI, _ := ethereumABI.JSON(strings.NewReader(abi.RouterABI))
return &DepositEventHandler{
return &StepEventHandler{
eventFetcher: eventFetcher,
blockFetcher: blockFetcher,
prover: prover,
Expand All @@ -74,9 +74,8 @@ func NewDepositEventHandler(
}
}

// HandleEvents fetches deposit events and if deposits exists, submits a step message
// to be executed on the destination network
func (h *DepositEventHandler) HandleEvents(checkpoint *apiv1.Finality) error {
// HandleEvents executes the step for the latest finality checkpoint
func (h *StepEventHandler) HandleEvents(checkpoint *apiv1.Finality) error {
args, err := h.prover.StepArgs()
if err != nil {
return err
Expand Down
16 changes: 6 additions & 10 deletions chains/evm/prover/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,20 @@ type Prover struct {
beaconClient BeaconClient
proverClient ProverClient

spec Spec
committeePeriodLength uint64
spec Spec
}

func NewProver(
proverClient ProverClient,
beaconClient BeaconClient,
lightClient LightClient,
spec Spec,
committeePeriodLength uint64,
) *Prover {
return &Prover{
proverClient: proverClient,
spec: spec,
committeePeriodLength: committeePeriodLength,
beaconClient: beaconClient,
lightClient: lightClient,
proverClient: proverClient,
spec: spec,
beaconClient: beaconClient,
lightClient: lightClient,
}
}

Expand Down Expand Up @@ -209,8 +206,7 @@ func (p *Prover) StepArgs() (*StepArgs, error) {
}, nil
}

func (p *Prover) RotateArgs(epoch uint64) (*RotateArgs, error) {
period := epoch / p.committeePeriodLength
func (p *Prover) RotateArgs(period uint64) (*RotateArgs, error) {
updates, err := p.lightClient.Updates(period)
if err != nil {
return nil, err
Expand Down
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const PREFIX = "SPECTRE"
type Config struct {
Observability *Observability `env_config:"observability"`
Prover *Prover `env_config:"prover"`
Store *Store `env_config:"store"`
Domains map[uint8]string `required:"true"`
}

Expand All @@ -23,6 +24,10 @@ type Prover struct {
URL string `required:"true"`
}

type Store struct {
Path string `default:"./lvldbdata"`
}

// LoadConfig loads config from the environment and validates the fields
func LoadConfig() (*Config, error) {
var c Config
Expand Down
7 changes: 7 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func (s *ConfigTestSuite) Test_LoadConfig_DefaultValues() {
Prover: &config.Prover{
URL: "http://prover.com",
},
Store: &config.Store{
Path: "./lvldbdata",
},
Domains: domains,
})
}
Expand All @@ -56,6 +59,7 @@ func (s *ConfigTestSuite) Test_LoadEVMConfig_SuccessfulLoad() {
os.Setenv("SPECTRE_OBSERVABILITY_LOG_LEVEL", "info")
os.Setenv("SPECTRE_OBSERVABILITY_LOG_FILE", "out2.log")
os.Setenv("SPECTRE_OBSERVABILITY_HEALTH_PORT", "9003")
os.Setenv("SPECTRE_STORE_PATH", "./custom_path")
os.Setenv("SPECTRE_PROVER_URL", "http://prover.com")
os.Setenv("SPECTRE_DOMAINS", "1:evm,2:evm")

Expand All @@ -74,6 +78,9 @@ func (s *ConfigTestSuite) Test_LoadEVMConfig_SuccessfulLoad() {
Prover: &config.Prover{
URL: "http://prover.com",
},
Store: &config.Store{
Path: "./custom_path",
},
Domains: domains,
})
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/rs/zerolog v1.31.0
github.com/stretchr/testify v1.8.4
github.com/sygmaprotocol/sygma-core v0.0.0-20231023115554-62219e098d0d
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a
github.com/umbracle/go-eth-consensus v0.1.3-0.20230605085523-929b6624372a
github.com/ybbus/jsonrpc/v3 v3.1.5
go.uber.org/mock v0.3.0
Expand Down Expand Up @@ -59,7 +60,6 @@ require (
github.com/r3labs/sse/v2 v2.10.0 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/supranational/blst v0.3.11 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
Expand Down
Loading
Loading