Skip to content

Commit

Permalink
feat: Valset relaying modes (#189)
Browse files Browse the repository at this point in the history
* add valset relay mode

* update e2e tests + add sort in deploy-gravity

* fix unit test

* cl++

* Update CHANGELOG.md

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

* Update cmd/peggo/orchestrator.go

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

* Update cmd/peggo/orchestrator.go

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

* Update orchestrator/relayer/find_latest_valset.go

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

* Update orchestrator/relayer/relayer.go

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

* suggestions by @alexanderbez :)

* lint

* Update cmd/peggo/orchestrator.go

Co-authored-by: Aleksandr Bezobchuk <[email protected]>

Co-authored-by: Aleksandr Bezobchuk <[email protected]>
  • Loading branch information
facundomedica and alexanderbez authored Feb 21, 2022
1 parent c3546a4 commit 047fb0d
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 42 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

### Features

- [#189] Add the flag `--valset-relay-mode` which allows a finer control over
how valsets will be relayed.

### Deprecated

- [#189] Deprecate the `--relay-valsets` flag.

### Bug Fixes

- [#189] Order validator set before deploying (`peggo bridge deploy-gravity`)

## [v0.2.4](https://github.com/umee-network/peggo/releases/tag/v0.2.4) - 2022-02-16

### Improvements
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export PEGGO_ETH_PK={ethereum private key}
$ peggo orchestrator {gravityAddress} \
--eth-rpc=$ETH_RPC \
--relay-batches=true \
--relay-valsets=true \
--valset-relay-mode=minimum \
--cosmos-chain-id=... \
--cosmos-grpc="tcp://..." \
--tendermint-rpc="http://..." \
Expand Down
4 changes: 4 additions & 0 deletions cmd/peggo/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"google.golang.org/grpc"

"github.com/umee-network/peggo/cmd/peggo/client"
"github.com/umee-network/peggo/orchestrator/relayer"
wrappers "github.com/umee-network/peggo/solwrappers/Gravity.sol"
)

Expand Down Expand Up @@ -140,6 +141,9 @@ func deployGravityCmd() *cobra.Command {
totalPower uint64
)

// Always sort the validator set
relayer.BridgeValidators(currValset.Valset.Members).Sort()

for i, member := range currValset.Valset.Members {
validators[i] = ethcmn.HexToAddress(member.EthereumAddress)
powers[i] = new(big.Int).SetUint64(member.Power)
Expand Down
1 change: 1 addition & 0 deletions cmd/peggo/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
flagEthGasLimitAdjustment = "eth-gas-limit-adjustment"
flagEthAlchemyWS = "eth-alchemy-ws"
flagRelayValsets = "relay-valsets"
flagValsetRelayMode = "valset-relay-mode"
flagRelayBatches = "relay-batches"
flagCoinGeckoAPI = "coingecko-api"
flagEthGasPrice = "eth-gas-price"
Expand Down
29 changes: 28 additions & 1 deletion cmd/peggo/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,23 @@ func getOrchestratorCmd() *cobra.Command {
// Here we cast the float64 to a Duration (int64); as we are dealing with ms, we'll lose as much as 1ms.
relayerLoopDuration := time.Duration(ethBlockTimeF64*relayerLoopMultiplier) * time.Millisecond

relayValsets := konfig.Bool(flagRelayValsets)
valsetRelayMode, err := validateRelayValsetsMode(konfig.String(flagValsetRelayMode))
if err != nil {
return err
}

// If relayValsets is true then the user didn't specify a value for 'valset-relay-mode',
// so we'll default to "minimum".
if relayValsets && valsetRelayMode == relayer.ValsetRelayModeNone {
valsetRelayMode = relayer.ValsetRelayModeMinimum
}

relayer := relayer.NewGravityRelayer(
logger,
gravityQuerier,
gravityContract,
konfig.Bool(flagRelayValsets),
valsetRelayMode,
konfig.Bool(flagRelayBatches),
relayerLoopDuration,
konfig.Duration(flagEthPendingTXWait),
Expand Down Expand Up @@ -244,6 +256,7 @@ func getOrchestratorCmd() *cobra.Command {
}

cmd.Flags().Bool(flagRelayValsets, false, "Relay validator set updates to Ethereum")
cmd.Flags().String(flagValsetRelayMode, relayer.ValsetRelayModeNone.String(), "Set an (optional) relaying mode for valset updates to Ethereum. Possible values: none, minimum, all")
cmd.Flags().Bool(flagRelayBatches, false, "Relay transaction batches to Ethereum")
cmd.Flags().Int64(flagEthBlocksPerLoop, 2000, "Number of Ethereum blocks to process per orchestrator loop")
cmd.Flags().String(flagCoinGeckoAPI, "https://api.coingecko.com/api/v3", "Specify the coingecko API endpoint")
Expand All @@ -259,6 +272,7 @@ func getOrchestratorCmd() *cobra.Command {
cmd.Flags().AddFlagSet(cosmosKeyringFlagSet())
cmd.Flags().AddFlagSet(ethereumKeyOptsFlagSet())
cmd.Flags().AddFlagSet(ethereumOptsFlagSet())
_ = cmd.Flags().MarkDeprecated(flagRelayValsets, "use --valset-relay-mode instead")

return cmd
}
Expand Down Expand Up @@ -294,3 +308,16 @@ func startOrchestrator(ctx context.Context, logger zerolog.Logger, orch orchestr
}
}
}

func validateRelayValsetsMode(mode string) (relayer.ValsetRelayMode, error) {
switch mode {
case relayer.ValsetRelayModeNone.String():
return relayer.ValsetRelayModeNone, nil
case relayer.ValsetRelayModeMinimum.String():
return relayer.ValsetRelayModeMinimum, nil
case relayer.ValsetRelayModeAll.String():
return relayer.ValsetRelayModeAll, nil
default:
return relayer.ValsetRelayModeNone, fmt.Errorf("invalid relay valsets mode: %s", mode)
}
}
7 changes: 2 additions & 5 deletions orchestrator/relayer/find_latest_valset.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,9 @@ func (s *gravityRelayer) checkIfValsetsDiffer(cosmosValset, ethereumValset *type
return
}

// Do not check if nonces are not equal.
// We queried cosmosValset using ethereumValset.Nonce so it is not necessary.
if cosmosValset.Nonce != ethereumValset.Nonce {

s.logger.Error().
Uint64("eth_valset_nonce", ethereumValset.Nonce).
Uint64("cosmos_valset_nonce", cosmosValset.Nonce).
Msg("cosmos does have a wrong valset nonce, differs from Ethereum chain. Possible bridge hijacking!")
return
}

Expand Down
4 changes: 2 additions & 2 deletions orchestrator/relayer/main_loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func (s *gravityRelayer) Start(ctx context.Context) error {
logger := s.logger.With().Str("loop", "RelayerMainLoop").Logger()

if s.valsetRelayEnabled {
if s.valsetRelayMode != ValsetRelayModeNone {
logger.Info().Msg("valset relay enabled; starting to relay valsets to Ethereum")
}

Expand Down Expand Up @@ -44,7 +44,7 @@ func (s *gravityRelayer) Start(ctx context.Context) error {
}

var pg loops.ParanoidGroup
if s.valsetRelayEnabled {
if s.valsetRelayMode != ValsetRelayModeNone {
pg.Go(func() error {
return retry.Do(func() error {
return s.RelayValsets(ctx, *currentValset)
Expand Down
55 changes: 35 additions & 20 deletions orchestrator/relayer/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ import (
gravitytypes "github.com/umee-network/Gravity-Bridge/module/x/gravity/types"
)

// ValsetRelayMode defines an enumerated validator set relay mode.
type ValsetRelayMode int64

// Allowed validator set relay modes
const (
ValsetRelayModeNone ValsetRelayMode = iota
ValsetRelayModeMinimum
ValsetRelayModeAll
)

// String gets the string representation of the validator set relay mode.
func (d ValsetRelayMode) String() string {
return [...]string{"none", "minimum", "all"}[d]
}

type GravityRelayer interface {
Start(ctx context.Context) error

Expand All @@ -33,16 +48,16 @@ type GravityRelayer interface {
}

type gravityRelayer struct {
logger zerolog.Logger
cosmosQueryClient gravitytypes.QueryClient
gravityContract gravity.Contract
ethProvider provider.EVMProvider
valsetRelayEnabled bool
batchRelayEnabled bool
loopDuration time.Duration
priceFeeder *coingecko.PriceFeed
pendingTxWait time.Duration
profitMultiplier float64
logger zerolog.Logger
cosmosQueryClient gravitytypes.QueryClient
gravityContract gravity.Contract
ethProvider provider.EVMProvider
valsetRelayMode ValsetRelayMode
batchRelayEnabled bool
loopDuration time.Duration
priceFeeder *coingecko.PriceFeed
pendingTxWait time.Duration
profitMultiplier float64

// Store locally the last tx this validator made to avoid sending duplicates
// or invalid txs.
Expand All @@ -54,23 +69,23 @@ func NewGravityRelayer(
logger zerolog.Logger,
gravityQueryClient gravitytypes.QueryClient,
gravityContract gravity.Contract,
valsetRelayEnabled bool,
valsetRelayMode ValsetRelayMode,
batchRelayEnabled bool,
loopDuration time.Duration,
pendingTxWait time.Duration,
profitMultiplier float64,
options ...func(GravityRelayer),
) GravityRelayer {
relayer := &gravityRelayer{
logger: logger.With().Str("module", "gravity_relayer").Logger(),
cosmosQueryClient: gravityQueryClient,
gravityContract: gravityContract,
ethProvider: gravityContract.Provider(),
valsetRelayEnabled: valsetRelayEnabled,
batchRelayEnabled: batchRelayEnabled,
loopDuration: loopDuration,
pendingTxWait: pendingTxWait,
profitMultiplier: profitMultiplier,
logger: logger.With().Str("module", "gravity_relayer").Logger(),
cosmosQueryClient: gravityQueryClient,
gravityContract: gravityContract,
ethProvider: gravityContract.Provider(),
valsetRelayMode: valsetRelayMode,
batchRelayEnabled: batchRelayEnabled,
loopDuration: loopDuration,
pendingTxWait: pendingTxWait,
profitMultiplier: profitMultiplier,
}

for _, option := range options {
Expand Down
2 changes: 1 addition & 1 deletion orchestrator/relayer/relayer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestNewGravityRelayer(t *testing.T) {
relayer := NewGravityRelayer(logger,
mockQClient,
mockGravityContract,
true,
ValsetRelayModeMinimum,
true,
time.Minute,
time.Minute,
Expand Down
34 changes: 23 additions & 11 deletions orchestrator/relayer/valset_relaying.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import (
// RelayValsets checks the last validator set on Ethereum, if it's lower than our latest validator
// set then we should package and submit the update as an Ethereum transaction
func (s *gravityRelayer) RelayValsets(ctx context.Context, currentValset types.Valset) error {
// we should determine if we need to relay one
// to Ethereum for that we will find the latest confirmed valset and compare it to the ethereum chain
// This works by checking if the latest valset update can be sent by the current valset stored on Ethereum.
// If so, there's no need to relay anything, given that the current valset on Eth shares enough signers with the
// latest valset on Cosmos.
// If the latest valset on Cosmos (latestValsets.Valsets[0]) can't be sent by the current valset on Ethereum,
// then we need to relay the latest valid valset on Cosmos; that means the one that shares enough signers with
// the valset stored on Ethereum.

latestValsets, err := s.cosmosQueryClient.LastValsetRequests(ctx, &types.QueryLastValsetRequestsRequest{})
if err != nil {
err = errors.Wrap(err, "failed to fetch latest valsets from cosmos")
Expand All @@ -20,7 +25,8 @@ func (s *gravityRelayer) RelayValsets(ctx context.Context, currentValset types.V
return errors.New("no valsets found")
}

latestCosmosConfirmed, latestCosmosSigs, err := s.findLatestValidValset(
// latestValidValset means the latest valset that can be sent using the current valset on Ethereum.
latestValidValset, latestValidValsetSigs, err := s.findLatestValidValset(
ctx,
currentValset,
latestValsets.Valsets[0].Nonce,
Expand All @@ -31,19 +37,19 @@ func (s *gravityRelayer) RelayValsets(ctx context.Context, currentValset types.V
return err
}

if latestCosmosConfirmed == nil && err == nil {
if latestValidValset == nil && err == nil {
s.logger.Info().Msg("no valset updates to relay")
return nil
}

if s.lastSentValsetNonce >= latestCosmosConfirmed.Nonce {
if s.lastSentValsetNonce >= latestValidValset.Nonce {
s.logger.Debug().Msg("already relayed this valset; skipping")
return nil
}

s.logger.Debug().
Uint64("current_eth_valset_nonce", currentValset.Nonce).
Uint64("latest_cosmos_confirmed_nonce", latestCosmosConfirmed.Nonce).
Uint64("latest_cosmos_confirmed_nonce", latestValidValset.Nonce).
Msg("found latest valsets")

latestEthereumValsetNonce, err := s.gravityContract.GetValsetNonce(ctx, s.gravityContract.FromAddress())
Expand All @@ -53,21 +59,27 @@ func (s *gravityRelayer) RelayValsets(ctx context.Context, currentValset types.V
}

// Check if latestCosmosConfirmed already submitted by other validators in mean time
if latestCosmosConfirmed.Nonce <= latestEthereumValsetNonce.Uint64() {
if latestValidValset.Nonce <= latestEthereumValsetNonce.Uint64() {
// This valset update is already confirmed.
return nil
}

// We might not need to relay this valset update unless the user explicitly specified it.
if s.valsetRelayMode == ValsetRelayModeMinimum && latestValidValset.Nonce == latestValsets.Valsets[0].Nonce {
s.logger.Debug().Msg("not relaying because nonces match")
return nil
}

s.logger.Info().
Uint64("latest_cosmos_confirmed_nonce", latestCosmosConfirmed.Nonce).
Uint64("latest_cosmos_confirmed_nonce", latestValidValset.Nonce).
Uint64("latest_ethereum_valset_nonce", latestEthereumValsetNonce.Uint64()).
Msg("detected latest cosmos valset nonce, but latest valset on Ethereum is different. Sending update to Ethereum")

txData, err := s.gravityContract.EncodeValsetUpdate(
ctx,
currentValset,
*latestCosmosConfirmed,
latestCosmosSigs,
*latestValidValset,
latestValidValsetSigs,
)
if err != nil {
s.logger.Err(err).Msg("failed to encode valset update")
Expand Down Expand Up @@ -109,7 +121,7 @@ func (s *gravityRelayer) RelayValsets(ctx context.Context, currentValset types.V
s.logger.Info().Str("tx_hash", txHash.Hex()).Msg("sent Tx (Gravity updateValset)")

// update our local tracker of the latest valset
s.lastSentValsetNonce = latestCosmosConfirmed.Nonce
s.lastSentValsetNonce = latestValidValset.Nonce

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/e2e_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ func (s *IntegrationTestSuite) runOrchestrators() {
"--cosmos-from",
s.chain.orchestrators[i].keyInfo.GetName(),
"--relay-batches=true",
"--relay-valsets=true",
"--valset-relay-mode=minimum",
"--profit-multiplier=0.0",
"--relayer-loop-multiplier=1.0",
"--requester-loop-multiplier=1.0",
Expand Down

0 comments on commit 047fb0d

Please sign in to comment.