-
Notifications
You must be signed in to change notification settings - Fork 57
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(monitor): add xcall loadgen to the monitor app #2723
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,6 +132,12 @@ type Manifest struct { | |
|
||
// FeatureFlags defines the feature flags to enable. | ||
FeatureFlags feature.Flags `toml:"feature_flags"` | ||
|
||
// XCallerEnabled defines whether to enable the xcaller load generator for the monitor app. | ||
XCallerEnabled bool `toml:"xcaller_enabled"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lets follow the existing loadgen pattern, "Validator self-delegation" is simply enabled by presence of the key. Also, avoid complex configuration options in v1, simply hardcode the chain pairs in |
||
|
||
// XCallerChainIDPairs maps chan ID pairs to make xcalls from -> to for the monitor xcall loadgen. | ||
XCallerChainIDPairs map[string]string `toml:"xcaller_chainid_pairs"` | ||
} | ||
|
||
// Seeds returns a map of seed nodes by name. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -121,7 +121,7 @@ func (s Static) ExecutionRPC() string { | |
if s.Network == Devnet { | ||
// First omni_evm in devnet docker-compose. | ||
// Note that it might not be running. | ||
return "http://127.0.0.1:8000" | ||
return "http://validator01_evm:8545" | ||
} | ||
|
||
return fmt.Sprintf("https://%s.omni.network", s.Network) | ||
|
@@ -131,7 +131,7 @@ func (s Static) ConsensusRPC() string { | |
if s.Network == Devnet { | ||
// First halo in devnet docker-compose. | ||
// Note that it might not be running. | ||
return "http://localhost:5701" | ||
return "http://validator01:26657" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
with have |
||
} | ||
|
||
return fmt.Sprintf("https://consensus.%s.omni.network", s.Network) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ import ( | |
rpcclient "github.com/cometbft/cometbft/rpc/client" | ||
rpchttp "github.com/cometbft/cometbft/rpc/client/http" | ||
|
||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
|
@@ -34,6 +35,7 @@ type Connector struct { | |
CProvider cchain.Provider | ||
EthClients map[uint64]ethclient.Client | ||
CmtCl rpcclient.Client | ||
network netconf.Network | ||
} | ||
|
||
// Backend returns an ethbackend for the given chainID. | ||
|
@@ -51,6 +53,21 @@ func (c Connector) Backend(chainID uint64) (*ethbackend.Backend, error) { | |
return ethbackend.NewBackend(chain.Name, chainID, chain.BlockPeriod, cl) | ||
} | ||
|
||
// GetPortal returns an OmniPortal binding for the portal at the provided address. | ||
func (c Connector) GetPortal(chainID uint64, backend bind.ContractBackend) (*bindings.OmniPortal, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. xconnector isn't used in monitor, it is only used in scripts, lets keep it that way. please remove this. |
||
chain, ok := c.network.Chain(chainID) | ||
if !ok { | ||
return nil, errors.New("failed to get chain from network", "chain", chainID) | ||
} | ||
|
||
portal, err := bindings.NewOmniPortal(chain.PortalAddress, backend) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to get portal") | ||
} | ||
|
||
return portal, nil | ||
} | ||
|
||
type option func(xchain.RPCEndpoints) error | ||
|
||
// WithPublicRPCs returns an option using well known public free RPCs for all xchains. | ||
|
@@ -188,6 +205,7 @@ func New(ctx context.Context, netID netconf.ID, opts ...option) (Connector, erro | |
CProvider: cprov, | ||
EthClients: ethClients, | ||
CmtCl: cometCl, | ||
network: network, | ||
}, nil | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,3 +72,12 @@ color = "auto" | |
[loadgen] | ||
# Validator keys glob defines the validator keys to use for self-delegation. | ||
validator-keys-glob = "path/*/1" | ||
|
||
[xcaller] | ||
enabled = "false" | ||
|
||
# Chain ID pairs, where each pair specifies a 'from' and 'to' chain for sending xCalls. | ||
[xcaller.chainid-pairs] | ||
# arbitrum_one = "optimism" | ||
# base = "arbitrum_one" | ||
|
||
Comment on lines
72
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,26 +7,28 @@ 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/omni-network/omni/lib/xchain/connect" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
ethcrypto "github.com/ethereum/go-ethereum/crypto" | ||
) | ||
|
||
// 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 | ||
} | ||
|
||
// 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 { | ||
func Start(ctx context.Context, network netconf.Network, ethClients map[uint64]ethclient.Client, cfg Config, xCallerCfg XCallerConfig, endpoints xchain.RPCEndpoints) error { | ||
// Only generate load in ephemeral networks, devnet and staging. | ||
if !network.ID.IsEphemeral() { | ||
return nil | ||
|
@@ -79,5 +81,28 @@ func Start(ctx context.Context, network netconf.Network, ethClients map[uint64]e | |
go selfDelegateForever(ctx, contract, backend, val, period) | ||
} | ||
|
||
if xCallerCfg.Enabled { | ||
if len(xCallerCfg.ChainIDs) == 0 { | ||
return errors.New("xcaller enabled but no chain id pairs specified") | ||
} | ||
|
||
xCallerPK, err := eoa.PrivateKey(ctx, network.ID, eoa.RoleXCaller) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to get RoleXCaller priv key exiting xcall loadgen") | ||
} | ||
|
||
backends, err := ethbackend.BackendsFromNetwork(network, endpoints, xCallerPK) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
connector, err := connect.New(ctx, network.ID) | ||
if err != nil { | ||
return err | ||
} | ||
xCallerAddr := eoa.MustAddress(network.ID, eoa.RoleXCaller) | ||
go xCallForever(ctx, xCallerAddr, period, xCallerCfg.ChainIDs, backends, connector) | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package loadgen | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math/rand/v2" | ||
"time" | ||
|
||
"github.com/omni-network/omni/lib/errors" | ||
"github.com/omni-network/omni/lib/ethclient/ethbackend" | ||
"github.com/omni-network/omni/lib/evmchain" | ||
"github.com/omni-network/omni/lib/log" | ||
"github.com/omni-network/omni/lib/xchain" | ||
"github.com/omni-network/omni/lib/xchain/connect" | ||
|
||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
const ( | ||
dead = "0x000000000000000000000000000000000000dead" | ||
) | ||
|
||
type XCallerConfig struct { | ||
Enabled bool | ||
ChainIDs map[string]string | ||
} | ||
|
||
func xCallForever(ctx context.Context, xCallerAddr common.Address, period time.Duration, chainIDs map[string]string, backends ethbackend.Backends, connector connect.Connector) { | ||
log.Info(ctx, "Starting periodic xcalls", "period", period) | ||
|
||
nextPeriod := func() time.Duration { | ||
jitter := time.Duration(float64(period) * rand.Float64() * loadgenJitter) //nolint:gosec // Weak random ok for load tests. | ||
return period + jitter | ||
} | ||
|
||
timer := time.NewTimer(nextPeriod()) | ||
defer timer.Stop() | ||
|
||
// tick immediately | ||
if err := xCallOnce(ctx, xCallerAddr, chainIDs, backends, connector); err != nil { | ||
log.Warn(ctx, "Failed to xcall (will retry)", err) | ||
} | ||
|
||
for { | ||
select { | ||
case <-ctx.Done(): | ||
return | ||
case <-timer.C: | ||
if err := xCallOnce(ctx, xCallerAddr, chainIDs, backends, connector); err != nil { | ||
log.Warn(ctx, "Failed to xcall (will retry)", err) | ||
} | ||
timer.Reset(nextPeriod()) | ||
} | ||
} | ||
} | ||
|
||
func xCallOnce(ctx context.Context, xCallerAddr common.Address, chainIDs map[string]string, backends ethbackend.Backends, connector connect.Connector) error { | ||
for from, to := range chainIDs { | ||
fromChain, ok := evmchain.MetadataByName(from) | ||
if !ok { | ||
return errors.New("unknown source chain name", from) | ||
} | ||
|
||
dstChain, ok := evmchain.MetadataByName(to) | ||
if !ok { | ||
return errors.New("unknown destination chain name", from) | ||
} | ||
|
||
backend, err := backends.Backend(fromChain.ChainID) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fromPortal, err := connector.GetPortal(fromChain.ChainID, backend) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var data []byte | ||
to := common.HexToAddress(dead) | ||
gasLimit := uint64(100_000) | ||
fee, err := fromPortal.FeeFor(&bind.CallOpts{}, dstChain.ChainID, data, gasLimit) | ||
if err != nil { | ||
return errors.Wrap(err, "feeFor", | ||
"src_chain", fromChain.ChainID, | ||
"dst_chain_id", dstChain.ChainID, | ||
) | ||
} | ||
|
||
txOpts, err := backend.BindOpts(ctx, xCallerAddr) | ||
if err != nil { | ||
return errors.Wrap(err, "bindOpts") | ||
} | ||
|
||
txOpts.Value = fee | ||
tx, err := fromPortal.Xcall(txOpts, dstChain.ChainID, uint8(xchain.ConfFinalized), to, data, gasLimit) | ||
if err != nil { | ||
return errors.Wrap(err, "xcall", | ||
"src_chain", fromChain.ChainID, | ||
"dst_chain_id", dstChain.ChainID, | ||
) | ||
} | ||
|
||
log.Debug(ctx, fmt.Sprintf("xcall made %d -> %d %s", fromChain.ChainID, dstChain.ChainID, tx.To())) | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some thoughts on config.
if we do have a need to specify, I think pairs are limitting. for ex, we could not loadtest xcalls on all streams by configuring pairs (at least, not as written). What about just specifying source and destination chains with a regex