From a9f86b51bf8d3a8548d4870c91f3e82669f5f21a Mon Sep 17 00:00:00 2001 From: cawthorne Date: Thu, 24 Oct 2024 08:31:59 +0100 Subject: [PATCH] Handle Hex String in EA Telemetry (#14827) * Handle Hex String in Utils.ToDecimal * add tests * add changeset * add #added to changeset * handle hex strings only in EA Telemetry * isolate hex tests * update changset * lint * fix error return usage * fix comment * fix tests * lint * try different return * test bump * test bump * test bump 2 * lint * lint * return decimal.Decimal{} instead of decimal.Zero in error case * update functon comments * use assert.InDelta instead of assert.Equal --- .changeset/lemon-ads-fix.md | 5 ++ core/services/ocrcommon/telemetry.go | 28 ++++++- core/services/ocrcommon/telemetry_test.go | 94 +++++++++++++++++++++++ core/utils/decimal.go | 3 + 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 .changeset/lemon-ads-fix.md diff --git a/.changeset/lemon-ads-fix.md b/.changeset/lemon-ads-fix.md new file mode 100644 index 00000000000..f7f3438eb23 --- /dev/null +++ b/.changeset/lemon-ads-fix.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#added Handle Hex String in EA Telemetry diff --git a/core/services/ocrcommon/telemetry.go b/core/services/ocrcommon/telemetry.go index 810952f455e..e20b2485d86 100644 --- a/core/services/ocrcommon/telemetry.go +++ b/core/services/ocrcommon/telemetry.go @@ -5,8 +5,10 @@ import ( "encoding/json" "fmt" "math/big" + "strings" "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" "github.com/smartcontractkit/libocr/commontypes" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "google.golang.org/protobuf/proto" @@ -220,13 +222,24 @@ func parseEATelemetry(b []byte) (EATelemetry, error) { } // getJsonParsedValue checks if the next logical task is of type pipeline.TaskTypeJSONParse and trys to return -// the response as a *big.Int +// the response as a *big.Int. +// Currently utils.ToDecimal cannot handle hex strings, so this function also has a special case, +// to check and handle if the result is a hex string, if the call to utils.ToDecimal fails. +// Draft PR to add hex string handling to utils.ToDecimal: https://github.com/smartcontractkit/chainlink/pull/14841 func getJsonParsedValue(trr pipeline.TaskRunResult, trrs *pipeline.TaskRunResults) *float64 { nextTask := trrs.GetNextTaskOf(trr) if nextTask != nil && nextTask.Task.Type() == pipeline.TaskTypeJSONParse { asDecimal, err := utils.ToDecimal(nextTask.Result.Value) if err != nil { - return nil + v, ok := nextTask.Result.Value.(string) + if !ok { + return nil + } + hexAnswer, success := hexStringToDecimal(v) + if !success { + return nil + } + asDecimal = hexAnswer } toFloat, _ := asDecimal.Float64() return &toFloat @@ -234,6 +247,17 @@ func getJsonParsedValue(trr pipeline.TaskRunResult, trrs *pipeline.TaskRunResult return nil } +// hexStringToDecimal takes in a hex string, and returns (decimal.Decimal, bool), bool indicates success status +func hexStringToDecimal(hexString string) (decimal.Decimal, bool) { + hexString = strings.TrimPrefix(hexString, "0x") + n := new(big.Int) + _, success := n.SetString(hexString, 16) + if !success { + return decimal.Decimal{}, false + } + return decimal.NewFromBigInt(n, 0), true +} + // getObservation checks pipeline.FinalResult and extracts the observation func (e *EnhancedTelemetryService[T]) getObservation(finalResult *pipeline.FinalResult) int64 { singularResult, err := finalResult.SingularResult() diff --git a/core/services/ocrcommon/telemetry_test.go b/core/services/ocrcommon/telemetry_test.go index ed64e45c2db..8fac0ab2cbf 100644 --- a/core/services/ocrcommon/telemetry_test.go +++ b/core/services/ocrcommon/telemetry_test.go @@ -188,6 +188,100 @@ func TestGetJsonParsedValue(t *testing.T) { assert.Nil(t, resp) } +func TestGetJsonParsedValueHexValues(t *testing.T) { + trrsHexData := pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "test-bridge-1", + BaseTask: pipeline.NewBaseTask(0, "ds1", nil, nil, 0), + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds1_parse", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: "0x1abcf", + }, + }, + } + + resp := getJsonParsedValue(trrsHexData[0], &trrsHexData) + assert.InDelta(t, 109519.0, *resp, 0) + + trrsHexData = pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "test-bridge-2", + BaseTask: pipeline.NewBaseTask(0, "ds2", nil, nil, 0), + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds2_parse", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: "1abcf", + }, + }, + } + + resp = getJsonParsedValue(trrsHexData[0], &trrsHexData) + assert.InDelta(t, 109519.0, *resp, 0) + + trrsHexData = pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "test-bridge-3", + BaseTask: pipeline.NewBaseTask(0, "ds3", nil, nil, 0), + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds3_parse", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: "0x1akbcf", + }, + }, + } + + resp = getJsonParsedValue(trrsHexData[0], &trrsHexData) + assert.Nil(t, resp) + + trrsHexData = pipeline.TaskRunResults{ + pipeline.TaskRunResult{ + Task: &pipeline.BridgeTask{ + Name: "test-bridge-4", + BaseTask: pipeline.NewBaseTask(0, "ds4", nil, nil, 0), + }, + Result: pipeline.Result{ + Value: bridgeResponse, + }, + }, + pipeline.TaskRunResult{ + Task: &pipeline.JSONParseTask{ + BaseTask: pipeline.NewBaseTask(1, "ds4_parse", nil, nil, 1), + }, + Result: pipeline.Result{ + Value: "1akbcf", + }, + }, + } + + resp = getJsonParsedValue(trrsHexData[0], &trrsHexData) + assert.Nil(t, resp) +} + func TestSendEATelemetry(t *testing.T) { wg := sync.WaitGroup{} ingressClient := mocks.NewTelemetryService(t) diff --git a/core/utils/decimal.go b/core/utils/decimal.go index 48343839079..e1aa7591ebe 100644 --- a/core/utils/decimal.go +++ b/core/utils/decimal.go @@ -9,6 +9,9 @@ import ( ) // ToDecimal converts an input to a decimal +// TODO: Currently this function does not handle hex string as the input. +// Draft PR to add hex string handling to utils.ToDecimal (review if this breaks other services): +// https://github.com/smartcontractkit/chainlink/pull/14841 func ToDecimal(input interface{}) (decimal.Decimal, error) { switch v := input.(type) { case string: