Skip to content

Commit

Permalink
Merge branch 'andirus/pre-reg' into 'main'
Browse files Browse the repository at this point in the history
Add voter pre-registration: trigger on reward epoch start.

See merge request flarenetwork/flare-system-client!56
  • Loading branch information
GrePod committed Dec 19, 2024
2 parents b7deb36 + 86617f7 commit 6c15b0a
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 61 deletions.
13 changes: 7 additions & 6 deletions client/config/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,16 @@ type SubmitSignatures struct {
}

type Clients struct {
EnabledRegistration bool `toml:"enabled_registration"`
EnabledUptimeVoting bool `toml:"enabled_uptime_voting"`
EnabledRewardSigning bool `toml:"enabled_reward_signing"`
EnabledProtocolVoting bool `toml:"enabled_protocol_voting"`
EnabledFinalizer bool `toml:"enabled_finalizer"`
EnabledRegistration bool `toml:"enabled_registration"`
EnabledPreregistration bool `toml:"enabled_pre_registration"`
EnabledUptimeVoting bool `toml:"enabled_uptime_voting"`
EnabledRewardSigning bool `toml:"enabled_reward_signing"`
EnabledProtocolVoting bool `toml:"enabled_protocol_voting"`
EnabledFinalizer bool `toml:"enabled_finalizer"`
}

func (c *Clients) EpochClientEnabled() bool {
return c.EnabledRegistration || c.EnabledUptimeVoting || c.EnabledRewardSigning
return c.EnabledRegistration || c.EnabledUptimeVoting || c.EnabledRewardSigning || c.EnabledPreregistration
}

type Finalizer struct {
Expand Down
50 changes: 35 additions & 15 deletions client/epoch/epoch_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ type client struct {

identityAddress common.Address

registrationEnabled bool
uptimeVotingEnabled bool
rewardsSigningEnabled bool
preregistrationEnabled bool
registrationEnabled bool
uptimeVotingEnabled bool
rewardsSigningEnabled bool

rewardsConfig *clientConfig.RewardsConfig
}
Expand Down Expand Up @@ -87,6 +88,7 @@ func NewClient(ctx flarectx.ClientContext) (*client, error) {
ethClient,
&cfg.RegisterGas,
cfg.ContractAddresses.VoterRegistry,
cfg.ContractAddresses.VoterPreRegistry,
senderTxOpts,
signerPk,
)
Expand All @@ -102,15 +104,16 @@ func NewClient(ctx flarectx.ClientContext) (*client, error) {

db := epochClientDBGorm{db: ctx.DB()}
return &client{
db: db,
systemsManagerClient: systemsManagerClient,
relayClient: relayClient,
registryClient: registryClient,
identityAddress: identityAddress,
registrationEnabled: cfg.Clients.EnabledRegistration,
uptimeVotingEnabled: cfg.Clients.EnabledUptimeVoting,
rewardsSigningEnabled: cfg.Clients.EnabledRewardSigning,
rewardsConfig: &cfg.Rewards,
db: db,
systemsManagerClient: systemsManagerClient,
relayClient: relayClient,
registryClient: registryClient,
identityAddress: identityAddress,
preregistrationEnabled: cfg.Clients.EnabledPreregistration,
registrationEnabled: cfg.Clients.EnabledRegistration,
uptimeVotingEnabled: cfg.Clients.EnabledUptimeVoting,
rewardsSigningEnabled: cfg.Clients.EnabledRewardSigning,
rewardsConfig: &cfg.Rewards,
}, nil
}

Expand All @@ -123,11 +126,15 @@ func (c *client) Run(ctx context.Context) error {
return err
}

var epochStartedListener <-chan *system.FlareSystemsManagerRewardEpochStarted
var vpbsListener <-chan *system.FlareSystemsManagerVotePowerBlockSelected
var policyListener <-chan *relay.RelaySigningPolicyInitialized
var uptimeEnabledListener <-chan *system.FlareSystemsManagerSignUptimeVoteEnabled
var uptimeSignedListener <-chan *system.FlareSystemsManagerUptimeVoteSigned

if c.preregistrationEnabled {
epochStartedListener = c.systemsManagerClient.RewardEpochStartedListener(c.db, rewardEpochTiming)
}
if c.registrationEnabled {
logger.Info("Waiting for VotePowerBlockSelected event to start registration")
vpbsListener = c.systemsManagerClient.VotePowerBlockSelectedListener(c.db, rewardEpochTiming)
Expand All @@ -144,6 +151,9 @@ func (c *client) Run(ctx context.Context) error {

for {
select {
case rewardEpochStarted := <-epochStartedListener:
logger.Debugf("RewardEpochStarted event emitted for epoch %v", rewardEpochStarted.RewardEpochId)
c.preregisterVoter(new(big.Int).Add(rewardEpochStarted.RewardEpochId, big.NewInt(1)))
case powerBlockData := <-vpbsListener:
logger.Debugf("VotePowerBlockSelected event emitted for epoch %v", powerBlockData.RewardEpochId)
c.registerVoter(powerBlockData.RewardEpochId)
Expand All @@ -170,13 +180,23 @@ func (c *client) registerVoter(epochID *big.Int) {

logger.Infof("VotePowerBlockSelected event emitted for next epoch %v, starting registration", epochID)
registerResult := <-c.registryClient.RegisterVoter(epochID, c.identityAddress)
if registerResult.Success {
logger.Info("RegisterVoter success")
} else {
if !registerResult.Success {
logger.Errorf("RegisterVoter failed %s", registerResult.Message)
}
}

func (c *client) preregisterVoter(epochID *big.Int) {
if !c.isFutureEpoch(epochID) {
logger.Debugf("Skipping pre-registration process for old epoch %v", epochID)
return
}

registerResult := <-c.registryClient.PreregisterVoter(epochID, c.identityAddress)
if !registerResult.Success {
logger.Errorf("PreregisterVoter failed %s", registerResult.Message)
}
}

func (c *client) signPolicy(epochID *big.Int, policy []byte) {
if !c.isFutureEpoch(epochID) {
logger.Debugf("Skipping policy signing for old epoch %v", epochID)
Expand Down
10 changes: 10 additions & 0 deletions client/epoch/epoch_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,16 @@ func (c testRegistryClient) RegisterVoter(
}, 1, 0)
}

func (c testSystemsManagerClient) RewardEpochStartedListener(db epochClientDB, config *utils.EpochTimingConfig) <-chan *system.FlareSystemsManagerRewardEpochStarted {
return make(chan *system.FlareSystemsManagerRewardEpochStarted)
}

func (c testRegistryClient) PreregisterVoter(nextRewardEpochId *big.Int, address common.Address) <-chan shared.ExecuteStatus[any] {
return shared.ExecuteWithRetryChan(func() (any, error) {
return nil, nil
}, 1, 0)
}

func (c testSystemsManagerClient) SignUptimeVoteEnabledListener(db epochClientDB, epoch *utils.EpochTimingConfig) <-chan *system.FlareSystemsManagerSignUptimeVoteEnabled {
return make(chan *system.FlareSystemsManagerSignUptimeVoteEnabled)
}
Expand Down
163 changes: 131 additions & 32 deletions client/epoch/registry_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,27 @@ import (

"github.com/flare-foundation/go-flare-common/pkg/logger"

"github.com/flare-foundation/go-flare-common/pkg/contracts/preregistry"
"github.com/flare-foundation/go-flare-common/pkg/contracts/registry"
)

var (
registratorArguments abi.Arguments
fallbackGasPrice = big.NewInt(50 * 1e9)
registryAbi *abi.ABI
preregistryAbi *abi.ABI
)

var (
nonFatalRegisterErrors = []string{
"already registered",
"voter registration not enabled",
}
nonFatalPreregisterErrors = []string{
"voter already pre-registered",
"voter currently not registered",
"pre-registration not opened anymore",
}
)

func init() {
Expand Down Expand Up @@ -53,42 +67,57 @@ func init() {
// panic, this error is fatal
panic(err)
}
preregistryAbi, err = preregistry.PreregistryMetaData.GetAbi()
if err != nil {
// panic, this error is fatal
panic(err)
}

}

type registryContractClient interface {
RegisterVoter(nextRewardEpochId *big.Int, address common.Address) <-chan shared.ExecuteStatus[any]
PreregisterVoter(nextRewardEpochId *big.Int, address common.Address) <-chan shared.ExecuteStatus[any]
}

type registryContractClientImpl struct {
ethClient *ethclient.Client
address common.Address
registry *registry.Registry
senderTxOpts *bind.TransactOpts
gasCfg *config.Gas
txVerifier *chain.TxVerifier
signerPrivateKey *ecdsa.PrivateKey
ethClient *ethclient.Client
address common.Address
preregistryAddress common.Address
registry *registry.Registry
preregistry *preregistry.Preregistry
senderTxOpts *bind.TransactOpts
gasCfg *config.Gas
txVerifier *chain.TxVerifier
signerPrivateKey *ecdsa.PrivateKey
}

func NewRegistryContractClient(
ethClient *ethclient.Client,
gasCfg *config.Gas,
address common.Address,
registryAddress common.Address,
preregistryAddress common.Address,
senderTxOpts *bind.TransactOpts,
signerPk *ecdsa.PrivateKey,
) (*registryContractClientImpl, error) {
registry, err := registry.NewRegistry(address, ethClient)
registry, err := registry.NewRegistry(registryAddress, ethClient)
if err != nil {
return nil, err
}
preregistry, err := preregistry.NewPreregistry(preregistryAddress, ethClient)
if err != nil {
return nil, err
}
return &registryContractClientImpl{
ethClient: ethClient,
address: address,
registry: registry,
senderTxOpts: senderTxOpts,
gasCfg: gasCfg,
txVerifier: chain.NewTxVerifier(ethClient),
signerPrivateKey: signerPk,
ethClient: ethClient,
address: registryAddress,
preregistryAddress: preregistryAddress,
registry: registry,
preregistry: preregistry,
senderTxOpts: senderTxOpts,
gasCfg: gasCfg,
txVerifier: chain.NewTxVerifier(ethClient),
signerPrivateKey: signerPk,
}, nil

}
Expand All @@ -97,7 +126,11 @@ func (r *registryContractClientImpl) RegisterVoter(nextRewardEpochId *big.Int, a
return shared.ExecuteWithRetryChan(func() (any, error) {
err := r.sendRegisterVoter(nextRewardEpochId, address)
if err != nil {
return nil, errors.Wrap(err, "error sending register voter")
if shared.ExistsAsSubstring(nonFatalRegisterErrors, err.Error()) {
logger.Debugf("Non fatal error sending register voter: %v", err)
} else {
return nil, errors.Wrap(err, "error sending register voter")
}
}
return nil, nil
}, shared.MaxTxSendRetries, shared.TxRetryInterval)
Expand All @@ -122,24 +155,24 @@ func (r *registryContractClientImpl) sendRegisterVoter(nextRewardEpochId *big.In
}
r.senderTxOpts.GasPrice = gasPrice

estimatedGasLimit, err := chain.DryRunTxAbi(
r.ethClient,
chain.DefaultTxTimeout,
r.senderTxOpts.From,
r.address,
common.Big0,
registryAbi,
"registerVoter",
address,
vrsSignature,
)
if err != nil {
return errors.Wrap(err, "Dry run failed")
}

if r.gasCfg.GasLimit != 0 {
r.senderTxOpts.GasLimit = uint64(r.gasCfg.GasLimit)
} else {
estimatedGasLimit, err := chain.DryRunTxAbi(
r.ethClient,
chain.DefaultTxTimeout,
r.senderTxOpts.From,
r.address,
common.Big0,
registryAbi,
"registerVoter",
address,
vrsSignature,
)
if err != nil {
logger.Warnf("Dry run fail: %v", err)
return err
}
r.senderTxOpts.GasLimit = estimatedGasLimit
}

Expand All @@ -163,3 +196,69 @@ func (r *registryContractClientImpl) createSignature(nextRewardEpochId uint32, a
messageHash := crypto.Keccak256(message)
return crypto.Sign(accounts.TextHash(messageHash), r.signerPrivateKey)
}

func (r *registryContractClientImpl) PreregisterVoter(nextRewardEpochId *big.Int, address common.Address) <-chan shared.ExecuteStatus[any] {
return shared.ExecuteWithRetryChan(func() (any, error) {
err := r.sendPreRegisterVoter(nextRewardEpochId, address)
if err != nil {
if shared.ExistsAsSubstring(nonFatalPreregisterErrors, err.Error()) {
logger.Debugf("Non fatal error sending pre-register voter: %v", err)
} else {
return nil, errors.Wrap(err, "error sending pre-register voter")
}
}
return nil, nil
}, shared.MaxTxSendRetries, shared.TxRetryInterval)
}

func (r *registryContractClientImpl) sendPreRegisterVoter(nextRewardEpochId *big.Int, address common.Address) error {
epochId := uint32(nextRewardEpochId.Uint64())
signature, err := r.createSignature(epochId, address)
if err != nil {
return err
}
vrsSignature := preregistry.IVoterRegistrySignature{
R: [32]byte(signature[0:32]),
S: [32]byte(signature[32:64]),
V: signature[64] + 27,
}

gasPrice, err := chain.GetGasPrice(r.gasCfg, r.ethClient, chain.DefaultTxTimeout)
if err != nil {
logger.Warnf("Unable to obtain gas price: %v, using fallback %d", err, fallbackGasPrice)
gasPrice = fallbackGasPrice
}
r.senderTxOpts.GasPrice = gasPrice

estimatedGasLimit, err := chain.DryRunTxAbi(
r.ethClient,
chain.DefaultTxTimeout,
r.senderTxOpts.From,
r.preregistryAddress,
common.Big0,
preregistryAbi,
"preRegisterVoter",
address,
vrsSignature,
)
if err != nil {
return errors.Wrap(err, "Dry run failed")
}

if r.gasCfg.GasLimit != 0 {
r.senderTxOpts.GasLimit = uint64(r.gasCfg.GasLimit)
} else {
r.senderTxOpts.GasLimit = estimatedGasLimit
}

tx, err := r.preregistry.PreRegisterVoter(r.senderTxOpts, address, vrsSignature)
if err != nil {
return err
}
err = r.txVerifier.WaitUntilMined(r.senderTxOpts.From, tx, chain.DefaultTxTimeout)
if err != nil {
return err
}
logger.Infof("Voter %s pre-registered for epoch %v", address, nextRewardEpochId)
return nil
}
Loading

0 comments on commit 6c15b0a

Please sign in to comment.