Skip to content

Commit

Permalink
feat(monitor): add xcall loadgen to the monitor app (#2723)
Browse files Browse the repository at this point in the history
This PR adds xcall load generation to the monitor app. 

issue: #2275
  • Loading branch information
kc1116 authored Jan 14, 2025
1 parent b1d02af commit 57d375d
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 8 deletions.
15 changes: 13 additions & 2 deletions e2e/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,8 +565,9 @@ func writeMonitorConfig(ctx context.Context, def Definition, logCfg log.Config,
confRoot := filepath.Join(def.Testnet.Dir, "monitor")

const (
privKeyFile = "privatekey"
configFile = "monitor.toml"
privKeyFile = "privatekey"
xCallerPrivKeyFile = "xcaller_privatekey"
configFile = "monitor.toml"
)

if err := os.MkdirAll(confRoot, 0o755); err != nil {
Expand Down Expand Up @@ -605,6 +606,15 @@ func writeMonitorConfig(ctx context.Context, def Definition, logCfg log.Config,
return errors.Wrap(err, "write private key")
}

// save xcaller key
xCallerPrivKey, err := eoa.PrivateKey(ctx, def.Testnet.Network, eoa.RoleXCaller)
if err != nil {
return errors.Wrap(err, "get xcaller key")
}
if err := ethcrypto.SaveECDSA(filepath.Join(confRoot, xCallerPrivKeyFile), xCallerPrivKey); err != nil {
return errors.Wrap(err, "write xcaller private key")
}

var validatorKeyGlob string
for i, privKey := range valPrivKeys {
validatorKeyGlob = "validator_*"
Expand Down Expand Up @@ -632,6 +642,7 @@ func writeMonitorConfig(ctx context.Context, def Definition, logCfg log.Config,
cfg.Network = def.Testnet.Network
cfg.HaloURL = archiveNode.AddressRPC()
cfg.LoadGen.ValidatorKeysGlob = validatorKeyGlob
cfg.LoadGen.XCallerKey = xCallerPrivKeyFile
cfg.RPCEndpoints = endpoints
cfg.XFeeMngr.RPCEndpoints = xfeemngrEndpoints
cfg.XFeeMngr.CoinGeckoAPIKey = def.Cfg.CoinGeckoAPIKey
Expand Down
2 changes: 1 addition & 1 deletion monitor/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func serveMonitoring(address string) <-chan error {
}

func startLoadGen(ctx context.Context, cfg Config, network netconf.Network, ethClients map[uint64]ethclient.Client) error {
if err := loadgen.Start(ctx, network, ethClients, cfg.LoadGen); err != nil {
if err := loadgen.Start(ctx, network, ethClients, cfg.LoadGen, cfg.RPCEndpoints); err != nil {
return errors.Wrap(err, "start load generator")
}

Expand Down
1 change: 1 addition & 0 deletions monitor/app/config.toml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ color = "{{ .Log.Color }}"
[loadgen]
# Validator keys glob defines the validator keys to use for self-delegation.
validator-keys-glob = "{{ .LoadGen.ValidatorKeysGlob }}"
xcaller-key = "{{ .LoadGen.XCallerKey }}"
1 change: 1 addition & 0 deletions monitor/app/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestDefaultConfigReference(t *testing.T) {
cfg := monitor.DefaultConfig()
cfg.LoadGen = loadgen.Config{
ValidatorKeysGlob: "path/*/1",
XCallerKey: "path/xcaller_privatekey",
}
cfg.XFeeMngr = xfeemngr.Config{
RPCEndpoints: xchain.RPCEndpoints{"test_chain": "http://localhost:8545"},
Expand Down
1 change: 1 addition & 0 deletions monitor/app/testdata/default_monitor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ color = "auto"
[loadgen]
# Validator keys glob defines the validator keys to use for self-delegation.
validator-keys-glob = "path/*/1"
xcaller-key = "path/xcaller_privatekey"
1 change: 1 addition & 0 deletions monitor/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func bindRunFlags(flags *pflag.FlagSet, cfg *monitor.Config) {

func bindLoadGenFlags(flags *pflag.FlagSet, cfg *loadgen.Config) {
flags.StringVar(&cfg.ValidatorKeysGlob, "loadgen-validator-keys-glob", cfg.ValidatorKeysGlob, "Glob path to the validator keys used for self-delegation load generation. Only applicable to devnet and staging")
flags.StringVar(&cfg.XCallerKey, "loadgen-xcaller-key", cfg.XCallerKey, "Path to the xcaller key used for xcall loadgen")
}

func bindXFeeMngrFlags(flags *pflag.FlagSet, cfg *xfeemngr.Config) {
Expand Down
66 changes: 63 additions & 3 deletions monitor/loadgen/loadgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,52 @@ import (
"time"

"github.com/omni-network/omni/contracts/bindings"
"github.com/omni-network/omni/e2e/app/eoa"
"github.com/omni-network/omni/halo/genutil/evm/predeploys"
"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/ethclient"
"github.com/omni-network/omni/lib/ethclient/ethbackend"
"github.com/omni-network/omni/lib/netconf"
"github.com/omni-network/omni/lib/xchain"

"github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)

const (
selfDelegationPeriod = time.Hour * 6
selfDelegationPeriodDevnet = time.Second * 5
xCallerPeriod = time.Hour * 2
xCallerPeriodDevnet = time.Second * 30
)

// Config is the configuration for the load generator.
type Config struct {
// ValidatorKeysGlob defines the paths to the validator keys used for self-delegation.
ValidatorKeysGlob string
// XCallerKey path to the xcaller private key.
XCallerKey string
}

// Start starts the validator self delegation load generator.
// It does:
// - Validator self-delegation on periodic basis.
func Start(ctx context.Context, network netconf.Network, ethClients map[uint64]ethclient.Client, cfg Config) error {
// - Makes XCalls from -> to random EVM portals on periodic basis.
func Start(ctx context.Context, network netconf.Network, ethClients map[uint64]ethclient.Client, cfg Config, rpcEndpoints xchain.RPCEndpoints) error {
err := startSelfDelegation(ctx, network, ethClients, cfg)
if err != nil {
return errors.Wrap(err, "start self delegation")
}

err = startXCaller(ctx, network, rpcEndpoints, cfg.XCallerKey)
if err != nil {
return errors.Wrap(err, "start xcaller")
}

return nil
}

func startSelfDelegation(ctx context.Context, network netconf.Network, ethClients map[uint64]ethclient.Client, cfg Config) error {
// Only generate load in ephemeral networks, devnet and staging.
if !network.ID.IsEphemeral() {
return nil
Expand Down Expand Up @@ -69,9 +95,9 @@ func Start(ctx context.Context, network netconf.Network, ethClients map[uint64]e
return errors.Wrap(err, "new omni stake")
}

var period = time.Hour * 6
period := selfDelegationPeriod
if network.ID == netconf.Devnet {
period = time.Second * 5
period = selfDelegationPeriodDevnet
}

for _, key := range keys {
Expand All @@ -81,3 +107,37 @@ func Start(ctx context.Context, network netconf.Network, ethClients map[uint64]e

return nil
}

func startXCaller(ctx context.Context, network netconf.Network, rpcEndpoints xchain.RPCEndpoints, keyPath string) error {
if keyPath == "" {
// Skip if no key is provided.
return nil
}

privKey, err := ethcrypto.LoadECDSA(keyPath)
if err != nil {
return errors.Wrap(err, "load xcaller key", "path", keyPath)
}

backends, err := ethbackend.BackendsFromNetwork(network, rpcEndpoints, privKey)
if err != nil {
return err
}

xCallerAddr := eoa.MustAddress(network.ID, eoa.RoleXCaller)

period := xCallerPeriod
if network.ID == netconf.Devnet {
period = xCallerPeriodDevnet
}
xCallCfg := xCallConfig{
NetworkID: network.ID,
XCallerAddr: xCallerAddr,
Period: period,
Backends: backends,
Chains: network.EVMChains(),
}
go xCallForever(ctx, xCallCfg)

return nil
}
4 changes: 2 additions & 2 deletions monitor/loadgen/stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import (
"github.com/ethereum/go-ethereum/params"
)

const selfDelegateJitter = 0.2 // 20% jitter
const loadgenJitter = 0.2 // 20% jitter

func selfDelegateForever(ctx context.Context, contract *bindings.Staking, backend *ethbackend.Backend, validator common.Address, period time.Duration) {
log.Info(ctx, "Starting periodic self-delegation", "validator", validator.Hex(), "period", period)

nextPeriod := func() time.Duration {
jitter := time.Duration(float64(period) * rand.Float64() * selfDelegateJitter) //nolint:gosec // Weak random ok for load tests.
jitter := time.Duration(float64(period) * rand.Float64() * loadgenJitter) //nolint:gosec // Weak random ok for load tests.
return period + jitter
}

Expand Down
130 changes: 130 additions & 0 deletions monitor/loadgen/xcall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package loadgen

import (
"context"
"math/rand/v2"
"time"

"github.com/omni-network/omni/contracts/bindings"
"github.com/omni-network/omni/lib/contracts"
"github.com/omni-network/omni/lib/contracts/portal"
"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/ethclient/ethbackend"
"github.com/omni-network/omni/lib/log"
"github.com/omni-network/omni/lib/netconf"
"github.com/omni-network/omni/lib/xchain"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)

const (
deadAddr = "0x000000000000000000000000000000000000dead"
)

type xCallConfig struct {
NetworkID netconf.ID
XCallerAddr common.Address
Period time.Duration
Backends ethbackend.Backends
Chains []netconf.Chain
}

func xCallForever(ctx context.Context, cfg xCallConfig) {
log.Info(ctx, "Starting periodic xcalls", "period", cfg.Period)

nextPeriod := func() time.Duration {
jitter := time.Duration(float64(cfg.Period) * rand.Float64() * loadgenJitter) //nolint:gosec // Weak random ok for load tests.
return cfg.Period + jitter
}

// timer will tick immediately
timer := time.NewTimer(0)
defer timer.Stop()

for {
select {
case <-ctx.Done():
return
case <-timer.C:
if err := xCall(ctx, cfg); err != nil {
log.Warn(ctx, "Failed to xcall (will retry)", err)
}
timer.Reset(nextPeriod())
}
}
}

func xCall(ctx context.Context, cfg xCallConfig) error {
srcChain, dstChain := getChainPair(cfg.Chains)
backend, err := cfg.Backends.Backend(srcChain.ID)
if err != nil {
return err
}

srcPortal, err := getPortal(ctx, cfg.NetworkID, srcChain.ID, cfg.Backends)
if err != nil {
return err
}

var nilData []byte
to := common.HexToAddress(deadAddr)
fee, err := srcPortal.FeeFor(&bind.CallOpts{Context: ctx}, dstChain.ID, nilData, portal.XMsgMinGasLimit)
if err != nil {
return errors.Wrap(err, "feeFor",
"src_chain", srcChain.ID,
"dst_chain_id", dstChain.ID,
)
}

txOpts, err := backend.BindOpts(ctx, cfg.XCallerAddr)
if err != nil {
return errors.Wrap(err, "bindOpts")
}

txOpts.Value = fee
tx, err := srcPortal.Xcall(txOpts, dstChain.ID, uint8(xchain.ConfLatest), to, nilData, portal.XMsgMinGasLimit)
if err != nil {
return errors.Wrap(err, "xcall",
"src_chain", srcChain.ID,
"dst_chain_id", dstChain.ID,
)
}

if _, err = backend.WaitMined(ctx, tx); err != nil {
return errors.Wrap(err, "wait mined")
}

log.Debug(ctx, "Xcall made", "src_chain", srcChain.Name, "dst_chain", dstChain.Name, "tx_hash", tx.Hash())

return nil
}

func getPortal(ctx context.Context, networkID netconf.ID, chainID uint64, backends ethbackend.Backends) (*bindings.OmniPortal, error) {
backend, err := backends.Backend(chainID)
if err != nil {
return nil, err
}

addrs, err := contracts.GetAddresses(ctx, networkID)
if err != nil {
return nil, err
}

contract, err := bindings.NewOmniPortal(addrs.Portal, backend)
if err != nil {
return nil, err
}

return contract, nil
}

func getChainPair(chains []netconf.Chain) (netconf.Chain, netconf.Chain) {
i := rand.IntN(len(chains)) //nolint:gosec // Weak random ok for load tests.
j := rand.IntN(len(chains)) //nolint:gosec // Weak random ok for load tests.
for i == j {
j = rand.IntN(len(chains)) //nolint:gosec // Weak random ok for load tests.
}

return chains[i], chains[j]
}

0 comments on commit 57d375d

Please sign in to comment.