diff --git a/integration-tests/load/functions/README.md b/integration-tests/load/functions/README.md deleted file mode 100644 index bcdbe92d524..00000000000 --- a/integration-tests/load/functions/README.md +++ /dev/null @@ -1,79 +0,0 @@ -### Functions & S4 Gateway Load tests - -## Setup -Export vars -``` -export SELECTED_NETWORKS=MUMBAI -export MUMBAI_KEYS=... -export MUMBAI_URLS=... -export LOKI_TOKEN=... -export LOKI_URL=... -``` -See more config options in [config.toml](./config.toml) - -## Usage - -All tests are split by network and in 3 groups: -- HTTP payload only -- Secrets decoding payload only -- Realistic payload with args/http/secrets - -Load test client is [here](../../../contracts/src/v0.8/functions/tests/v1_X/testhelpers/FunctionsLoadTestClient.sol) - -Load is controlled with 2 params: -- RPS -- requests_per_call (generating more events in a loop in the contract) - -`Soak` is a stable workload for which there **must** be no issues - -`Stress` is a peak workload for which issues **must** be analyzed - -Load test client can execute `78 calls per request` at max (gas limit) - -Functions tests: -``` -go test -v -run TestFunctionsLoad/mumbai_functions_soak_test_http -go test -v -run TestFunctionsLoad/mumbai_functions_stress_test_http -go test -v -run TestFunctionsLoad/mumbai_functions_soak_test_only_secrets -go test -v -run TestFunctionsLoad/mumbai_functions_stress_test_only_secrets -go test -v -run TestFunctionsLoad/mumbai_functions_soak_test_real -go test -v -run TestFunctionsLoad/mumbai_functions_stress_test_real -``` - -Gateway tests: -``` -go test -v -run TestGatewayLoad/gateway_secrets_list_soak_test -go test -v -run TestGatewayLoad/gateway_secrets_set_soak_test -``` - -Chaos suite can be combined with any test, can be found [here](../../chaos/functions/full.yaml) - -Default [dashboard](https://chainlinklabs.grafana.net/d/FunctionsV1/functionsv1?orgId=1&from=now-5m&to=now&var-go_test_name=All&var-gen_name=All&var-branch=All&var-commit=All&var-call_group=All&refresh=5s) - -## Redeploying client and funding a new sub -When contracts got redeployed on `Mumbai` just comment these lines in config -``` -# comment both client and sub to automatically create a new pair -client_addr = "0x64a351fbAa61681A5a7e569Cc5A691150c4D73D2" -subscription_id = 23 -``` -Then insert new client addr and subscription number back - -## Debug -Show more logs -``` -export WASP_LOG_LEVEL=debug -``` - -### Dashboards - -Deploying dashboard: -``` -export GRAFANA_URL=... -export GRAFANA_TOKEN=... -export DATA_SOURCE_NAME=... -export DASHBOARD_FOLDER=LoadTests -export DASHBOARD_NAME=FunctionsV1 - -go run dashboard.go -``` \ No newline at end of file diff --git a/integration-tests/load/functions/functions_test.go b/integration-tests/load/functions/functions_test.go deleted file mode 100644 index 90c163ca372..00000000000 --- a/integration-tests/load/functions/functions_test.go +++ /dev/null @@ -1,231 +0,0 @@ -package loadfunctions - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-testing-framework/wasp" - - tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" -) - -func TestFunctionsLoad(t *testing.T) { - generalConfig, err := tc.GetConfig([]string{""}, tc.Functions) - require.NoError(t, err, "failed to get config") - - ft, err := SetupLocalLoadTestEnv(&generalConfig, &generalConfig) - require.NoError(t, err) - - labels := map[string]string{ - "branch": "functions_healthcheck", - "commit": "functions_healthcheck", - } - - MonitorLoadStats(t, ft, labels, &generalConfig) - - t.Run("mumbai functions soak test http", func(t *testing.T) { - config, err := tc.GetConfig([]string{"Soak"}, tc.Functions) - require.NoError(t, err, "failed to get config") - cfg := config.Functions - cfgl := config.Logging.Loki - _, err = wasp.NewProfile(). - Add(wasp.NewGenerator(&wasp.Config{ - T: t, - LoadType: wasp.RPS, - GenName: "functions_soak_gen", - RateLimitUnitDuration: 5 * time.Second, - CallTimeout: 3 * time.Minute, - Schedule: wasp.Plain( - *cfg.Performance.RPS, - cfg.Performance.Duration.Duration, - ), - Gun: NewSingleFunctionCallGun( - ft, - ModeHTTPPayload, - *cfg.Performance.RequestsPerCall, - *cfg.Common.FunctionsCallPayloadHTTP, - *cfg.Common.SecretsSlotID, - *cfg.Common.SecretsVersionID, - []string{}, - *cfg.Common.SubscriptionID, - StringToByte32(*cfg.Common.DONID), - ), - Labels: labels, - LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), - })). - Run(true) - require.NoError(t, err) - }) - - t.Run("mumbai functions stress test http", func(t *testing.T) { - config, err := tc.GetConfig([]string{"Stress"}, tc.Functions) - require.NoError(t, err, "failed to get config") - cfg := config.Functions - cfgl := config.Logging.Loki - _, err = wasp.NewProfile(). - Add(wasp.NewGenerator(&wasp.Config{ - T: t, - LoadType: wasp.RPS, - GenName: "functions_stress_gen", - RateLimitUnitDuration: 5 * time.Second, - CallTimeout: 3 * time.Minute, - Schedule: wasp.Plain( - *cfg.Performance.RPS, - cfg.Performance.Duration.Duration, - ), - Gun: NewSingleFunctionCallGun( - ft, - ModeHTTPPayload, - *cfg.Performance.RequestsPerCall, - *cfg.Common.FunctionsCallPayloadHTTP, - *cfg.Common.SecretsSlotID, - *cfg.Common.SecretsVersionID, - []string{}, - *cfg.Common.SubscriptionID, - StringToByte32(*cfg.Common.DONID), - ), - Labels: labels, - LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), - })). - Run(true) - require.NoError(t, err) - }) - - t.Run("mumbai functions soak test only secrets", func(t *testing.T) { - config, err := tc.GetConfig([]string{"SecretsSoak"}, tc.Functions) - require.NoError(t, err, "failed to get config") - cfg := config.Functions - cfgl := config.Logging.Loki - _, err = wasp.NewProfile(). - Add(wasp.NewGenerator(&wasp.Config{ - T: t, - LoadType: wasp.RPS, - GenName: "functions_soak_gen", - RateLimitUnitDuration: 5 * time.Second, - CallTimeout: 3 * time.Minute, - Schedule: wasp.Plain( - *cfg.Performance.RPS, - cfg.Performance.Duration.Duration, - ), - Gun: NewSingleFunctionCallGun( - ft, - ModeSecretsOnlyPayload, - *cfg.Performance.RequestsPerCall, - *cfg.Common.FunctionsCallPayloadWithSecrets, - *cfg.Common.SecretsSlotID, - *cfg.Common.SecretsVersionID, - []string{}, - *cfg.Common.SubscriptionID, - StringToByte32(*cfg.Common.DONID), - ), - Labels: labels, - LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), - })). - Run(true) - require.NoError(t, err) - }) - - t.Run("mumbai functions stress test only secrets", func(t *testing.T) { - config, err := tc.GetConfig([]string{"SecretsStress"}, tc.Functions) - require.NoError(t, err, "failed to get config") - cfg := config.Functions - cfgl := config.Logging.Loki - _, err = wasp.NewProfile(). - Add(wasp.NewGenerator(&wasp.Config{ - T: t, - LoadType: wasp.RPS, - GenName: "functions_stress_gen", - RateLimitUnitDuration: 5 * time.Second, - CallTimeout: 3 * time.Minute, - Schedule: wasp.Plain( - *cfg.Performance.RPS, - cfg.Performance.Duration.Duration, - ), - Gun: NewSingleFunctionCallGun( - ft, - ModeSecretsOnlyPayload, - *cfg.Performance.RequestsPerCall, - *cfg.Common.FunctionsCallPayloadWithSecrets, - *cfg.Common.SecretsSlotID, - *cfg.Common.SecretsVersionID, - []string{}, - *cfg.Common.SubscriptionID, - StringToByte32(*cfg.Common.DONID), - ), - Labels: labels, - LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), - })). - Run(true) - require.NoError(t, err) - }) - - t.Run("mumbai functions soak test real", func(t *testing.T) { - config, err := tc.GetConfig([]string{"RealSoak"}, tc.Functions) - require.NoError(t, err, "failed to get config") - cfg := config.Functions - cfgl := config.Logging.Loki - _, err = wasp.NewProfile(). - Add(wasp.NewGenerator(&wasp.Config{ - T: t, - LoadType: wasp.RPS, - GenName: "functions_soak_gen", - RateLimitUnitDuration: 5 * time.Second, - CallTimeout: 3 * time.Minute, - Schedule: wasp.Plain( - *cfg.Performance.RPS, - cfg.Performance.Duration.Duration, - ), - Gun: NewSingleFunctionCallGun( - ft, - ModeReal, - *cfg.Performance.RequestsPerCall, - *cfg.Common.FunctionsCallPayloadReal, - *cfg.Common.SecretsSlotID, - *cfg.Common.SecretsVersionID, - []string{"1", "2", "3", "4"}, - *cfg.Common.SubscriptionID, - StringToByte32(*cfg.Common.DONID), - ), - Labels: labels, - LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), - })). - Run(true) - require.NoError(t, err) - }) - - t.Run("mumbai functions stress test real", func(t *testing.T) { - config, err := tc.GetConfig([]string{"RealStress"}, tc.Functions) - require.NoError(t, err, "failed to get config") - cfg := config.Functions - cfgl := config.Logging.Loki - _, err = wasp.NewProfile(). - Add(wasp.NewGenerator(&wasp.Config{ - T: t, - LoadType: wasp.RPS, - GenName: "functions_stress_gen", - RateLimitUnitDuration: 5 * time.Second, - CallTimeout: 3 * time.Minute, - Schedule: wasp.Plain( - *cfg.Performance.RPS, - cfg.Performance.Duration.Duration, - ), - Gun: NewSingleFunctionCallGun( - ft, - ModeReal, - *cfg.Performance.RequestsPerCall, - *cfg.Common.FunctionsCallPayloadReal, - *cfg.Common.SecretsSlotID, - *cfg.Common.SecretsVersionID, - []string{"1", "2", "3", "4"}, - *cfg.Common.SubscriptionID, - StringToByte32(*cfg.Common.DONID), - ), - Labels: labels, - LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), - })). - Run(true) - require.NoError(t, err) - }) -} diff --git a/integration-tests/load/functions/functionscmd/dashboard.go b/integration-tests/load/functions/functionscmd/dashboard.go deleted file mode 100644 index b0a00602f8d..00000000000 --- a/integration-tests/load/functions/functionscmd/dashboard.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "github.com/K-Phoen/grabana/dashboard" - "github.com/K-Phoen/grabana/logs" - "github.com/K-Phoen/grabana/row" - - db "github.com/smartcontractkit/chainlink-testing-framework/wasp/dashboard" -) - -func main() { - lokiDS := "grafanacloud-logs" - d, err := db.NewDashboard(nil, - []dashboard.Option{ - dashboard.Row("DON logs (errors)", - row.Collapse(), - row.WithLogs( - "DON logs", - logs.DataSource(lokiDS), - logs.Span(12), - logs.Height("300px"), - logs.Transparent(), - logs.WithLokiTarget(` - { cluster="staging-us-west-2-main", app=~"clc-ocr2-dr-matic-testnet" } | json | level="error" - `), - )), - }, - ) - if err != nil { - panic(err) - } - if _, err := d.Deploy(); err != nil { - panic(err) - } -} diff --git a/integration-tests/load/functions/gateway.go b/integration-tests/load/functions/gateway.go deleted file mode 100644 index ed813a56858..00000000000 --- a/integration-tests/load/functions/gateway.go +++ /dev/null @@ -1,231 +0,0 @@ -package loadfunctions - -import ( - "bytes" - "crypto/ecdsa" - "crypto/rand" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "math" - "time" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/go-resty/resty/v2" - "github.com/rs/zerolog/log" - "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" - - "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" - "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" - "github.com/smartcontractkit/chainlink/v2/core/services/s4" -) - -type RPCResponse struct { - ID string `json:"id"` - Jsonrpc string `json:"jsonrpc"` - Result struct { - Body struct { - DonID string `json:"don_id"` - MessageID string `json:"message_id"` - Method string `json:"method"` - Payload struct { - NodeResponses []struct { - Body struct { - DonID string `json:"don_id"` - MessageID string `json:"message_id"` - Method string `json:"method"` - Payload struct { - Success bool `json:"success"` - } `json:"payload"` - Receiver string `json:"receiver"` - } `json:"body"` - Signature string `json:"signature"` - } `json:"node_responses"` - Success bool `json:"success"` - } `json:"payload"` - Receiver string `json:"receiver"` - } `json:"body"` - Signature string `json:"signature"` - } `json:"result"` -} - -func UploadS4Secrets(rc *resty.Client, s4Cfg *S4SecretsCfg) (uint8, uint64, error) { - key, err := crypto.HexToECDSA(s4Cfg.PrivateKey) - if err != nil { - return 0, 0, err - } - address := crypto.PubkeyToAddress(key.PublicKey) - var payloadJSON []byte - envelope := s4.Envelope{ - Address: address.Bytes(), - SlotID: s4Cfg.S4SetSlotID, - Version: s4Cfg.S4SetVersion, - Payload: []byte(s4Cfg.S4SetPayload), - Expiration: time.Now().UnixMilli() + s4Cfg.S4SetExpirationPeriod, - } - signature, err := envelope.Sign(key) - if err != nil { - return 0, 0, err - } - - s4SetPayload := functions.SecretsSetRequest{ - SlotID: envelope.SlotID, - Version: envelope.Version, - Expiration: envelope.Expiration, - Payload: []byte(s4Cfg.S4SetPayload), - Signature: signature, - } - - payloadJSON, err = json.Marshal(s4SetPayload) - if err != nil { - return 0, 0, err - } - - msg := &api.Message{ - Body: api.MessageBody{ - MessageId: s4Cfg.MessageID, - Method: s4Cfg.Method, - DonId: s4Cfg.DonID, - Payload: json.RawMessage(payloadJSON), - }, - } - - err = msg.Sign(key) - if err != nil { - return 0, 0, err - } - codec := api.JsonRPCCodec{} - rawMsg, err := codec.EncodeLegacyRequest(msg) - if err != nil { - return 0, 0, err - } - var result *RPCResponse - resp, err := rc.R(). - SetBody(rawMsg). - Post(s4Cfg.GatewayURL) - if err != nil { - return 0, 0, err - } - if resp.StatusCode() != 200 { - return 0, 0, fmt.Errorf("status code was %d, expected 200", resp.StatusCode()) - } - if err := json.Unmarshal(resp.Body(), &result); err != nil { - return 0, 0, err - } - log.Debug().Interface("Result", result).Msg("S4 secrets_set response result") - for _, nodeResponse := range result.Result.Body.Payload.NodeResponses { - if !nodeResponse.Body.Payload.Success { - return 0, 0, errors.New("node response was not successful") - } - } - if envelope.SlotID > math.MaxUint8 { - return 0, 0, fmt.Errorf("slot ID overflows uint8: %d", envelope.SlotID) - } - return uint8(envelope.SlotID), envelope.Version, nil -} - -func ListS4Secrets(rc *resty.Client, s4Cfg *S4SecretsCfg) error { - key, err := crypto.HexToECDSA(s4Cfg.PrivateKey) - if err != nil { - return err - } - - msg := &api.Message{ - Body: api.MessageBody{ - MessageId: s4Cfg.MessageID, - Method: s4Cfg.Method, - DonId: s4Cfg.DonID, - Receiver: s4Cfg.ReceiverAddr, - }, - } - - err = msg.Sign(key) - if err != nil { - return err - } - codec := api.JsonRPCCodec{} - rawMsg, err := codec.EncodeLegacyRequest(msg) - if err != nil { - return err - } - msgdec, err := codec.DecodeLegacyResponse(rawMsg) - if err != nil { - return err - } - log.Debug().Interface("Request", msgdec).Msg("Sending RPC request") - var result map[string]any - resp, err := rc.R(). - SetBody(rawMsg). - Post(s4Cfg.GatewayURL) - if err != nil { - return err - } - if err := json.Unmarshal(resp.Body(), &result); err != nil { - return err - } - log.Debug().Interface("Result", result).Msg("S4 secrets_list response result") - if resp.StatusCode() != 200 { - return fmt.Errorf("status code was %d, expected 200", resp.StatusCode()) - } - return nil -} - -func ParseTDH2Key(data []byte) (*tdh2easy.PublicKey, error) { - pk := &tdh2easy.PublicKey{} - if err := pk.Unmarshal(data); err != nil { - return nil, err - } - return pk, nil -} - -func EncryptS4Secrets(deployerPk *ecdsa.PrivateKey, tdh2Pk *tdh2easy.PublicKey, donKey []byte, msgJSON string) (string, error) { - // 65 bytes PublicKey format, should start with 0x04 to be processed by crypto.UnmarshalPubkey() - b := make([]byte, 1) - b[0] = 0x04 - donKey = bytes.Join([][]byte{b, donKey}, nil) - donPubKey, err := crypto.UnmarshalPubkey(donKey) - if err != nil { - return "", fmt.Errorf("failed to unmarshal DON key: %w", err) - } - eciesDONPubKey := ecies.ImportECDSAPublic(donPubKey) - signature, err := deployerPk.Sign(rand.Reader, []byte(msgJSON), nil) - if err != nil { - return "", fmt.Errorf("failed to sign the msg with Ethereum key: %w", err) - } - signedSecrets, err := json.Marshal(struct { - Signature []byte `json:"signature"` - Message string `json:"message"` - }{ - Signature: signature, - Message: msgJSON, - }) - if err != nil { - return "", fmt.Errorf("failed to marshal signed secrets: %w", err) - } - ct, err := ecies.Encrypt(rand.Reader, eciesDONPubKey, signedSecrets, nil, nil) - if err != nil { - return "", fmt.Errorf("failed to encrypt with DON key: %w", err) - } - ct0xFormat, err := json.Marshal(map[string]any{"0x0": base64.StdEncoding.EncodeToString(ct)}) - if err != nil { - return "", fmt.Errorf("failed to marshal DON key encrypted format: %w", err) - } - ctTDH2Format, err := tdh2easy.Encrypt(tdh2Pk, ct0xFormat) - if err != nil { - return "", fmt.Errorf("failed to encrypt with TDH2 public key: %w", err) - } - tdh2Message, err := ctTDH2Format.Marshal() - if err != nil { - return "", fmt.Errorf("failed to marshal TDH2 encrypted msg: %w", err) - } - finalMsg, err := json.Marshal(map[string]any{ - "encryptedSecrets": "0x" + hex.EncodeToString(tdh2Message), - }) - if err != nil { - return "", fmt.Errorf("failed to marshal secrets msg: %w", err) - } - return string(finalMsg), nil -} diff --git a/integration-tests/load/functions/gateway_gun.go b/integration-tests/load/functions/gateway_gun.go deleted file mode 100644 index a6c3afe4ebd..00000000000 --- a/integration-tests/load/functions/gateway_gun.go +++ /dev/null @@ -1,137 +0,0 @@ -package loadfunctions - -import ( - "crypto/ecdsa" - "fmt" - "math/rand" - "strconv" - "time" - - "github.com/go-resty/resty/v2" - "github.com/rs/zerolog/log" - "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" - - "github.com/smartcontractkit/chainlink-testing-framework/wasp" - - "github.com/smartcontractkit/chainlink/integration-tests/types" -) - -/* SingleFunctionCallGun is a gun that constantly requests randomness for one feed */ - -type GatewaySecretsSetGun struct { - Cfg types.FunctionsTestConfig - Resty *resty.Client - SlotID uint - Method string - EthereumPrivateKey *ecdsa.PrivateKey - ThresholdPublicKey *tdh2easy.PublicKey - DONPublicKey []byte -} - -func NewGatewaySecretsSetGun(cfg types.FunctionsTestConfig, method string, pKey *ecdsa.PrivateKey, tdh2PubKey *tdh2easy.PublicKey, donPubKey []byte) *GatewaySecretsSetGun { - return &GatewaySecretsSetGun{ - Cfg: cfg, - Resty: resty.New(), - Method: method, - EthereumPrivateKey: pKey, - ThresholdPublicKey: tdh2PubKey, - DONPublicKey: donPubKey, - } -} - -func callSecretsSet(m *GatewaySecretsSetGun) *wasp.Response { - randNum := strconv.Itoa(rand.Intn(100000)) - randSlot := rand.Intn(5) - if randSlot < 0 { - panic(fmt.Errorf("negative rand slot: %d", randSlot)) - } - version := time.Now().UnixNano() - if version < 0 { - panic(fmt.Errorf("negative timestamp: %d", version)) - } - expiration := int64(60 * 60 * 1000) - secret := fmt.Sprintf("{\"ltsecret\": \"%s\"}", randNum) - log.Debug(). - Int("SlotID", randSlot). - Str("MessageID", randNum). - Int64("Version", version). - Int64("Expiration", expiration). - Str("Secret", secret). - Msg("Sending S4 envelope") - secrets, err := EncryptS4Secrets( - m.EthereumPrivateKey, - m.ThresholdPublicKey, - m.DONPublicKey, - secret, - ) - if err != nil { - return &wasp.Response{Error: err.Error(), Failed: true} - } - network := m.Cfg.GetNetworkConfig().SelectedNetworks[0] - if len(m.Cfg.GetNetworkConfig().WalletKeys[network]) < 1 { - panic("no wallet keys found for " + network) - } - - cfg := m.Cfg.GetFunctionsConfig() - _, _, err = UploadS4Secrets(m.Resty, &S4SecretsCfg{ - GatewayURL: *cfg.Common.GatewayURL, - PrivateKey: m.Cfg.GetNetworkConfig().WalletKeys[network][0], - MessageID: randNum, - Method: "secrets_set", - DonID: *cfg.Common.DONID, - S4SetSlotID: uint(randSlot), - S4SetVersion: uint64(version), - S4SetExpirationPeriod: expiration, - S4SetPayload: secrets, - }) - if err != nil { - return &wasp.Response{Error: err.Error(), Failed: true} - } - return &wasp.Response{} -} - -func callSecretsList(m *GatewaySecretsSetGun) *wasp.Response { - randNum := strconv.Itoa(rand.Intn(100000)) - randSlot := rand.Intn(5) - if randSlot < 0 { - panic(fmt.Errorf("negative rand slot: %d", randSlot)) - } - version := time.Now().UnixNano() - if version < 0 { - panic(fmt.Errorf("negative timestamp: %d", version)) - } - expiration := int64(60 * 60 * 1000) - network := m.Cfg.GetNetworkConfig().SelectedNetworks[0] - if len(m.Cfg.GetNetworkConfig().WalletKeys[network]) < 1 { - panic("no wallet keys found for " + network) - } - cfg := m.Cfg.GetFunctionsConfig() - if err := ListS4Secrets(m.Resty, &S4SecretsCfg{ - GatewayURL: *cfg.Common.GatewayURL, - ReceiverAddr: *cfg.Common.Receiver, - PrivateKey: m.Cfg.GetNetworkConfig().WalletKeys[network][0], - MessageID: randNum, - Method: m.Method, - DonID: *cfg.Common.DONID, - S4SetSlotID: uint(randSlot), - S4SetVersion: uint64(version), - S4SetExpirationPeriod: expiration, - }); err != nil { - return &wasp.Response{Error: err.Error(), Failed: true} - } - return &wasp.Response{} -} - -// Call implements example gun call, assertions on response bodies should be done here -func (m *GatewaySecretsSetGun) Call(_ *wasp.Generator) *wasp.Response { - var res *wasp.Response - switch m.Method { - case "secrets_set": - res = callSecretsSet(m) - case "secrets_list": - res = callSecretsList(m) - default: - panic("gateway gun must use either 'secrets_set' or 'list' methods") - } - return res -} diff --git a/integration-tests/load/functions/gateway_test.go b/integration-tests/load/functions/gateway_test.go deleted file mode 100644 index 9812b7ea081..00000000000 --- a/integration-tests/load/functions/gateway_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package loadfunctions - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-testing-framework/wasp" - - tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" - "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" -) - -func TestGatewayLoad(t *testing.T) { - listConfig, err := tc.GetConfig([]string{"GatewayList"}, tc.Functions) - require.NoError(t, err) - cfgl := listConfig.Logging.Loki - - require.NoError(t, err) - ft, err := SetupLocalLoadTestEnv(&listConfig, &listConfig) - require.NoError(t, err) - - labels := map[string]string{ - "branch": "gateway_healthcheck", - "commit": "gateway_healthcheck", - } - - secretsListCfg := &wasp.Config{ - LoadType: wasp.RPS, - GenName: functions.MethodSecretsList, - Schedule: wasp.Plain( - *listConfig.Functions.Performance.RPS, - listConfig.Functions.Performance.Duration.Duration, - ), - Gun: NewGatewaySecretsSetGun( - &listConfig, - functions.MethodSecretsList, - ft.EthereumPrivateKey, - ft.ThresholdPublicKey, - ft.DONPublicKey, - ), - Labels: labels, - LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), - } - - setConfig, err := tc.GetConfig([]string{"GatewaySet"}, tc.Functions) - require.NoError(t, err) - - secretsSetCfg := &wasp.Config{ - LoadType: wasp.RPS, - GenName: functions.MethodSecretsSet, - Schedule: wasp.Plain( - *setConfig.Functions.Performance.RPS, - setConfig.Functions.Performance.Duration.Duration, - ), - Gun: NewGatewaySecretsSetGun( - &setConfig, - functions.MethodSecretsSet, - ft.EthereumPrivateKey, - ft.ThresholdPublicKey, - ft.DONPublicKey, - ), - Labels: labels, - LokiConfig: wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken), - } - - t.Run("gateway secrets list soak test", func(t *testing.T) { - secretsListCfg.T = t - _, err := wasp.NewProfile(). - Add(wasp.NewGenerator(secretsListCfg)). - Run(true) - require.NoError(t, err) - }) - - t.Run("gateway secrets set soak test", func(t *testing.T) { - secretsListCfg.T = t - _, err := wasp.NewProfile(). - Add(wasp.NewGenerator(secretsSetCfg)). - Run(true) - require.NoError(t, err) - }) -} diff --git a/integration-tests/load/functions/onchain_monitoring.go b/integration-tests/load/functions/onchain_monitoring.go deleted file mode 100644 index ba76b84acbb..00000000000 --- a/integration-tests/load/functions/onchain_monitoring.go +++ /dev/null @@ -1,70 +0,0 @@ -package loadfunctions - -import ( - "maps" - "testing" - "time" - - "github.com/rs/zerolog/log" - - "github.com/smartcontractkit/chainlink-testing-framework/wasp" - - ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" -) - -/* Monitors on-chain stats of LoadConsumer and pushes them to Loki every second */ - -const ( - LokiTypeLabel = "functions_contracts_load_summary" - ErrMetrics = "failed to get Functions load test metrics" - ErrLokiClient = "failed to create Loki client for monitoring" - ErrLokiPush = "failed to push monitoring metrics to Loki" -) - -type LoadStats struct { - Succeeded uint32 - Errored uint32 - Empty uint32 -} - -func MonitorLoadStats(t *testing.T, ft *FunctionsTest, labels map[string]string, config ctf_config.GlobalTestConfig) { - go func() { - updatedLabels := make(map[string]string) - maps.Copy(updatedLabels, labels) - updatedLabels["type"] = LokiTypeLabel - updatedLabels["go_test_name"] = t.Name() - updatedLabels["gen_name"] = "performance" - cfgl := config.GetLoggingConfig().Loki - lokiConfig := wasp.NewLokiConfig(cfgl.Endpoint, cfgl.TenantId, cfgl.BasicAuth, cfgl.BearerToken) - lc, err := wasp.NewLokiClient(lokiConfig) - if err != nil { - log.Error().Err(err).Msg(ErrLokiClient) - return - } - if err := ft.LoadTestClient.ResetStats(); err != nil { - log.Error().Err(err).Msg("failed to reset load test client stats") - } - for { - time.Sleep(5 * time.Second) - stats, err := ft.LoadTestClient.GetStats() - if err != nil { - log.Error().Err(err).Msg(ErrMetrics) - } - if stats != nil { - log.Info(). - Hex("LastReqID", []byte(stats.LastRequestID)). - Str("LastResponse", stats.LastResponse). - Str("LastError", stats.LastError). - Uint32("Total", stats.Total). - Uint32("Succeeded", stats.Succeeded). - Uint32("Errored", stats.Errored). - Uint32("Empty", stats.Empty).Msg("On-chain stats for load test client") - if err := lc.HandleStruct(wasp.LabelsMapToModel(updatedLabels), time.Now(), stats); err != nil { - log.Error().Err(err).Msg(ErrLokiPush) - } - } else { - log.Warn().Msg("No stats to push to Loki") - } - } - }() -} diff --git a/integration-tests/load/functions/request_gun.go b/integration-tests/load/functions/request_gun.go deleted file mode 100644 index 2947617ae8b..00000000000 --- a/integration-tests/load/functions/request_gun.go +++ /dev/null @@ -1,110 +0,0 @@ -package loadfunctions - -import ( - "github.com/smartcontractkit/chainlink-testing-framework/wasp" -) - -type TestMode int - -const ( - ModeHTTPPayload TestMode = iota - ModeSecretsOnlyPayload - ModeReal -) - -type SingleFunctionCallGun struct { - ft *FunctionsTest - mode TestMode - times uint32 - source string - slotID uint8 - slotVersion uint64 - args []string - subscriptionID uint64 - jobID [32]byte -} - -func NewSingleFunctionCallGun( - ft *FunctionsTest, - mode TestMode, - times uint32, - source string, - slotID uint8, - slotVersion uint64, - args []string, - subscriptionID uint64, - jobID [32]byte, -) *SingleFunctionCallGun { - return &SingleFunctionCallGun{ - ft: ft, - mode: mode, - times: times, - source: source, - slotID: slotID, - slotVersion: slotVersion, - args: args, - subscriptionID: subscriptionID, - jobID: jobID, - } -} - -func (m *SingleFunctionCallGun) callReal() *wasp.Response { - err := m.ft.LoadTestClient.SendRequestWithDONHostedSecrets( - m.times, - m.source, - m.slotID, - m.slotVersion, - m.args, - m.subscriptionID, - m.jobID, - ) - if err != nil { - return &wasp.Response{Error: err.Error(), Failed: true} - } - return &wasp.Response{} -} - -func (m *SingleFunctionCallGun) callWithSecrets() *wasp.Response { - err := m.ft.LoadTestClient.SendRequestWithDONHostedSecrets( - m.times, - m.source, - m.slotID, - m.slotVersion, - m.args, - m.subscriptionID, - m.jobID, - ) - if err != nil { - return &wasp.Response{Error: err.Error(), Failed: true} - } - return &wasp.Response{} -} - -func (m *SingleFunctionCallGun) callWithHTTP() *wasp.Response { - err := m.ft.LoadTestClient.SendRequest( - m.times, - m.source, - []byte{}, - m.args, - m.subscriptionID, - m.jobID, - ) - if err != nil { - return &wasp.Response{Error: err.Error(), Failed: true} - } - return &wasp.Response{} -} - -// Call implements example gun call, assertions on response bodies should be done here -func (m *SingleFunctionCallGun) Call(_ *wasp.Generator) *wasp.Response { - switch m.mode { - case ModeSecretsOnlyPayload: - return m.callWithSecrets() - case ModeHTTPPayload: - return m.callWithHTTP() - case ModeReal: - return m.callReal() - default: - panic("test mode must be ModeSecretsOnlyPayload, ModeHTTPPayload or ModeReal") - } -} diff --git a/integration-tests/load/functions/setup.go b/integration-tests/load/functions/setup.go deleted file mode 100644 index 99c4bf92132..00000000000 --- a/integration-tests/load/functions/setup.go +++ /dev/null @@ -1,181 +0,0 @@ -package loadfunctions - -import ( - "crypto/ecdsa" - "fmt" - "math/big" - mrand "math/rand" - "strconv" - "time" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/go-resty/resty/v2" - "github.com/rs/zerolog/log" - "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" - - ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" - "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" - seth_utils "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/seth" - "github.com/smartcontractkit/chainlink-testing-framework/seth" - - "github.com/smartcontractkit/chainlink/integration-tests/contracts" - "github.com/smartcontractkit/chainlink/integration-tests/types" - - "github.com/smartcontractkit/chainlink-evm/pkg/utils" -) - -type FunctionsTest struct { - SethClient seth.Client - LinkToken contracts.LinkToken - Coordinator contracts.FunctionsCoordinator - Router contracts.FunctionsRouter - LoadTestClient contracts.FunctionsLoadTestClient - EthereumPrivateKey *ecdsa.PrivateKey - EthereumPublicKey *ecdsa.PublicKey - ThresholdPublicKey *tdh2easy.PublicKey - DONPublicKey []byte - ThresholdPublicKeyBytes []byte - ThresholdEncryptedSecrets string -} - -type S4SecretsCfg struct { - GatewayURL string - PrivateKey string - ReceiverAddr string - MessageID string - Method string - DonID string - S4SetSlotID uint - S4SetVersion uint64 - S4SetExpirationPeriod int64 - S4SetPayload string -} - -func SetupLocalLoadTestEnv(globalConfig ctf_config.GlobalTestConfig, functionsConfig types.FunctionsTestConfig) (*FunctionsTest, error) { - selectedNetwork := networks.MustGetSelectedNetworkConfig(globalConfig.GetNetworkConfig())[0] - sethClient, err := seth_utils.GetChainClient(globalConfig, selectedNetwork) - if err != nil { - return nil, err - } - - cfg := functionsConfig.GetFunctionsConfig() - - lt, err := contracts.DeployLinkTokenContract(log.Logger, sethClient) - if err != nil { - return nil, err - } - coord, err := contracts.LoadFunctionsCoordinator(sethClient, *cfg.Common.Coordinator) - if err != nil { - return nil, err - } - router, err := contracts.LoadFunctionsRouter(log.Logger, sethClient, *cfg.Common.Router) - if err != nil { - return nil, err - } - var loadTestClient contracts.FunctionsLoadTestClient - if cfg.Common.LoadTestClient != nil && *cfg.Common.LoadTestClient != "" { - loadTestClient, err = contracts.LoadFunctionsLoadTestClient(sethClient, *cfg.Common.LoadTestClient) - } else { - loadTestClient, err = contracts.DeployFunctionsLoadTestClient(sethClient, *cfg.Common.Router) - } - if err != nil { - return nil, err - } - if cfg.Common.SubscriptionID == nil { - log.Info().Msg("Creating new subscription") - subID, err := router.CreateSubscriptionWithConsumer(loadTestClient.Address()) - if err != nil { - return nil, fmt.Errorf("failed to create a new subscription: %w", err) - } - encodedSubID, err := utils.ABIEncode(`[{"type":"uint64"}]`, subID) - if err != nil { - return nil, fmt.Errorf("failed to encode subscription ID for funding: %w", err) - } - _, err = lt.TransferAndCall(router.Address(), big.NewInt(0).Mul(cfg.Common.SubFunds, big.NewInt(1e18)), encodedSubID) - if err != nil { - return nil, fmt.Errorf("failed to transferAndCall router, LINK funding: %w", err) - } - cfg.Common.SubscriptionID = &subID - } - pKey, pubKey, err := parseEthereumPrivateKey(selectedNetwork.PrivateKeys[0]) - if err != nil { - return nil, fmt.Errorf("failed to load Ethereum private key: %w", err) - } - tpk, err := coord.GetThresholdPublicKey() - if err != nil { - return nil, fmt.Errorf("failed to get Threshold public key: %w", err) - } - log.Info().Hex("ThresholdPublicKeyBytesHex", tpk).Msg("Loaded coordinator keys") - donPubKey, err := coord.GetDONPublicKey() - if err != nil { - return nil, fmt.Errorf("failed to get DON public key: %w", err) - } - log.Info().Hex("DONPublicKeyHex", donPubKey).Msg("Loaded DON key") - tdh2pk, err := ParseTDH2Key(tpk) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal tdh2 public key: %w", err) - } - var encryptedSecrets string - if cfg.Common.Secrets != nil && *cfg.Common.Secrets != "" { - encryptedSecrets, err = EncryptS4Secrets(pKey, tdh2pk, donPubKey, *cfg.Common.Secrets) - if err != nil { - return nil, fmt.Errorf("failed to generate tdh2 secrets: %w", err) - } - randInt := mrand.Intn(5) - if randInt < 0 { - return nil, fmt.Errorf("negative random int: %d", randInt) - } - now := time.Now().UnixNano() - if now < 0 { - return nil, fmt.Errorf("negative timestamp: %d", now) - } - slotID, slotVersion, err := UploadS4Secrets(resty.New(), &S4SecretsCfg{ - GatewayURL: *cfg.Common.GatewayURL, - PrivateKey: selectedNetwork.PrivateKeys[0], - MessageID: strconv.Itoa(mrand.Intn(100000-1) + 1), - Method: "secrets_set", - DonID: *cfg.Common.DONID, - S4SetSlotID: uint(randInt), - S4SetVersion: uint64(now), - S4SetExpirationPeriod: 60 * 60 * 1000, - S4SetPayload: encryptedSecrets, - }) - if err != nil { - return nil, fmt.Errorf("failed to upload secrets to S4: %w", err) - } - cfg.Common.SecretsSlotID = &slotID - cfg.Common.SecretsVersionID = &slotVersion - log.Info(). - Uint8("SlotID", slotID). - Uint64("SlotVersion", slotVersion). - Msg("Set new secret") - } - return &FunctionsTest{ - SethClient: *sethClient, - LinkToken: lt, - Coordinator: coord, - Router: router, - LoadTestClient: loadTestClient, - EthereumPrivateKey: pKey, - EthereumPublicKey: pubKey, - ThresholdPublicKey: tdh2pk, - ThresholdPublicKeyBytes: tpk, - ThresholdEncryptedSecrets: encryptedSecrets, - DONPublicKey: donPubKey, - }, nil -} - -func parseEthereumPrivateKey(pk string) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) { - pKey, err := crypto.HexToECDSA(pk) - if err != nil { - return nil, nil, fmt.Errorf("failed to convert Ethereum key from hex: %w", err) - } - - publicKey := pKey.Public() - pubKey, ok := publicKey.(*ecdsa.PublicKey) - if !ok { - return nil, nil, fmt.Errorf("failed to get public key from Ethereum private key: %w", err) - } - log.Info().Str("Address", crypto.PubkeyToAddress(*pubKey).Hex()).Msg("Parsed private key for address") - return pKey, pubKey, nil -} diff --git a/integration-tests/load/functions/util.go b/integration-tests/load/functions/util.go deleted file mode 100644 index d5a4a2d6061..00000000000 --- a/integration-tests/load/functions/util.go +++ /dev/null @@ -1,15 +0,0 @@ -package loadfunctions - -// StringToByte32 transforms a single string into a [32]byte value -func StringToByte32(s string) [32]byte { - var result [32]byte - - for i, ch := range []byte(s) { - if i > 31 { - break - } - result[i] = ch - } - - return result -} diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 55037b9c60b..13bdbd16ef2 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -22,7 +22,6 @@ require ( github.com/aptos-labs/aptos-go-sdk v1.12.0 github.com/ethereum/go-ethereum v1.17.1 github.com/gagliardetto/solana-go v1.13.0 - github.com/go-resty/resty/v2 v2.17.2 github.com/pelletier/go-toml/v2 v2.2.4 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.34.0 @@ -41,7 +40,6 @@ require ( github.com/smartcontractkit/chainlink-testing-framework/lib v1.54.7 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.5 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.51.2 - github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c github.com/stretchr/testify v1.11.1 github.com/wiremock/go-wiremock v1.9.0 go.uber.org/atomic v1.11.0 @@ -256,6 +254,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/go-redsync/redsync/v4 v4.13.0 // indirect + github.com/go-resty/resty/v2 v2.17.2 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect @@ -523,6 +522,7 @@ require ( github.com/smartcontractkit/mcms v0.38.2 // indirect github.com/smartcontractkit/smdkg v0.0.0-20251029093710-c38905e58aeb // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de // indirect + github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20251120172354-e8ec0386b06c // indirect github.com/smartcontractkit/wsrpc v0.8.5-0.20250502134807-c57d3d995945 // indirect github.com/sony/gobreaker/v2 v2.1.0 // indirect github.com/spf13/cast v1.10.0 // indirect