-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
CCIP Load Test connected to crib (not yet working) #15404
base: develop
Are you sure you want to change the base?
Changes from all commits
cb1f984
b38d7da
af85019
648bf68
1d87264
4f21404
8c01cc2
44152cb
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 |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package ccip | ||
|
||
import ( | ||
"fmt" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests" | ||
"github.com/smartcontractkit/chainlink-testing-framework/wasp" | ||
ccipchangeset "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" | ||
crib "github.com/smartcontractkit/chainlink/deployment/environment/crib" | ||
tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" | ||
"github.com/stretchr/testify/require" | ||
"math/big" | ||
"sync" | ||
"testing" | ||
"time" | ||
) | ||
|
||
var ( | ||
CommonTestLabels = map[string]string{ | ||
"branch": "ccip_load_crib", | ||
"commit": "ccip_load_crib", | ||
} | ||
wg sync.WaitGroup | ||
SIM_CHAIN_PRIVATE_KEYS = map[uint64]string{ | ||
1337: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", | ||
2337: "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", | ||
} | ||
) | ||
|
||
const CRIB_DIRECTORY = "/Users/austin.wang/ccip-core/repos/crib/deployments/ccip-v2/.tmp" | ||
|
||
// step 1: setup | ||
// Parse the test config, initialize CRIB with configurations defined | ||
// step 2: load | ||
// Use wasp to initiate load | ||
// step 3: parse logs | ||
// Parse all events from the simulated chains, send to Loki | ||
// step 4: teardown | ||
// Stop the chains, cleanup the environment | ||
func TestCCIPLoad_RPS(t *testing.T) { | ||
ctx := tests.Context(t) | ||
lggr := logger.Test(t) | ||
|
||
config, err := tc.GetConfig([]string{"Load"}, tc.CCIP) | ||
require.NoError(t, err) | ||
lggr.Infof("loaded ccip test config: %+v", config.CCIP.Load) | ||
|
||
cribEnv := crib.NewDevspaceEnvFromStateDir(CRIB_DIRECTORY) | ||
|
||
cribDeployOutput, err := cribEnv.GetConfig(SIM_CHAIN_PRIVATE_KEYS) | ||
require.NoError(t, err) | ||
env, err := crib.NewDeployEnvironmentFromCribOutput(lggr, cribDeployOutput) | ||
require.NoError(t, err) | ||
require.NotNil(t, env) | ||
|
||
// Need to keep track of the block number for each chain so that event subscription can be done from that block. | ||
startBlocks := make(map[uint64]*uint64) | ||
state, err := ccipchangeset.LoadOnchainState(*env) | ||
require.NoError(t, err) | ||
|
||
// Parse all events from the simulated chains, send to Loki | ||
loki, err := wasp.NewLokiClient(wasp.NewLokiConfig(config.CCIP.Load.LokiEndpoint, nil, nil, nil)) | ||
require.NoError(t, err) | ||
defer loki.StopNow() | ||
|
||
// Based on the config, initialize DestinationGun | ||
p := wasp.NewProfile() | ||
for selector, chain := range env.Chains { | ||
latesthdr, err := chain.Client.HeaderByNumber(ctx, nil) | ||
require.NoError(t, err) | ||
block := latesthdr.Number.Uint64() | ||
startBlocks[selector] = &block | ||
|
||
p.Add(wasp.NewGenerator(&wasp.Config{ | ||
T: t, | ||
GenName: "ccipLoad", | ||
LoadType: wasp.RPS, | ||
CallTimeout: 5 * time.Second, | ||
Schedule: wasp.Plain(1, 5*time.Second), | ||
// will need to be divided by number of chains | ||
// this schedule is per generator | ||
// in this example, it would be 1 request per 10seconds per generator (dest chain) | ||
// so if there are 3 generators, it would be 3 requests per 10 seconds over the network | ||
Gun: NewDestinationGun(env.Logger, selector, *env, state.Chains[selector].Receiver.Address(), loki), | ||
Labels: CommonTestLabels, | ||
LokiConfig: wasp.NewLokiConfig(config.CCIP.Load.LokiEndpoint, nil, nil, nil), | ||
// use the same loki client using `NewLokiClient` with the same config for sending events | ||
})) | ||
} | ||
|
||
_, err = p.Run(true) | ||
|
||
lokiLabels := map[string]string{} | ||
for chainSelector, startBlock := range startBlocks { | ||
wg.Add(1) | ||
go func(chainSelector uint64, startBlock *uint64) { | ||
defer wg.Done() | ||
lggr.Infow("Starting to query for events on ", "chainSelector", chainSelector, "startblock", startBlock) | ||
|
||
filterOpts := &bind.FilterOpts{ | ||
Start: *startBlock, | ||
End: nil, // To the latest block | ||
Context: ctx, | ||
} | ||
|
||
offRamp := state.Chains[chainSelector].OffRamp | ||
// Filter CommitReportAccepted events | ||
commitIterator, err := offRamp.FilterCommitReportAccepted(filterOpts) | ||
require.NoError(t, err) | ||
|
||
fmt.Printf("Events on commitIterator %+v", commitIterator) | ||
|
||
for commitIterator.Next() { | ||
event := commitIterator.Event | ||
fmt.Printf("CommitReportAccepted event: %+v\n", event) | ||
|
||
blockNum := commitIterator.Event.Raw.BlockNumber | ||
header, err := env.Chains[chainSelector].Client.HeaderByNumber(ctx, big.NewInt(int64(blockNum))) | ||
require.NoError(t, err) | ||
timestamp := time.Unix(int64(header.Time), 0) | ||
|
||
for _, root := range event.MerkleRoots { | ||
lokiLabels, err = setLokiLabels(root.SourceChainSelector, chainSelector) | ||
require.NoError(t, err) | ||
|
||
for i := root.MinSeqNr; i <= root.MaxSeqNr; i++ { | ||
// todo: push loki calls to channel? | ||
SendMetricsToLoki(lggr, loki, lokiLabels, &LokiMetric{ | ||
EventType: committed, | ||
Timestamp: timestamp, | ||
SequenceNumber: i, | ||
}) | ||
} | ||
} | ||
} | ||
|
||
// Filter ExecutionStateChanged events | ||
execIterator, err := state.Chains[chainSelector].OffRamp.FilterExecutionStateChanged(filterOpts, []uint64{chainSelector}, nil, nil) | ||
require.NoError(t, err) | ||
for execIterator.Next() { | ||
event := execIterator.Event | ||
fmt.Printf("ExecutionStateChanged event: %+v\n", event) | ||
|
||
blockNum := execIterator.Event.Raw.BlockNumber | ||
header, err := env.Chains[chainSelector].Client.HeaderByNumber(ctx, big.NewInt(int64(blockNum))) | ||
require.NoError(t, err) | ||
timestamp := time.Unix(int64(header.Time), 0) | ||
|
||
// todo: push loki calls to channel? | ||
lokiLabels, err = setLokiLabels(execIterator.Event.SourceChainSelector, chainSelector) | ||
require.NoError(t, err) | ||
SendMetricsToLoki(lggr, loki, lokiLabels, &LokiMetric{ | ||
EventType: executed, | ||
Timestamp: timestamp, | ||
GasUsed: execIterator.Event.GasUsed.Uint64(), | ||
SequenceNumber: execIterator.Event.SequenceNumber, | ||
}) | ||
|
||
} | ||
}(chainSelector, startBlock) | ||
} | ||
|
||
wg.Wait() | ||
|
||
// Stop the chains, cleanup the environment | ||
|
||
// crib.StopChains(env) | ||
// crib.StopNodes(env) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package ccip | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
"github.com/smartcontractkit/chainlink-testing-framework/wasp" | ||
"github.com/smartcontractkit/chainlink/deployment" | ||
ccipchangeset "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" | ||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" | ||
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" | ||
"go.uber.org/atomic" | ||
"time" | ||
) | ||
|
||
type ChainSelectorPair struct { | ||
src uint64 | ||
dst uint64 | ||
} | ||
|
||
type DestinationGun struct { | ||
l logger.Logger | ||
env deployment.Environment | ||
seqNums map[ChainSelectorPair]*atomic.Uint64 | ||
roundNum *atomic.Int32 | ||
chainSelector uint64 | ||
receiver common.Address | ||
loki *wasp.LokiClient | ||
} | ||
|
||
func NewDestinationGun(l logger.Logger, chainSelector uint64, env deployment.Environment, receiver common.Address, loki *wasp.LokiClient) *DestinationGun { | ||
seqNums := make(map[ChainSelectorPair]*atomic.Uint64) | ||
for _, cs := range env.AllChainSelectorsExcluding([]uint64{chainSelector}) { | ||
|
||
seqNums[ChainSelectorPair{ | ||
src: cs, | ||
dst: chainSelector, | ||
}] = atomic.NewUint64(1) | ||
} | ||
return &DestinationGun{ | ||
l: l, | ||
env: env, | ||
seqNums: seqNums, | ||
roundNum: &atomic.Int32{}, | ||
chainSelector: chainSelector, | ||
receiver: receiver, | ||
loki: loki, | ||
} | ||
} | ||
|
||
func (m *DestinationGun) Call(_ *wasp.Generator) *wasp.Response { | ||
m.roundNum.Add(1) | ||
requestedRound := m.roundNum.Load() | ||
|
||
waspGroup := fmt.Sprintf("%d-%s", m.chainSelector, "messageOnly") | ||
|
||
state, err := ccipchangeset.LoadOnchainState(m.env) | ||
if err != nil { | ||
return &wasp.Response{Error: err.Error(), Group: waspGroup, Failed: true} | ||
} | ||
|
||
src, err := m.MustSourceChain() | ||
if err != nil { | ||
return &wasp.Response{Error: err.Error(), Group: waspGroup, Failed: true} | ||
} | ||
|
||
csPair := ChainSelectorPair{ | ||
src: src, | ||
dst: m.chainSelector, | ||
} | ||
m.seqNums[csPair].Add(1) | ||
m.l.Infow("Starting transmit with ", "RoundNum", requestedRound, "Destination ChainSelector", m.chainSelector, "Source ChainSelector", src, "SequenceNumber", m.seqNums[csPair].Load()) | ||
|
||
r := state.Chains[src].Router | ||
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. please check for nil value in router |
||
|
||
msg, err := m.GetMessage() | ||
if err != nil { | ||
return &wasp.Response{Error: err.Error(), Group: waspGroup, Failed: true} | ||
} | ||
|
||
fee, err := r.GetFee( | ||
&bind.CallOpts{Context: context.Background()}, m.chainSelector, msg) | ||
if err != nil { | ||
m.l.Errorw("could not get fee ", "dstChainSelector", m.chainSelector, "msg", msg, "fee", fee) | ||
return &wasp.Response{Error: err.Error(), Group: waspGroup, Failed: true} | ||
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. it will help if you use deployment.MayBeDataError(err) here, it will show you the revert reason, if there is any |
||
} | ||
m.l.Debugw("setting fee for ", "srcChain", src, "dstChain", m.chainSelector, "fee", fee, "msg", msg) | ||
if msg.FeeToken == common.HexToAddress("0x0") { | ||
m.env.Chains[src].DeployerKey.Value = fee | ||
defer func() { m.env.Chains[src].DeployerKey.Value = nil }() | ||
} | ||
tx, err := r.CcipSend( | ||
m.env.Chains[src].DeployerKey, | ||
m.chainSelector, | ||
msg) | ||
if err != nil { | ||
m.l.Errorw("execution reverted from ", "sourceChain", src, "destchain", m.chainSelector, "err", err, "tx", tx) | ||
return &wasp.Response{Error: err.Error(), Group: waspGroup, Failed: true} | ||
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. same comment about using |
||
} | ||
|
||
lokiLabels, err := setLokiLabels(src, m.chainSelector) | ||
if err != nil { | ||
m.l.Errorw("Failed setting loki labels", "error", err) | ||
} | ||
SendMetricsToLoki(m.l, m.loki, lokiLabels, &LokiMetric{ | ||
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. is this a blocking call? |
||
EventType: transmitted, | ||
Timestamp: time.Now(), | ||
SequenceNumber: m.seqNums[csPair].Load(), | ||
}) | ||
|
||
return &wasp.Response{Failed: false, Group: waspGroup} | ||
} | ||
|
||
// MustSourceChain will return a chain selector to send a message from | ||
func (m *DestinationGun) MustSourceChain() (uint64, error) { | ||
|
||
// TODO: make this smarter by checking if this chain has sent a message recently, if so, switch to the next chain | ||
otherCS := m.env.AllChainSelectorsExcluding([]uint64{m.chainSelector}) | ||
if len(otherCS) == 0 { | ||
return 0, fmt.Errorf("no other chains to send from") | ||
} | ||
index := m.roundNum.Load() % int32(len(otherCS)) | ||
return otherCS[index], nil | ||
} | ||
|
||
// GetMessage will return the message to be sent while considering expected load of different messages | ||
// TODO: implement randomness and different types of messages | ||
func (m *DestinationGun) GetMessage() (router.ClientEVM2AnyMessage, error) { | ||
rcv, err := utils.ABIEncode(`[{"type":"address"}]`, m.receiver) | ||
if err != nil { | ||
m.l.Error("Error encoding receiver address") | ||
return router.ClientEVM2AnyMessage{}, err | ||
} | ||
|
||
return router.ClientEVM2AnyMessage{ | ||
Receiver: rcv, | ||
Data: common.Hex2Bytes("hello world"), | ||
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. How about using some identifier here like msg with id to distinguish. |
||
TokenAmounts: nil, | ||
FeeToken: common.HexToAddress("0x0"), | ||
ExtraArgs: nil, | ||
}, 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.
A lot of code in this can be extracted to common functions. You might need more than one load test in future