From b603fd7d8b5bca7d33741ac391df2089fd854619 Mon Sep 17 00:00:00 2001 From: george-dorin Date: Thu, 5 Dec 2024 15:14:29 +0200 Subject: [PATCH] Add hint and refund validation --- core/services/job/job_orm_test.go | 93 ++++++++++++++++++++++++++++++- core/services/job/orm.go | 76 +++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/core/services/job/job_orm_test.go b/core/services/job/job_orm_test.go index 9db99fcd48d..15ea6aebe77 100644 --- a/core/services/job/job_orm_test.go +++ b/core/services/job/job_orm_test.go @@ -2070,7 +2070,8 @@ func TestORM_CreateJob_OCR2_With_DualTransmission(t *testing.T) { require.ErrorContains(t, jobORM.CreateJob(ctx, &jb), "invalid transmitter address in dual transmission config") dtTransmitterAddress := cltest.MustGenerateRandomKey(t) - completeDualTransmissionSpec := fmt.Sprintf(` + + metaNotSliceDualTransmissionSpec := fmt.Sprintf(` enableDualTransmission=true [relayConfig.dualTransmission] contractAddress = '0x613a38AC1659769640aaE063C651F48E0250454C' @@ -2081,6 +2082,96 @@ func TestORM_CreateJob_OCR2_With_DualTransmission(t *testing.T) { `, dtTransmitterAddress.Address.String()) + jb, err = ocr2validate.ValidatedOracleSpecToml(testutils.Context(t), config.OCR2(), config.Insecure(), baseJobSpec+metaNotSliceDualTransmissionSpec, nil) + require.NoError(t, err) + require.ErrorContains(t, jobORM.CreateJob(ctx, &jb), "dual transmission meta value key1 is not a slice") + + hintNotValidDualTransmissionSpec := fmt.Sprintf(` + enableDualTransmission=true + [relayConfig.dualTransmission] + contractAddress = '0x613a38AC1659769640aaE063C651F48E0250454C' + transmitterAddress = '%s' + [relayConfig.dualTransmission.meta] + hint = ['some-invalid-hint'] + key2 = ['val2','val3'] + `, + dtTransmitterAddress.Address.String()) + + jb, err = ocr2validate.ValidatedOracleSpecToml(testutils.Context(t), config.OCR2(), config.Insecure(), baseJobSpec+hintNotValidDualTransmissionSpec, nil) + require.NoError(t, err) + require.ErrorContains(t, jobORM.CreateJob(ctx, &jb), "dual transmission meta.hint value some-invalid-hint should be one of the following [contract_address function_selector logs calldata default_logs]") + + invalidRefundFormatDualTransmissionSpec := fmt.Sprintf(` + enableDualTransmission=true + [relayConfig.dualTransmission] + contractAddress = '0x613a38AC1659769640aaE063C651F48E0250454C' + transmitterAddress = '%s' + [relayConfig.dualTransmission.meta] + hint = ['calldata','logs'] + refund = ['0x00'] + `, + dtTransmitterAddress.Address.String()) + + jb, err = ocr2validate.ValidatedOracleSpecToml(testutils.Context(t), config.OCR2(), config.Insecure(), baseJobSpec+invalidRefundFormatDualTransmissionSpec, nil) + require.NoError(t, err) + require.ErrorContains(t, jobORM.CreateJob(ctx, &jb), "invalid dual transmission refund, format should be
:") + + invalidRefundAddressFormatDualTransmissionSpec := fmt.Sprintf(` + enableDualTransmission=true + [relayConfig.dualTransmission] + contractAddress = '0x613a38AC1659769640aaE063C651F48E0250454C' + transmitterAddress = '%s' + [relayConfig.dualTransmission.meta] + hint = ['calldata','logs'] + refund = ['0x000:50'] + `, + dtTransmitterAddress.Address.String()) + + jb, err = ocr2validate.ValidatedOracleSpecToml(testutils.Context(t), config.OCR2(), config.Insecure(), baseJobSpec+invalidRefundAddressFormatDualTransmissionSpec, nil) + require.NoError(t, err) + require.ErrorContains(t, jobORM.CreateJob(ctx, &jb), "invalid dual transmission refund address, 0x000 is not a valid address") + + invalidRefundPercentFormatDualTransmissionSpec := fmt.Sprintf(` + enableDualTransmission=true + [relayConfig.dualTransmission] + contractAddress = '0x613a38AC1659769640aaE063C651F48E0250454C' + transmitterAddress = '%s' + [relayConfig.dualTransmission.meta] + hint = ['calldata','logs'] + refund = ['0x0000000000000000000000000000000000000000:A'] + `, + dtTransmitterAddress.Address.String()) + + jb, err = ocr2validate.ValidatedOracleSpecToml(testutils.Context(t), config.OCR2(), config.Insecure(), baseJobSpec+invalidRefundPercentFormatDualTransmissionSpec, nil) + require.NoError(t, err) + require.ErrorContains(t, jobORM.CreateJob(ctx, &jb), "invalid dual transmission refund percent, A is not a number") + + invalidRefundPercentTotalFormatDualTransmissionSpec := fmt.Sprintf(` + enableDualTransmission=true + [relayConfig.dualTransmission] + contractAddress = '0x613a38AC1659769640aaE063C651F48E0250454C' + transmitterAddress = '%s' + [relayConfig.dualTransmission.meta] + hint = ['calldata','logs'] + refund = ['0x0000000000000000000000000000000000000000:50','0x0000000000000000000000000000000000000001:50'] + `, + dtTransmitterAddress.Address.String()) + + jb, err = ocr2validate.ValidatedOracleSpecToml(testutils.Context(t), config.OCR2(), config.Insecure(), baseJobSpec+invalidRefundPercentTotalFormatDualTransmissionSpec, nil) + require.NoError(t, err) + require.ErrorContains(t, jobORM.CreateJob(ctx, &jb), "invalid dual transmission refund percentages, total sum of percentages must be less than 100") + + completeDualTransmissionSpec := fmt.Sprintf(` + enableDualTransmission=true + [relayConfig.dualTransmission] + contractAddress = '0x613a38AC1659769640aaE063C651F48E0250454C' + transmitterAddress = '%s' + [relayConfig.dualTransmission.meta] + key1 = ['val1'] + key2 = ['val2','val3'] + `, + dtTransmitterAddress.Address.String()) + jb, err = ocr2validate.ValidatedOracleSpecToml(testutils.Context(t), config.OCR2(), config.Insecure(), baseJobSpec+completeDualTransmissionSpec, nil) require.NoError(t, err) diff --git a/core/services/job/orm.go b/core/services/job/orm.go index 5e8b5ce127f..08b91063173 100644 --- a/core/services/job/orm.go +++ b/core/services/job/orm.go @@ -7,6 +7,8 @@ import ( "fmt" "reflect" "slices" + "strconv" + "strings" "time" "github.com/ethereum/go-ethereum/common" @@ -324,9 +326,19 @@ func (o *orm) CreateJob(ctx context.Context, jb *Job) error { return errors.New("invalid transmitter address in dual transmission config") } + rawMeta, ok := dualTransmissionConfig["meta"].(map[string]interface{}) + if !ok { + return errors.New("invalid dual transmission meta") + } + + if err = validateDualTransmissionMeta(rawMeta); err != nil { + return err + } + if err = validateKeyStoreMatchForRelay(ctx, jb.OCR2OracleSpec.Relay, tx.keyStore, dtTransmitterAddress); err != nil { return errors.Wrap(err, "unknown dual transmission transmitterAddress") } + } specID, err := tx.insertOCR2OracleSpec(ctx, jb.OCR2OracleSpec) @@ -1643,3 +1655,67 @@ func (r legacyGasStationServerSpecRow) toLegacyGasStationServerSpec() *LegacyGas func (o *orm) loadJobSpecErrors(ctx context.Context, jb *Job) error { return errors.Wrapf(o.ds.SelectContext(ctx, &jb.JobSpecErrors, `SELECT * FROM job_spec_errors WHERE job_id = $1`, jb.ID), "failed to load job spec errors for job %d", jb.ID) } + +func validateDualTransmissionHint(vals []interface{}) error { + accepted := []string{"contract_address", "function_selector", "logs", "calldata", "default_logs"} + for _, v := range vals { + valString, ok := v.(string) + if !ok { + return errors.Errorf("dual transmission meta value %v is not a string", v) + } + if !slices.Contains(accepted, valString) { + return errors.Errorf("dual transmission meta.hint value %s should be one of the following %s", valString, accepted) + } + } + return nil +} + +func validateDualTransmissionRefund(vals []interface{}) error { + totalRefund := 0 + for _, v := range vals { + valString, ok := v.(string) + if !ok { + return errors.Errorf("dual transmission meta value %v is not a string", v) + } + + s := strings.Split(valString, ":") + if len(s) != 2 { + return errors.New("invalid dual transmission refund, format should be
:") + } + if !common.IsHexAddress(s[0]) { + return errors.Errorf("invalid dual transmission refund address, %s is not a valid address", s[0]) + } + percent, err := strconv.Atoi(s[1]) + if err != nil { + return errors.Errorf("invalid dual transmission refund percent, %s is not a number", s[1]) + } + totalRefund += percent + } + + if totalRefund >= 100 { + return errors.New("invalid dual transmission refund percentages, total sum of percentages must be less than 100") + } + return nil +} + +func validateDualTransmissionMeta(meta map[string]interface{}) error { + for k, v := range meta { + metaFieldValues, ok := v.([]interface{}) + if !ok { + return errors.Errorf("dual transmission meta value %s is not a slice", k) + } + if k == "hint" { + if err := validateDualTransmissionHint(metaFieldValues); err != nil { + return err + } + } + + if k == "refund" { + if err := validateDualTransmissionRefund(metaFieldValues); err != nil { + return err + } + } + } + + return nil +}