diff --git a/.changeset/eight-rocks-notice.md b/.changeset/eight-rocks-notice.md new file mode 100644 index 00000000000..230abaec481 --- /dev/null +++ b/.changeset/eight-rocks-notice.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +New Mercury v4 report schema #added diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 5cd4aaf63c0..4ee443d46f8 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -22,7 +22,7 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/spf13/cobra v1.8.0 @@ -271,7 +271,7 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.10 // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect - github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect + github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index c383b6bf81e..3ae26beb633 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1184,12 +1184,12 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996 h1:6s4cTIE3NbATxWLrD5JLCq097PC5Y4GKK/Kk4fhURpY= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc h1:nNZqLasN8y5huDKX76JUZtni7WkUI36J61//czbJpDM= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa h1:g75H8oh2ws52s8BekwvGQ9XvBVu3E7WM1rfiA0PN0zk= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa/go.mod h1:wZvLHX/Sd9hskN51016cTFcT3G62KXVa6xbVDS7tRjc= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f h1:I9fTBJpHkeldFplXUy71eLIn6A6GxuR4xrABoUeD+CM= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f/go.mod h1:V/86loaFSH0dqqUEHqyXVbyNqDRSjvcf9BRomWFTljU= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= diff --git a/core/services/ocr2/plugins/mercury/config/config.go b/core/services/ocr2/plugins/mercury/config/config.go index 5763b883ac0..40854bd8c0a 100644 --- a/core/services/ocr2/plugins/mercury/config/config.go +++ b/core/services/ocr2/plugins/mercury/config/config.go @@ -108,7 +108,7 @@ func ValidatePluginConfig(config PluginConfig, feedID mercuryutils.FeedID) (merr if config.NativeFeedID != nil { merr = errors.Join(merr, errors.New("nativeFeedID may not be specified for v1 jobs")) } - case 2, 3: + case 2, 3, 4: if config.LinkFeedID == nil { merr = errors.Join(merr, fmt.Errorf("linkFeedID must be specified for v%d jobs", feedID.Version())) } @@ -119,7 +119,7 @@ func ValidatePluginConfig(config PluginConfig, feedID mercuryutils.FeedID) (merr merr = errors.Join(merr, fmt.Errorf("initialBlockNumber may not be specified for v%d jobs", feedID.Version())) } default: - merr = errors.Join(merr, fmt.Errorf("got unsupported schema version %d; supported versions are 1,2,3", feedID.Version())) + merr = errors.Join(merr, fmt.Errorf("got unsupported schema version %d; supported versions are 1,2,3,4", feedID.Version())) } return merr diff --git a/core/services/ocr2/plugins/mercury/helpers_test.go b/core/services/ocr2/plugins/mercury/helpers_test.go index 43d709453b7..9691e8d4fab 100644 --- a/core/services/ocr2/plugins/mercury/helpers_test.go +++ b/core/services/ocr2/plugins/mercury/helpers_test.go @@ -121,6 +121,7 @@ type Feed struct { baseBenchmarkPrice *big.Int baseBid *big.Int baseAsk *big.Int + baseMarketStatus uint32 } func randomFeedID(version uint16) [32]byte { @@ -467,3 +468,97 @@ chainID = 1337 nativeFeedID, )) } + +func addV4MercuryJob( + t *testing.T, + node Node, + i int, + verifierAddress common.Address, + bootstrapPeerID string, + bootstrapNodePort int, + bmBridge, + bidBridge, + askBridge, + marketStatusBridge string, + servers map[string]string, + clientPubKey ed25519.PublicKey, + feedName string, + feedID [32]byte, + linkFeedID [32]byte, + nativeFeedID [32]byte, +) { + srvs := make([]string, 0, len(servers)) + for u, k := range servers { + srvs = append(srvs, fmt.Sprintf("%q = %q", u, k)) + } + serversStr := fmt.Sprintf("{ %s }", strings.Join(srvs, ", ")) + + node.AddJob(t, fmt.Sprintf(` +type = "offchainreporting2" +schemaVersion = 1 +name = "mercury-%[1]d-%[11]s" +forwardingAllowed = false +maxTaskDuration = "1s" +contractID = "%[2]s" +feedID = "0x%[10]x" +contractConfigTrackerPollInterval = "1s" +ocrKeyBundleID = "%[3]s" +p2pv2Bootstrappers = [ + "%[4]s" +] +relay = "evm" +pluginType = "mercury" +transmitterID = "%[9]x" +observationSource = """ + // Benchmark Price + price1 [type=bridge name="%[5]s" timeout="50ms" requestData="{\\"data\\":{\\"from\\":\\"ETH\\",\\"to\\":\\"USD\\"}}"]; + price1_parse [type=jsonparse path="result"]; + price1_multiply [type=multiply times=100000000 index=0]; + + price1 -> price1_parse -> price1_multiply; + + // Bid + bid [type=bridge name="%[6]s" timeout="50ms" requestData="{\\"data\\":{\\"from\\":\\"ETH\\",\\"to\\":\\"USD\\"}}"]; + bid_parse [type=jsonparse path="result"]; + bid_multiply [type=multiply times=100000000 index=1]; + + bid -> bid_parse -> bid_multiply; + + // Ask + ask [type=bridge name="%[7]s" timeout="50ms" requestData="{\\"data\\":{\\"from\\":\\"ETH\\",\\"to\\":\\"USD\\"}}"]; + ask_parse [type=jsonparse path="result"]; + ask_multiply [type=multiply times=100000000 index=2]; + + ask -> ask_parse -> ask_multiply; + + // Market Status + marketstatus [type=bridge name="%[14]s" timeout="50ms" requestData="{\\"data\\":{\\"from\\":\\"ETH\\",\\"to\\":\\"USD\\"}}"]; + marketstatus_parse [type=jsonparse path="result" index=3]; + + marketstatus -> marketstatus_parse; +""" + +[pluginConfig] +servers = %[8]s +linkFeedID = "0x%[12]x" +nativeFeedID = "0x%[13]x" + +[relayConfig] +chainID = 1337 + `, + i, + verifierAddress, + node.KeyBundle.ID(), + fmt.Sprintf("%s@127.0.0.1:%d", bootstrapPeerID, bootstrapNodePort), + bmBridge, + bidBridge, + askBridge, + serversStr, + clientPubKey, + feedID, + feedName, + linkFeedID, + nativeFeedID, + marketStatusBridge, + )) +} diff --git a/core/services/ocr2/plugins/mercury/integration_test.go b/core/services/ocr2/plugins/mercury/integration_test.go index 832a39237ee..9e34e9da8b4 100644 --- a/core/services/ocr2/plugins/mercury/integration_test.go +++ b/core/services/ocr2/plugins/mercury/integration_test.go @@ -24,22 +24,21 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/hashicorp/consul/sdk/freeport" "github.com/shopspring/decimal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - "go.uber.org/zap/zaptest/observer" - "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/wsrpc/credentials" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" mercurytypes "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" v1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" datastreamsmercury "github.com/smartcontractkit/chainlink-data-streams/mercury" - "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" token "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/link_token_interface" @@ -56,6 +55,7 @@ import ( reportcodecv1 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1/reportcodec" reportcodecv2 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v2/reportcodec" reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" + reportcodecv4 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/reportcodec" "github.com/smartcontractkit/chainlink/v2/core/store/models" ) @@ -146,9 +146,9 @@ func integration_MercuryV1(t *testing.T) { pError := atomic.Int64{} // feeds - btcFeed := Feed{"BTC/USD", randomFeedID(1), big.NewInt(20_000 * multiplier), big.NewInt(19_997 * multiplier), big.NewInt(20_004 * multiplier)} - ethFeed := Feed{"ETH/USD", randomFeedID(1), big.NewInt(1_568 * multiplier), big.NewInt(1_566 * multiplier), big.NewInt(1_569 * multiplier)} - linkFeed := Feed{"LINK/USD", randomFeedID(1), big.NewInt(7150 * multiplier / 1000), big.NewInt(7123 * multiplier / 1000), big.NewInt(7177 * multiplier / 1000)} + btcFeed := Feed{"BTC/USD", randomFeedID(1), big.NewInt(20_000 * multiplier), big.NewInt(19_997 * multiplier), big.NewInt(20_004 * multiplier), 0} + ethFeed := Feed{"ETH/USD", randomFeedID(1), big.NewInt(1_568 * multiplier), big.NewInt(1_566 * multiplier), big.NewInt(1_569 * multiplier), 0} + linkFeed := Feed{"LINK/USD", randomFeedID(1), big.NewInt(7150 * multiplier / 1000), big.NewInt(7123 * multiplier / 1000), big.NewInt(7177 * multiplier / 1000), 0} feeds := []Feed{btcFeed, ethFeed, linkFeed} feedM := make(map[[32]byte]Feed, len(feeds)) for i := range feeds { @@ -1036,3 +1036,308 @@ func integration_MercuryV3(t *testing.T) { } }) } + +func TestIntegration_MercuryV4(t *testing.T) { + t.Parallel() + + integration_MercuryV4(t) +} + +func integration_MercuryV4(t *testing.T) { + ctx := testutils.Context(t) + var logObservers []*observer.ObservedLogs + t.Cleanup(func() { + detectPanicLogs(t, logObservers) + }) + + testStartTimeStamp := uint32(time.Now().Unix()) + + // test vars + // pError is the probability that an EA will return an error instead of a result, as integer percentage + // pError = 0 means it will never return error + pError := atomic.Int64{} + + // feeds + btcFeed := Feed{ + name: "BTC/USD", + id: randomFeedID(4), + baseBenchmarkPrice: big.NewInt(20_000 * multiplier), + baseBid: big.NewInt(19_997 * multiplier), + baseAsk: big.NewInt(20_004 * multiplier), + baseMarketStatus: 1, + } + ethFeed := Feed{ + name: "ETH/USD", + id: randomFeedID(4), + baseBenchmarkPrice: big.NewInt(1_568 * multiplier), + baseBid: big.NewInt(1_566 * multiplier), + baseAsk: big.NewInt(1_569 * multiplier), + baseMarketStatus: 2, + } + linkFeed := Feed{ + name: "LINK/USD", + id: randomFeedID(4), + baseBenchmarkPrice: big.NewInt(7150 * multiplier / 1000), + baseBid: big.NewInt(7123 * multiplier / 1000), + baseAsk: big.NewInt(7177 * multiplier / 1000), + baseMarketStatus: 3, + } + feeds := []Feed{btcFeed, ethFeed, linkFeed} + feedM := make(map[[32]byte]Feed, len(feeds)) + for i := range feeds { + feedM[feeds[i].id] = feeds[i] + } + + clientCSAKeys := make([]csakey.KeyV2, n+1) + clientPubKeys := make([]ed25519.PublicKey, n+1) + for i := 0; i < n+1; i++ { + k := big.NewInt(int64(i)) + key := csakey.MustNewV2XXXTestingOnly(k) + clientCSAKeys[i] = key + clientPubKeys[i] = key.PublicKey + } + + // Test multi-send to three servers + const nSrvs = 3 + reqChs := make([]chan request, nSrvs) + servers := make(map[string]string) + for i := 0; i < nSrvs; i++ { + k := csakey.MustNewV2XXXTestingOnly(big.NewInt(int64(-(i + 1)))) + reqs := make(chan request, 100) + srv := NewMercuryServer(t, ed25519.PrivateKey(k.Raw()), reqs, func() []byte { + report, err := (&reportcodecv4.ReportCodec{}).BuildReport(v4.ReportFields{BenchmarkPrice: big.NewInt(234567), Bid: big.NewInt(1), Ask: big.NewInt(1), LinkFee: big.NewInt(1), NativeFee: big.NewInt(1), MarketStatus: 1}) + if err != nil { + panic(err) + } + return report + }) + serverURL := startMercuryServer(t, srv, clientPubKeys) + reqChs[i] = reqs + servers[serverURL] = fmt.Sprintf("%x", k.PublicKey) + } + chainID := testutils.SimulatedChainID + + steve, backend, verifier, verifierAddress := setupBlockchain(t) + + // Setup bootstrap + oracle nodes + bootstrapNodePort := freeport.GetOne(t) + appBootstrap, bootstrapPeerID, _, bootstrapKb, observedLogs := setupNode(t, bootstrapNodePort, "bootstrap_mercury", backend, clientCSAKeys[n]) + bootstrapNode := Node{App: appBootstrap, KeyBundle: bootstrapKb} + logObservers = append(logObservers, observedLogs) + + // Commit blocks to finality depth to ensure LogPoller has finalized blocks to read from + ch, err := bootstrapNode.App.GetRelayers().LegacyEVMChains().Get(testutils.SimulatedChainID.String()) + require.NoError(t, err) + finalityDepth := ch.Config().EVM().FinalityDepth() + for i := 0; i < int(finalityDepth); i++ { + backend.Commit() + } + + // Set up n oracles + var ( + oracles []confighelper.OracleIdentityExtra + nodes []Node + ) + ports := freeport.GetN(t, n) + for i := 0; i < n; i++ { + app, peerID, transmitter, kb, observedLogs := setupNode(t, ports[i], fmt.Sprintf("oracle_mercury%d", i), backend, clientCSAKeys[i]) + + nodes = append(nodes, Node{ + app, transmitter, kb, + }) + + offchainPublicKey, _ := hex.DecodeString(strings.TrimPrefix(kb.OnChainPublicKey(), "0x")) + oracles = append(oracles, confighelper.OracleIdentityExtra{ + OracleIdentity: confighelper.OracleIdentity{ + OnchainPublicKey: offchainPublicKey, + TransmitAccount: ocr2types.Account(fmt.Sprintf("%x", transmitter[:])), + OffchainPublicKey: kb.OffchainPublicKey(), + PeerID: peerID, + }, + ConfigEncryptionPublicKey: kb.ConfigEncryptionPublicKey(), + }) + logObservers = append(logObservers, observedLogs) + } + + for _, feed := range feeds { + addBootstrapJob(t, bootstrapNode, chainID, verifierAddress, feed.name, feed.id) + } + + createBridge := func(name string, i int, p *big.Int, marketStatus uint32, borm bridges.ORM) (bridgeName string) { + bridge := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + b, herr := io.ReadAll(req.Body) + require.NoError(t, herr) + require.Equal(t, `{"data":{"from":"ETH","to":"USD"}}`, string(b)) + + r := rand.Int63n(101) + if r > pError.Load() { + res.WriteHeader(http.StatusOK) + + var val string + if p != nil { + val = decimal.NewFromBigInt(p, 0).Div(decimal.NewFromInt(multiplier)).Add(decimal.NewFromInt(int64(i)).Div(decimal.NewFromInt(100))).String() + } else { + val = fmt.Sprintf("%d", marketStatus) + } + + resp := fmt.Sprintf(`{"result": %s}`, val) + _, herr = res.Write([]byte(resp)) + require.NoError(t, herr) + } else { + res.WriteHeader(http.StatusInternalServerError) + resp := `{"error": "pError test error"}` + _, herr = res.Write([]byte(resp)) + require.NoError(t, herr) + } + })) + t.Cleanup(bridge.Close) + u, _ := url.Parse(bridge.URL) + bridgeName = fmt.Sprintf("bridge-%s-%d", name, i) + require.NoError(t, borm.CreateBridgeType(ctx, &bridges.BridgeType{ + Name: bridges.BridgeName(bridgeName), + URL: models.WebURL(*u), + })) + + return bridgeName + } + + // Add OCR jobs - one per feed on each node + for i, node := range nodes { + for j, feed := range feeds { + bmBridge := createBridge(fmt.Sprintf("benchmarkprice-%d", j), i, feed.baseBenchmarkPrice, 0, node.App.BridgeORM()) + bidBridge := createBridge(fmt.Sprintf("bid-%d", j), i, feed.baseBid, 0, node.App.BridgeORM()) + askBridge := createBridge(fmt.Sprintf("ask-%d", j), i, feed.baseAsk, 0, node.App.BridgeORM()) + marketStatusBridge := createBridge(fmt.Sprintf("marketstatus-%d", j), i, nil, feed.baseMarketStatus, node.App.BridgeORM()) + + addV4MercuryJob( + t, + node, + i, + verifierAddress, + bootstrapPeerID, + bootstrapNodePort, + bmBridge, + bidBridge, + askBridge, + marketStatusBridge, + servers, + clientPubKeys[i], + feed.name, + feed.id, + randomFeedID(2), + randomFeedID(2), + ) + } + } + + // Setup config on contract + onchainConfig, err := (datastreamsmercury.StandardOnchainConfigCodec{}).Encode(rawOnchainConfig) + require.NoError(t, err) + + reportingPluginConfig, err := json.Marshal(rawReportingPluginConfig) + require.NoError(t, err) + + signers, _, _, onchainConfig, offchainConfigVersion, offchainConfig, err := ocr3confighelper.ContractSetConfigArgsForTestsMercuryV02( + 2*time.Second, // DeltaProgress + 20*time.Second, // DeltaResend + 400*time.Millisecond, // DeltaInitial + 100*time.Millisecond, // DeltaRound + 0, // DeltaGrace + 300*time.Millisecond, // DeltaCertifiedCommitRequest + 1*time.Minute, // DeltaStage + 100, // rMax + []int{len(nodes)}, // S + oracles, + reportingPluginConfig, // reportingPluginConfig []byte, + 250*time.Millisecond, // Max duration observation + int(f), // f + onchainConfig, + ) + + require.NoError(t, err) + signerAddresses, err := evm.OnchainPublicKeyToAddress(signers) + require.NoError(t, err) + + offchainTransmitters := make([][32]byte, n) + for i := 0; i < n; i++ { + offchainTransmitters[i] = nodes[i].ClientPubKey + } + + for _, feed := range feeds { + _, ferr := verifier.SetConfig( + steve, + feed.id, + signerAddresses, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + nil, + ) + require.NoError(t, ferr) + backend.Commit() + } + + runTestSetup := func(reqs chan request) { + // Expect at least one report per feed from each oracle, per server + seen := make(map[[32]byte]map[credentials.StaticSizedPublicKey]struct{}) + for i := range feeds { + // feedID will be deleted when all n oracles have reported + seen[feeds[i].id] = make(map[credentials.StaticSizedPublicKey]struct{}, n) + } + + for req := range reqs { + v := make(map[string]interface{}) + err := mercury.PayloadTypes.UnpackIntoMap(v, req.req.Payload) + require.NoError(t, err) + report, exists := v["report"] + if !exists { + t.Fatalf("expected payload %#v to contain 'report'", v) + } + reportElems := make(map[string]interface{}) + err = reportcodecv4.ReportTypes.UnpackIntoMap(reportElems, report.([]byte)) + require.NoError(t, err) + + feedID := reportElems["feedId"].([32]uint8) + feed, exists := feedM[feedID] + require.True(t, exists) + + if _, exists := seen[feedID]; !exists { + continue // already saw all oracles for this feed + } + + expectedFee := datastreamsmercury.CalculateFee(big.NewInt(234567), rawReportingPluginConfig.BaseUSDFee) + expectedExpiresAt := reportElems["observationsTimestamp"].(uint32) + rawReportingPluginConfig.ExpirationWindow + + assert.GreaterOrEqual(t, int(reportElems["observationsTimestamp"].(uint32)), int(testStartTimeStamp)) + assert.InDelta(t, feed.baseBenchmarkPrice.Int64(), reportElems["benchmarkPrice"].(*big.Int).Int64(), 5000000) + assert.InDelta(t, feed.baseBid.Int64(), reportElems["bid"].(*big.Int).Int64(), 5000000) + assert.InDelta(t, feed.baseAsk.Int64(), reportElems["ask"].(*big.Int).Int64(), 5000000) + assert.NotZero(t, reportElems["validFromTimestamp"].(uint32)) + assert.GreaterOrEqual(t, reportElems["observationsTimestamp"].(uint32), reportElems["validFromTimestamp"].(uint32)) + assert.Equal(t, expectedExpiresAt, reportElems["expiresAt"].(uint32)) + assert.Equal(t, expectedFee, reportElems["linkFee"].(*big.Int)) + assert.Equal(t, expectedFee, reportElems["nativeFee"].(*big.Int)) + assert.Equal(t, feed.baseMarketStatus, reportElems["marketStatus"].(uint32)) + + t.Logf("oracle %x reported for feed %s (0x%x)", req.pk, feed.name, feed.id) + + seen[feedID][req.pk] = struct{}{} + if len(seen[feedID]) == n { + t.Logf("all oracles reported for feed %s (0x%x)", feed.name, feed.id) + delete(seen, feedID) + if len(seen) == 0 { + break // saw all oracles; success! + } + } + } + } + + t.Run("receives at least one report per feed for every server from each oracle when EAs are at 100% reliability", func(t *testing.T) { + for i := 0; i < nSrvs; i++ { + reqs := reqChs[i] + runTestSetup(reqs) + } + }) +} diff --git a/core/services/ocr2/plugins/mercury/plugin.go b/core/services/ocr2/plugins/mercury/plugin.go index c5eba78b0d8..0898c1821ec 100644 --- a/core/services/ocr2/plugins/mercury/plugin.go +++ b/core/services/ocr2/plugins/mercury/plugin.go @@ -13,6 +13,7 @@ import ( relaymercuryv1 "github.com/smartcontractkit/chainlink-data-streams/mercury/v1" relaymercuryv2 "github.com/smartcontractkit/chainlink-data-streams/mercury/v2" relaymercuryv3 "github.com/smartcontractkit/chainlink-data-streams/mercury/v3" + relaymercuryv4 "github.com/smartcontractkit/chainlink-data-streams/mercury/v4" "github.com/smartcontractkit/chainlink-common/pkg/loop" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -29,6 +30,7 @@ import ( mercuryv1 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v1" mercuryv2 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v2" mercuryv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3" + mercuryv4 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -136,6 +138,13 @@ func NewServices( return nil, fmt.Errorf("failed to create mercury v3 factory: %w", err) } srvs = append(srvs, factoryServices...) + case 4: + factory, factoryServices, err = newv4factory(fCfg) + if err != nil { + abort() + return nil, fmt.Errorf("failed to create mercury v4 factory: %w", err) + } + srvs = append(srvs, factoryServices...) default: return nil, errors.Errorf("unknown Mercury report schema version: %d", feedID.Version()) } @@ -162,6 +171,43 @@ type factoryCfg struct { feedID utils.FeedID } +func newv4factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job.ServiceCtx, error) { + var factory ocr3types.MercuryPluginFactory + srvs := make([]job.ServiceCtx, 0) + + ds := mercuryv4.NewDataSource( + factoryCfg.orm, + factoryCfg.pipelineRunner, + factoryCfg.jb, + *factoryCfg.jb.PipelineSpec, + factoryCfg.feedID, + factoryCfg.lggr, + factoryCfg.saver, + factoryCfg.chEnhancedTelem, + factoryCfg.ocr2Provider.MercuryServerFetcher(), + *factoryCfg.reportingPluginConfig.LinkFeedID, + *factoryCfg.reportingPluginConfig.NativeFeedID, + ) + + loopCmd := env.MercuryPlugin.Cmd.Get() + loopEnabled := loopCmd != "" + + if loopEnabled { + cmdFn, opts, mercuryLggr, err := initLoop(loopCmd, factoryCfg.cfg, factoryCfg.feedID, factoryCfg.lggr) + if err != nil { + return nil, nil, fmt.Errorf("failed to init loop for feed %s: %w", factoryCfg.feedID, err) + } + // in loop mode, the factory is grpc server, and we need to handle the server lifecycle + factoryServer := loop.NewMercuryV4Service(mercuryLggr, opts, cmdFn, factoryCfg.ocr2Provider, ds) + srvs = append(srvs, factoryServer) + // adapt the grpc server to the vanilla mercury plugin factory interface used by the oracle + factory = factoryServer + } else { + factory = relaymercuryv4.NewFactory(ds, factoryCfg.lggr, factoryCfg.ocr2Provider.OnchainConfigCodec(), factoryCfg.ocr2Provider.ReportCodecV4()) + } + return factory, srvs, nil +} + func newv3factory(factoryCfg factoryCfg) (ocr3types.MercuryPluginFactory, []job.ServiceCtx, error) { var factory ocr3types.MercuryPluginFactory srvs := make([]job.ServiceCtx, 0) diff --git a/core/services/ocr2/plugins/mercury/plugin_test.go b/core/services/ocr2/plugins/mercury/plugin_test.go index 95aaabec142..f9bef4a3f1a 100644 --- a/core/services/ocr2/plugins/mercury/plugin_test.go +++ b/core/services/ocr2/plugins/mercury/plugin_test.go @@ -21,6 +21,7 @@ import ( v1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" mercuryocr2 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury" @@ -37,6 +38,7 @@ var ( v1FeedId = [32]uint8{00, 01, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} v2FeedId = [32]uint8{00, 02, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} v3FeedId = [32]uint8{00, 03, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} + v4FeedId = [32]uint8{00, 04, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} testArgsNoPlugin = libocr2.MercuryOracleArgs{ LocalConfig: libocr2types.LocalConfig{ @@ -66,6 +68,13 @@ var ( "nativeFeedID": "0x00036b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472", } + v4jsonCfg = job.JSONConfig{ + "serverURL": "example.com:80", + "serverPubKey": "724ff6eae9e900270edfff233e16322a70ec06e1a6e62a81ef13921f398f6c93", + "linkFeedID": "0x00026b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472", + "nativeFeedID": "0x00036b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472", + } + testJob = job.Job{ ID: 1, ExternalJobID: uuid.Must(uuid.NewRandom()), @@ -135,6 +144,15 @@ func TestNewServices(t *testing.T) { wantServiceCnt: expectedEmbeddedServiceCnt, wantErr: false, }, + { + name: "v4 legacy", + args: args{ + pluginConfig: v4jsonCfg, + feedID: v4FeedId, + }, + wantServiceCnt: expectedEmbeddedServiceCnt, + wantErr: false, + }, { name: "v1 loop", loopMode: true, @@ -168,6 +186,17 @@ func TestNewServices(t *testing.T) { wantErr: false, wantLoopFactory: &loop.MercuryV3Service{}, }, + { + name: "v4 loop", + loopMode: true, + args: args{ + pluginConfig: v4jsonCfg, + feedID: v4FeedId, + }, + wantServiceCnt: expectedLoopServiceCnt, + wantErr: false, + wantLoopFactory: &loop.MercuryV4Service{}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -259,6 +288,9 @@ func (*testProvider) ReportCodecV2() v2.ReportCodec { return nil } // ReportCodecV3 implements types.MercuryProvider. func (*testProvider) ReportCodecV3() v3.ReportCodec { return nil } +// ReportCodecV4 implements types.MercuryProvider. +func (*testProvider) ReportCodecV4() v4.ReportCodec { return nil } + // Start implements types.MercuryProvider. func (*testProvider) Start(context.Context) error { panic("unimplemented") } diff --git a/core/services/ocrcommon/telemetry.go b/core/services/ocrcommon/telemetry.go index 2ef76800a42..2cb4fda9105 100644 --- a/core/services/ocrcommon/telemetry.go +++ b/core/services/ocrcommon/telemetry.go @@ -15,6 +15,8 @@ import ( v1types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + v4types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" @@ -41,6 +43,7 @@ type EnhancedTelemetryMercuryData struct { V1Observation *v1types.Observation V2Observation *v2types.Observation V3Observation *v3types.Observation + V4Observation *v4types.Observation TaskRunResults pipeline.TaskRunResults RepTimestamp ocrtypes.ReportTimestamp FeedVersion mercuryutils.FeedVersion @@ -298,6 +301,8 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d Enhanced ask := big.NewInt(0) // v2+v3 fields var mfts, lp, np int64 + // v4 fields + var marketStatus telem.MarketStatus switch { case d.V1Observation != nil: @@ -354,6 +359,29 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d Enhanced if obs.Ask.Err == nil && obs.Ask.Val != nil { ask = obs.Ask.Val } + case d.V4Observation != nil: + obs := *d.V4Observation + if obs.MaxFinalizedTimestamp.Err == nil { + mfts = obs.MaxFinalizedTimestamp.Val + } + if obs.LinkPrice.Err == nil && obs.LinkPrice.Val != nil { + lp = obs.LinkPrice.Val.Int64() + } + if obs.NativePrice.Err == nil && obs.NativePrice.Val != nil { + np = obs.NativePrice.Val.Int64() + } + if obs.BenchmarkPrice.Err == nil && obs.BenchmarkPrice.Val != nil { + bp = obs.BenchmarkPrice.Val + } + if obs.Bid.Err == nil && obs.Bid.Val != nil { + bid = obs.Bid.Val + } + if obs.Ask.Err == nil && obs.Ask.Val != nil { + ask = obs.Ask.Val + } + if obs.MarketStatus.Err == nil { + marketStatus = telem.MarketStatus(obs.MarketStatus.Val) + } } for _, trr := range d.TaskRunResults { @@ -401,6 +429,7 @@ func (e *EnhancedTelemetryService[T]) collectMercuryEnhancedTelemetry(d Enhanced ObservationBenchmarkPriceString: stringOrEmpty(bp), ObservationBidString: stringOrEmpty(bid), ObservationAskString: stringOrEmpty(ask), + ObservationMarketStatus: marketStatus, IsLinkFeed: d.IsLinkFeed, LinkPrice: lp, IsNativeFeed: d.IsNativeFeed, diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 3b3393441a2..a0782380b5b 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -25,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" coretypes "github.com/smartcontractkit/chainlink-common/pkg/types/core" + reportcodecv4 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/reportcodec" txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" txm "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" @@ -296,6 +297,7 @@ func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commonty reportCodecV1 := reportcodecv1.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV1")) reportCodecV2 := reportcodecv2.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV2")) reportCodecV3 := reportcodecv3.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV3")) + reportCodecV4 := reportcodecv4.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV4")) var transmitterCodec mercury.TransmitterReportDecoder switch feedID.Version() { @@ -305,12 +307,14 @@ func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commonty transmitterCodec = reportCodecV2 case 3: transmitterCodec = reportCodecV3 + case 4: + transmitterCodec = reportCodecV4 default: return nil, fmt.Errorf("invalid feed version %d", feedID.Version()) } transmitter := mercury.NewTransmitter(lggr, r.transmitterCfg, clients, privKey.PublicKey, rargs.JobID, *relayConfig.FeedID, r.mercuryORM, transmitterCodec, r.triggerCapability) - return NewMercuryProvider(cp, r.chainReader, r.codec, NewMercuryChainReader(r.chain.HeadTracker()), transmitter, reportCodecV1, reportCodecV2, reportCodecV3, lggr), nil + return NewMercuryProvider(cp, r.chainReader, r.codec, NewMercuryChainReader(r.chain.HeadTracker()), transmitter, reportCodecV1, reportCodecV2, reportCodecV3, reportCodecV4, lggr), nil } func (r *Relayer) NewLLOProvider(rargs commontypes.RelayArgs, pargs commontypes.PluginArgs) (commontypes.LLOProvider, error) { diff --git a/core/services/relay/evm/mercury/utils/feeds.go b/core/services/relay/evm/mercury/utils/feeds.go index 6f8978bbf0d..36d6bc60f58 100644 --- a/core/services/relay/evm/mercury/utils/feeds.go +++ b/core/services/relay/evm/mercury/utils/feeds.go @@ -83,6 +83,7 @@ const ( REPORT_V1 REPORT_V2 REPORT_V3 + REPORT_V4 _ ) @@ -110,3 +111,4 @@ func (f FeedID) Version() FeedVersion { func (f FeedID) IsV1() bool { return f.Version() == REPORT_V1 } func (f FeedID) IsV2() bool { return f.Version() == REPORT_V2 } func (f FeedID) IsV3() bool { return f.Version() == REPORT_V3 } +func (f FeedID) IsV4() bool { return f.Version() == REPORT_V4 } diff --git a/core/services/relay/evm/mercury/v4/data_source.go b/core/services/relay/evm/mercury/v4/data_source.go new file mode 100644 index 00000000000..f9c2c2d5de0 --- /dev/null +++ b/core/services/relay/evm/mercury/v4/data_source.go @@ -0,0 +1,290 @@ +package v4 + +import ( + "context" + "errors" + "fmt" + "math/big" + "sync" + + pkgerrors "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" + v4types "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" + v4 "github.com/smartcontractkit/chainlink-data-streams/mercury/v4" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/types" + mercurytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/types" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/reportcodec" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +type Runner interface { + ExecuteRun(ctx context.Context, spec pipeline.Spec, vars pipeline.Vars) (run *pipeline.Run, trrs pipeline.TaskRunResults, err error) +} + +type LatestReportFetcher interface { + LatestPrice(ctx context.Context, feedID [32]byte) (*big.Int, error) + LatestTimestamp(context.Context) (int64, error) +} + +type datasource struct { + pipelineRunner Runner + jb job.Job + spec pipeline.Spec + feedID mercuryutils.FeedID + lggr logger.Logger + saver ocrcommon.Saver + orm types.DataSourceORM + codec reportcodec.ReportCodec + + fetcher LatestReportFetcher + linkFeedID mercuryutils.FeedID + nativeFeedID mercuryutils.FeedID + + mu sync.RWMutex + + chEnhancedTelem chan<- ocrcommon.EnhancedTelemetryMercuryData +} + +var _ v4.DataSource = &datasource{} + +func NewDataSource(orm types.DataSourceORM, pr pipeline.Runner, jb job.Job, spec pipeline.Spec, feedID mercuryutils.FeedID, lggr logger.Logger, s ocrcommon.Saver, enhancedTelemChan chan ocrcommon.EnhancedTelemetryMercuryData, fetcher LatestReportFetcher, linkFeedID, nativeFeedID mercuryutils.FeedID) *datasource { + return &datasource{pr, jb, spec, feedID, lggr, s, orm, reportcodec.ReportCodec{}, fetcher, linkFeedID, nativeFeedID, sync.RWMutex{}, enhancedTelemChan} +} + +func (ds *datasource) Observe(ctx context.Context, repts ocrtypes.ReportTimestamp, fetchMaxFinalizedTimestamp bool) (obs v4types.Observation, pipelineExecutionErr error) { + var wg sync.WaitGroup + ctx, cancel := context.WithCancel(ctx) + + if fetchMaxFinalizedTimestamp { + wg.Add(1) + go func() { + defer wg.Done() + latest, dbErr := ds.orm.LatestReport(ctx, ds.feedID) + if dbErr != nil { + obs.MaxFinalizedTimestamp.Err = dbErr + return + } + if latest != nil { + maxFinalizedBlockNumber, decodeErr := ds.codec.ObservationTimestampFromReport(latest) + obs.MaxFinalizedTimestamp.Val, obs.MaxFinalizedTimestamp.Err = int64(maxFinalizedBlockNumber), decodeErr + return + } + obs.MaxFinalizedTimestamp.Val, obs.MaxFinalizedTimestamp.Err = ds.fetcher.LatestTimestamp(ctx) + }() + } + + var trrs pipeline.TaskRunResults + wg.Add(1) + go func() { + defer wg.Done() + var run *pipeline.Run + run, trrs, pipelineExecutionErr = ds.executeRun(ctx) + if pipelineExecutionErr != nil { + cancel() + pipelineExecutionErr = fmt.Errorf("Observe failed while executing run: %w", pipelineExecutionErr) + return + } + + ds.saver.Save(run) + + var parsed parseOutput + parsed, pipelineExecutionErr = ds.parse(trrs) + if pipelineExecutionErr != nil { + cancel() + // This is not expected under normal circumstances + ds.lggr.Errorw("Observe failed while parsing run results", "err", pipelineExecutionErr) + pipelineExecutionErr = fmt.Errorf("Observe failed while parsing run results: %w", pipelineExecutionErr) + return + } + obs.BenchmarkPrice = parsed.benchmarkPrice + obs.Bid = parsed.bid + obs.Ask = parsed.ask + obs.MarketStatus = parsed.marketStatus + }() + + var isLink, isNative bool + if ds.feedID == ds.linkFeedID { + isLink = true + } else { + wg.Add(1) + go func() { + defer wg.Done() + obs.LinkPrice.Val, obs.LinkPrice.Err = ds.fetcher.LatestPrice(ctx, ds.linkFeedID) + if obs.LinkPrice.Val == nil && obs.LinkPrice.Err == nil { + mercurytypes.PriceFeedMissingCount.WithLabelValues(ds.linkFeedID.String()).Inc() + ds.lggr.Warnw(fmt.Sprintf("Mercury server was missing LINK feed, using sentinel value of %s", v4.MissingPrice), "linkFeedID", ds.linkFeedID) + obs.LinkPrice.Val = v4.MissingPrice + } else if obs.LinkPrice.Err != nil { + mercurytypes.PriceFeedErrorCount.WithLabelValues(ds.linkFeedID.String()).Inc() + ds.lggr.Errorw("Mercury server returned error querying LINK price feed", "err", obs.LinkPrice.Err, "linkFeedID", ds.linkFeedID) + } + }() + } + + if ds.feedID == ds.nativeFeedID { + isNative = true + } else { + wg.Add(1) + go func() { + defer wg.Done() + obs.NativePrice.Val, obs.NativePrice.Err = ds.fetcher.LatestPrice(ctx, ds.nativeFeedID) + if obs.NativePrice.Val == nil && obs.NativePrice.Err == nil { + mercurytypes.PriceFeedMissingCount.WithLabelValues(ds.nativeFeedID.String()).Inc() + ds.lggr.Warnw(fmt.Sprintf("Mercury server was missing native feed, using sentinel value of %s", v4.MissingPrice), "nativeFeedID", ds.nativeFeedID) + obs.NativePrice.Val = v4.MissingPrice + } else if obs.NativePrice.Err != nil { + mercurytypes.PriceFeedErrorCount.WithLabelValues(ds.nativeFeedID.String()).Inc() + ds.lggr.Errorw("Mercury server returned error querying native price feed", "err", obs.NativePrice.Err, "nativeFeedID", ds.nativeFeedID) + } + }() + } + + wg.Wait() + cancel() + + if pipelineExecutionErr != nil { + return + } + + if isLink || isNative { + // run has now completed so it is safe to use benchmark price + if isLink { + // This IS the LINK feed, use our observed price + obs.LinkPrice.Val, obs.LinkPrice.Err = obs.BenchmarkPrice.Val, obs.BenchmarkPrice.Err + } + if isNative { + // This IS the native feed, use our observed price + obs.NativePrice.Val, obs.NativePrice.Err = obs.BenchmarkPrice.Val, obs.BenchmarkPrice.Err + } + } + + ocrcommon.MaybeEnqueueEnhancedTelem(ds.jb, ds.chEnhancedTelem, ocrcommon.EnhancedTelemetryMercuryData{ + V4Observation: &obs, + TaskRunResults: trrs, + RepTimestamp: repts, + FeedVersion: mercuryutils.REPORT_V4, + FetchMaxFinalizedTimestamp: fetchMaxFinalizedTimestamp, + IsLinkFeed: isLink, + IsNativeFeed: isNative, + }) + + return obs, nil +} + +func toBigInt(val interface{}) (*big.Int, error) { + dec, err := utils.ToDecimal(val) + if err != nil { + return nil, err + } + return dec.BigInt(), nil +} + +type parseOutput struct { + benchmarkPrice mercury.ObsResult[*big.Int] + bid mercury.ObsResult[*big.Int] + ask mercury.ObsResult[*big.Int] + marketStatus mercury.ObsResult[uint32] +} + +func (ds *datasource) parse(trrs pipeline.TaskRunResults) (o parseOutput, merr error) { + var finaltrrs []pipeline.TaskRunResult + for _, trr := range trrs { + // only return terminal trrs from executeRun + if trr.IsTerminal() { + finaltrrs = append(finaltrrs, trr) + } + } + + // pipeline.TaskRunResults comes ordered asc by index, this is guaranteed + // by the pipeline executor + if len(finaltrrs) != 4 { + return o, fmt.Errorf("invalid number of results, expected: 4, got: %d", len(finaltrrs)) + } + + merr = errors.Join( + setBenchmarkPrice(&o, finaltrrs[0].Result), + setBid(&o, finaltrrs[1].Result), + setAsk(&o, finaltrrs[2].Result), + setMarketStatus(&o, finaltrrs[3].Result), + ) + + return o, merr +} + +func setBenchmarkPrice(o *parseOutput, res pipeline.Result) error { + if res.Error != nil { + o.benchmarkPrice.Err = res.Error + return res.Error + } + val, err := toBigInt(res.Value) + if err != nil { + return fmt.Errorf("failed to parse BenchmarkPrice: %w", err) + } + o.benchmarkPrice.Val = val + return nil +} + +func setBid(o *parseOutput, res pipeline.Result) error { + if res.Error != nil { + o.bid.Err = res.Error + return res.Error + } + val, err := toBigInt(res.Value) + if err != nil { + return fmt.Errorf("failed to parse Bid: %w", err) + } + o.bid.Val = val + return nil +} + +func setAsk(o *parseOutput, res pipeline.Result) error { + if res.Error != nil { + o.ask.Err = res.Error + return res.Error + } + val, err := toBigInt(res.Value) + if err != nil { + return fmt.Errorf("failed to parse Ask: %w", err) + } + o.ask.Val = val + return nil +} + +func setMarketStatus(o *parseOutput, res pipeline.Result) error { + if res.Error != nil { + o.marketStatus.Err = res.Error + return res.Error + } + val, err := toBigInt(res.Value) + if err != nil { + return fmt.Errorf("failed to parse MarketStatus: %w", err) + } + o.marketStatus.Val = uint32(val.Int64()) + return nil +} + +// The context passed in here has a timeout of (ObservationTimeout + ObservationGracePeriod). +// Upon context cancellation, its expected that we return any usable values within ObservationGracePeriod. +func (ds *datasource) executeRun(ctx context.Context) (*pipeline.Run, pipeline.TaskRunResults, error) { + vars := pipeline.NewVarsFrom(map[string]interface{}{ + "jb": map[string]interface{}{ + "databaseID": ds.jb.ID, + "externalJobID": ds.jb.ExternalJobID, + "name": ds.jb.Name.ValueOrZero(), + }, + }) + + run, trrs, err := ds.pipelineRunner.ExecuteRun(ctx, ds.spec, vars) + if err != nil { + return nil, nil, pkgerrors.Wrapf(err, "error executing run for spec ID %v", ds.spec.ID) + } + + return run, trrs, err +} diff --git a/core/services/relay/evm/mercury/v4/data_source_test.go b/core/services/relay/evm/mercury/v4/data_source_test.go new file mode 100644 index 00000000000..bce9c3c6088 --- /dev/null +++ b/core/services/relay/evm/mercury/v4/data_source_test.go @@ -0,0 +1,349 @@ +package v4 + +import ( + "context" + "math/big" + "testing" + + "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + + mercurytypes "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" + relaymercuryv4 "github.com/smartcontractkit/chainlink-data-streams/mercury/v4" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" + mercurymocks "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + reportcodecv4 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/reportcodec" +) + +var _ mercurytypes.ServerFetcher = &mockFetcher{} + +type mockFetcher struct { + ts int64 + tsErr error + linkPrice *big.Int + linkPriceErr error + nativePrice *big.Int + nativePriceErr error +} + +var feedId utils.FeedID = [32]byte{1} +var linkFeedId utils.FeedID = [32]byte{2} +var nativeFeedId utils.FeedID = [32]byte{3} + +func (m *mockFetcher) FetchInitialMaxFinalizedBlockNumber(context.Context) (*int64, error) { + return nil, nil +} + +func (m *mockFetcher) LatestPrice(ctx context.Context, fId [32]byte) (*big.Int, error) { + if fId == linkFeedId { + return m.linkPrice, m.linkPriceErr + } else if fId == nativeFeedId { + return m.nativePrice, m.nativePriceErr + } + return nil, nil +} + +func (m *mockFetcher) LatestTimestamp(context.Context) (int64, error) { + return m.ts, m.tsErr +} + +type mockORM struct { + report []byte + err error +} + +func (m *mockORM) LatestReport(ctx context.Context, feedID [32]byte) (report []byte, err error) { + return m.report, m.err +} + +type mockSaver struct { + r *pipeline.Run +} + +func (ms *mockSaver) Save(r *pipeline.Run) { + ms.r = r +} + +func Test_Datasource(t *testing.T) { + orm := &mockORM{} + ds := &datasource{orm: orm, lggr: logger.TestLogger(t)} + ctx := testutils.Context(t) + repts := ocrtypes.ReportTimestamp{} + + fetcher := &mockFetcher{} + ds.fetcher = fetcher + + saver := &mockSaver{} + ds.saver = saver + + goodTrrs := []pipeline.TaskRunResult{ + { + // bp + Result: pipeline.Result{Value: "122.345"}, + Task: &mercurymocks.MockTask{}, + }, + { + // bid + Result: pipeline.Result{Value: "121.993"}, + Task: &mercurymocks.MockTask{}, + }, + { + // ask + Result: pipeline.Result{Value: "123.111"}, + Task: &mercurymocks.MockTask{}, + }, + { + // marketStatus + Result: pipeline.Result{Value: "1"}, + Task: &mercurymocks.MockTask{}, + }, + } + + ds.pipelineRunner = &mercurymocks.MockRunner{ + Trrs: goodTrrs, + } + + spec := pipeline.Spec{} + ds.spec = spec + + t.Run("when fetchMaxFinalizedTimestamp=true", func(t *testing.T) { + t.Run("with latest report in database", func(t *testing.T) { + orm.report = buildSamplev4Report() + orm.err = nil + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + assert.Equal(t, int64(124), obs.MaxFinalizedTimestamp.Val) + }) + t.Run("if querying latest report fails", func(t *testing.T) { + orm.report = nil + orm.err = errors.New("something exploded") + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.EqualError(t, obs.MaxFinalizedTimestamp.Err, "something exploded") + assert.Zero(t, obs.MaxFinalizedTimestamp.Val) + }) + t.Run("if codec fails to decode", func(t *testing.T) { + orm.report = []byte{1, 2, 3} + orm.err = nil + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.EqualError(t, obs.MaxFinalizedTimestamp.Err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + assert.Zero(t, obs.MaxFinalizedTimestamp.Val) + }) + + orm.report = nil + orm.err = nil + + t.Run("if LatestTimestamp returns error", func(t *testing.T) { + fetcher.tsErr = errors.New("some error") + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.EqualError(t, obs.MaxFinalizedTimestamp.Err, "some error") + assert.Zero(t, obs.MaxFinalizedTimestamp.Val) + }) + + t.Run("if LatestTimestamp succeeds", func(t *testing.T) { + fetcher.tsErr = nil + fetcher.ts = 123 + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.Equal(t, int64(123), obs.MaxFinalizedTimestamp.Val) + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + }) + + t.Run("if LatestTimestamp succeeds but ts=0 (new feed)", func(t *testing.T) { + fetcher.tsErr = nil + fetcher.ts = 0 + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + assert.Zero(t, obs.MaxFinalizedTimestamp.Val) + }) + + t.Run("when run execution succeeded", func(t *testing.T) { + t.Run("when feedId=linkFeedID=nativeFeedId", func(t *testing.T) { + t.Cleanup(func() { + ds.feedID, ds.linkFeedID, ds.nativeFeedID = feedId, linkFeedId, nativeFeedId + }) + + ds.feedID, ds.linkFeedID, ds.nativeFeedID = feedId, feedId, feedId + + fetcher.ts = 123123 + fetcher.tsErr = nil + + obs, err := ds.Observe(ctx, repts, true) + assert.NoError(t, err) + + assert.Equal(t, big.NewInt(122), obs.BenchmarkPrice.Val) + assert.NoError(t, obs.BenchmarkPrice.Err) + assert.Equal(t, big.NewInt(121), obs.Bid.Val) + assert.NoError(t, obs.Bid.Err) + assert.Equal(t, big.NewInt(123), obs.Ask.Val) + assert.NoError(t, obs.Ask.Err) + assert.Equal(t, int64(123123), obs.MaxFinalizedTimestamp.Val) + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + assert.Equal(t, big.NewInt(122), obs.LinkPrice.Val) + assert.NoError(t, obs.LinkPrice.Err) + assert.Equal(t, big.NewInt(122), obs.NativePrice.Val) + assert.NoError(t, obs.NativePrice.Err) + assert.Equal(t, uint32(1), obs.MarketStatus.Val) + assert.NoError(t, obs.MarketStatus.Err) + }) + }) + }) + + t.Run("when fetchMaxFinalizedTimestamp=false", func(t *testing.T) { + t.Run("when run execution fails, returns error", func(t *testing.T) { + t.Cleanup(func() { + ds.pipelineRunner = &mercurymocks.MockRunner{ + Trrs: goodTrrs, + Err: nil, + } + }) + + ds.pipelineRunner = &mercurymocks.MockRunner{ + Trrs: goodTrrs, + Err: errors.New("run execution failed"), + } + + _, err := ds.Observe(ctx, repts, false) + assert.EqualError(t, err, "Observe failed while executing run: error executing run for spec ID 0: run execution failed") + }) + + t.Run("when parsing run results fails, return error", func(t *testing.T) { + t.Cleanup(func() { + runner := &mercurymocks.MockRunner{ + Trrs: goodTrrs, + Err: nil, + } + ds.pipelineRunner = runner + }) + + badTrrs := []pipeline.TaskRunResult{ + { + // benchmark price + Result: pipeline.Result{Value: "122.345"}, + Task: &mercurymocks.MockTask{}, + }, + { + // bid + Result: pipeline.Result{Value: "121.993"}, + Task: &mercurymocks.MockTask{}, + }, + { + // ask + Result: pipeline.Result{Error: errors.New("some error with ask")}, + Task: &mercurymocks.MockTask{}, + }, + { + // marketStatus + Result: pipeline.Result{Value: "1"}, + Task: &mercurymocks.MockTask{}, + }, + } + + ds.pipelineRunner = &mercurymocks.MockRunner{ + Trrs: badTrrs, + Err: nil, + } + + _, err := ds.Observe(ctx, repts, false) + assert.EqualError(t, err, "Observe failed while parsing run results: some error with ask") + }) + + t.Run("when run execution succeeded", func(t *testing.T) { + t.Run("when feedId=linkFeedID=nativeFeedId", func(t *testing.T) { + t.Cleanup(func() { + ds.feedID, ds.linkFeedID, ds.nativeFeedID = feedId, linkFeedId, nativeFeedId + }) + + var feedId utils.FeedID = [32]byte{1} + ds.feedID, ds.linkFeedID, ds.nativeFeedID = feedId, feedId, feedId + + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + + assert.Equal(t, big.NewInt(122), obs.BenchmarkPrice.Val) + assert.NoError(t, obs.BenchmarkPrice.Err) + assert.Equal(t, big.NewInt(121), obs.Bid.Val) + assert.NoError(t, obs.Bid.Err) + assert.Equal(t, big.NewInt(123), obs.Ask.Val) + assert.NoError(t, obs.Ask.Err) + assert.Equal(t, int64(0), obs.MaxFinalizedTimestamp.Val) + assert.NoError(t, obs.MaxFinalizedTimestamp.Err) + assert.Equal(t, big.NewInt(122), obs.LinkPrice.Val) + assert.NoError(t, obs.LinkPrice.Err) + assert.Equal(t, big.NewInt(122), obs.NativePrice.Val) + assert.NoError(t, obs.NativePrice.Err) + assert.Equal(t, uint32(1), obs.MarketStatus.Val) + assert.NoError(t, obs.MarketStatus.Err) + }) + + t.Run("when fails to fetch linkPrice or nativePrice", func(t *testing.T) { + t.Cleanup(func() { + fetcher.linkPriceErr = nil + fetcher.nativePriceErr = nil + }) + + fetcher.linkPriceErr = errors.New("some error fetching link price") + fetcher.nativePriceErr = errors.New("some error fetching native price") + + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + + assert.Nil(t, obs.LinkPrice.Val) + assert.EqualError(t, obs.LinkPrice.Err, "some error fetching link price") + assert.Nil(t, obs.NativePrice.Val) + assert.EqualError(t, obs.NativePrice.Err, "some error fetching native price") + }) + + t.Run("when succeeds to fetch linkPrice or nativePrice but got nil (new feed)", func(t *testing.T) { + obs, err := ds.Observe(ctx, repts, false) + assert.NoError(t, err) + + assert.Equal(t, obs.LinkPrice.Val, relaymercuryv4.MissingPrice) + assert.Nil(t, obs.LinkPrice.Err) + assert.Equal(t, obs.NativePrice.Val, relaymercuryv4.MissingPrice) + assert.Nil(t, obs.NativePrice.Err) + }) + }) + }) +} + +var sampleFeedID = [32]uint8{28, 145, 107, 74, 167, 229, 124, 167, 182, 138, 225, 191, 69, 101, 63, 86, 182, 86, 253, 58, 163, 53, 239, 127, 174, 105, 107, 102, 63, 27, 132, 114} + +func buildSamplev4Report() []byte { + feedID := sampleFeedID + timestamp := uint32(124) + bp := big.NewInt(242) + bid := big.NewInt(243) + ask := big.NewInt(244) + validFromTimestamp := uint32(123) + expiresAt := uint32(456) + linkFee := big.NewInt(3334455) + nativeFee := big.NewInt(556677) + marketStatus := uint32(1) + + b, err := reportcodecv4.ReportTypes.Pack(feedID, validFromTimestamp, timestamp, nativeFee, linkFee, expiresAt, bp, bid, ask, marketStatus) + if err != nil { + panic(err) + } + return b +} diff --git a/core/services/relay/evm/mercury/v4/reportcodec/report_codec.go b/core/services/relay/evm/mercury/v4/reportcodec/report_codec.go new file mode 100644 index 00000000000..12f3d88e733 --- /dev/null +++ b/core/services/relay/evm/mercury/v4/reportcodec/report_codec.go @@ -0,0 +1,82 @@ +package reportcodec + +import ( + "errors" + "fmt" + "math/big" + + pkgerrors "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" + reporttypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v4/types" +) + +var ReportTypes = reporttypes.GetSchema() +var maxReportLength = 32 * len(ReportTypes) // each arg is 256 bit EVM word +var zero = big.NewInt(0) + +var _ v4.ReportCodec = &ReportCodec{} + +type ReportCodec struct { + logger logger.Logger + feedID utils.FeedID +} + +func NewReportCodec(feedID [32]byte, lggr logger.Logger) *ReportCodec { + return &ReportCodec{lggr, feedID} +} + +func (r *ReportCodec) BuildReport(rf v4.ReportFields) (ocrtypes.Report, error) { + var merr error + if rf.BenchmarkPrice == nil { + merr = errors.Join(merr, errors.New("benchmarkPrice may not be nil")) + } + if rf.Bid == nil { + merr = errors.Join(merr, errors.New("bid may not be nil")) + } + if rf.Ask == nil { + merr = errors.Join(merr, errors.New("ask may not be nil")) + } + if rf.LinkFee == nil { + merr = errors.Join(merr, errors.New("linkFee may not be nil")) + } else if rf.LinkFee.Cmp(zero) < 0 { + merr = errors.Join(merr, fmt.Errorf("linkFee may not be negative (got: %s)", rf.LinkFee)) + } + if rf.NativeFee == nil { + merr = errors.Join(merr, errors.New("nativeFee may not be nil")) + } else if rf.NativeFee.Cmp(zero) < 0 { + merr = errors.Join(merr, fmt.Errorf("nativeFee may not be negative (got: %s)", rf.NativeFee)) + } + if merr != nil { + return nil, merr + } + reportBytes, err := ReportTypes.Pack(r.feedID, rf.ValidFromTimestamp, rf.Timestamp, rf.NativeFee, rf.LinkFee, rf.ExpiresAt, rf.BenchmarkPrice, rf.Bid, rf.Ask, rf.MarketStatus) + return ocrtypes.Report(reportBytes), pkgerrors.Wrap(err, "failed to pack report blob") +} + +func (r *ReportCodec) MaxReportLength(n int) (int, error) { + return maxReportLength, nil +} + +func (r *ReportCodec) ObservationTimestampFromReport(report ocrtypes.Report) (uint32, error) { + decoded, err := r.Decode(report) + if err != nil { + return 0, err + } + return decoded.ObservationsTimestamp, nil +} + +func (r *ReportCodec) Decode(report ocrtypes.Report) (*reporttypes.Report, error) { + return reporttypes.Decode(report) +} + +func (r *ReportCodec) BenchmarkPriceFromReport(report ocrtypes.Report) (*big.Int, error) { + decoded, err := r.Decode(report) + if err != nil { + return nil, err + } + return decoded.BenchmarkPrice, nil +} diff --git a/core/services/relay/evm/mercury/v4/reportcodec/report_codec_test.go b/core/services/relay/evm/mercury/v4/reportcodec/report_codec_test.go new file mode 100644 index 00000000000..b62f42ef575 --- /dev/null +++ b/core/services/relay/evm/mercury/v4/reportcodec/report_codec_test.go @@ -0,0 +1,163 @@ +package reportcodec + +import ( + "math/big" + "testing" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" +) + +func newValidReportFields() v4.ReportFields { + return v4.ReportFields{ + Timestamp: 242, + BenchmarkPrice: big.NewInt(243), + Bid: big.NewInt(244), + Ask: big.NewInt(245), + ValidFromTimestamp: 123, + ExpiresAt: 20, + LinkFee: big.NewInt(456), + NativeFee: big.NewInt(457), + MarketStatus: 1, + } +} + +func Test_ReportCodec_BuildReport(t *testing.T) { + r := ReportCodec{} + + t.Run("BuildReport errors on zero values", func(t *testing.T) { + _, err := r.BuildReport(v4.ReportFields{}) + require.Error(t, err) + assert.Contains(t, err.Error(), "benchmarkPrice may not be nil") + assert.Contains(t, err.Error(), "linkFee may not be nil") + assert.Contains(t, err.Error(), "nativeFee may not be nil") + }) + + t.Run("BuildReport constructs a report from observations", func(t *testing.T) { + rf := newValidReportFields() + // only need to test happy path since validations are done in relaymercury + + report, err := r.BuildReport(rf) + require.NoError(t, err) + + reportElems := make(map[string]interface{}) + err = ReportTypes.UnpackIntoMap(reportElems, report) + require.NoError(t, err) + + assert.Equal(t, int(reportElems["observationsTimestamp"].(uint32)), 242) + assert.Equal(t, reportElems["benchmarkPrice"].(*big.Int).Int64(), int64(243)) + assert.Equal(t, reportElems["bid"].(*big.Int).Int64(), int64(244)) + assert.Equal(t, reportElems["ask"].(*big.Int).Int64(), int64(245)) + assert.Equal(t, reportElems["validFromTimestamp"].(uint32), uint32(123)) + assert.Equal(t, reportElems["expiresAt"].(uint32), uint32(20)) + assert.Equal(t, reportElems["linkFee"].(*big.Int).Int64(), int64(456)) + assert.Equal(t, reportElems["nativeFee"].(*big.Int).Int64(), int64(457)) + assert.Equal(t, reportElems["marketStatus"].(uint32), uint32(1)) + + assert.Equal(t, types.Report{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xc9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xc8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, report) + max, err := r.MaxReportLength(4) + require.NoError(t, err) + assert.LessOrEqual(t, len(report), max) + + t.Run("Decode decodes the report", func(t *testing.T) { + decoded, err := r.Decode(report) + require.NoError(t, err) + + require.NotNil(t, decoded) + + assert.Equal(t, uint32(242), decoded.ObservationsTimestamp) + assert.Equal(t, big.NewInt(243), decoded.BenchmarkPrice) + assert.Equal(t, big.NewInt(244), decoded.Bid) + assert.Equal(t, big.NewInt(245), decoded.Ask) + assert.Equal(t, uint32(123), decoded.ValidFromTimestamp) + assert.Equal(t, uint32(20), decoded.ExpiresAt) + assert.Equal(t, big.NewInt(456), decoded.LinkFee) + assert.Equal(t, big.NewInt(457), decoded.NativeFee) + assert.Equal(t, uint32(1), decoded.MarketStatus) + }) + }) + + t.Run("errors on negative fee", func(t *testing.T) { + rf := newValidReportFields() + rf.LinkFee = big.NewInt(-1) + rf.NativeFee = big.NewInt(-1) + _, err := r.BuildReport(rf) + require.Error(t, err) + + assert.Contains(t, err.Error(), "linkFee may not be negative (got: -1)") + assert.Contains(t, err.Error(), "nativeFee may not be negative (got: -1)") + }) + + t.Run("Decode errors on invalid report", func(t *testing.T) { + _, err := r.Decode([]byte{1, 2, 3}) + assert.EqualError(t, err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + + longBad := make([]byte, 64) + for i := 0; i < len(longBad); i++ { + longBad[i] = byte(i) + } + _, err = r.Decode(longBad) + assert.EqualError(t, err, "failed to decode report: abi: improperly encoded uint32 value") + }) +} + +func buildSampleReport(ts int64) []byte { + feedID := [32]byte{'f', 'o', 'o'} + timestamp := uint32(ts) + bp := big.NewInt(242) + bid := big.NewInt(243) + ask := big.NewInt(244) + validFromTimestamp := uint32(123) + expiresAt := uint32(456) + linkFee := big.NewInt(3334455) + nativeFee := big.NewInt(556677) + marketStatus := uint32(1) + + b, err := ReportTypes.Pack(feedID, validFromTimestamp, timestamp, nativeFee, linkFee, expiresAt, bp, bid, ask, marketStatus) + if err != nil { + panic(err) + } + return b +} + +func Test_ReportCodec_ObservationTimestampFromReport(t *testing.T) { + r := ReportCodec{} + + t.Run("ObservationTimestampFromReport extracts observation timestamp from a valid report", func(t *testing.T) { + report := buildSampleReport(123) + + ts, err := r.ObservationTimestampFromReport(report) + require.NoError(t, err) + + assert.Equal(t, ts, uint32(123)) + }) + t.Run("ObservationTimestampFromReport returns error when report is invalid", func(t *testing.T) { + report := []byte{1, 2, 3} + + _, err := r.ObservationTimestampFromReport(report) + require.Error(t, err) + + assert.EqualError(t, err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + }) +} + +func Test_ReportCodec_BenchmarkPriceFromReport(t *testing.T) { + r := ReportCodec{} + + t.Run("BenchmarkPriceFromReport extracts the benchmark price from valid report", func(t *testing.T) { + report := buildSampleReport(123) + + bp, err := r.BenchmarkPriceFromReport(report) + require.NoError(t, err) + + assert.Equal(t, big.NewInt(242), bp) + }) + t.Run("BenchmarkPriceFromReport errors on invalid report", func(t *testing.T) { + _, err := r.BenchmarkPriceFromReport([]byte{1, 2, 3}) + require.Error(t, err) + assert.EqualError(t, err, "failed to decode report: abi: cannot marshal in to go type: length insufficient 3 require 32") + }) +} diff --git a/core/services/relay/evm/mercury/v4/types/types.go b/core/services/relay/evm/mercury/v4/types/types.go new file mode 100644 index 00000000000..3abdd262a65 --- /dev/null +++ b/core/services/relay/evm/mercury/v4/types/types.go @@ -0,0 +1,58 @@ +package reporttypes + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var schema = GetSchema() + +func GetSchema() abi.Arguments { + mustNewType := func(t string) abi.Type { + result, err := abi.NewType(t, "", []abi.ArgumentMarshaling{}) + if err != nil { + panic(fmt.Sprintf("Unexpected error during abi.NewType: %s", err)) + } + return result + } + return abi.Arguments([]abi.Argument{ + {Name: "feedId", Type: mustNewType("bytes32")}, + {Name: "validFromTimestamp", Type: mustNewType("uint32")}, + {Name: "observationsTimestamp", Type: mustNewType("uint32")}, + {Name: "nativeFee", Type: mustNewType("uint192")}, + {Name: "linkFee", Type: mustNewType("uint192")}, + {Name: "expiresAt", Type: mustNewType("uint32")}, + {Name: "benchmarkPrice", Type: mustNewType("int192")}, + {Name: "bid", Type: mustNewType("int192")}, + {Name: "ask", Type: mustNewType("int192")}, + {Name: "marketStatus", Type: mustNewType("uint32")}, + }) +} + +type Report struct { + FeedId [32]byte + ObservationsTimestamp uint32 + BenchmarkPrice *big.Int + Bid *big.Int + Ask *big.Int + ValidFromTimestamp uint32 + ExpiresAt uint32 + LinkFee *big.Int + NativeFee *big.Int + MarketStatus uint32 +} + +// Decode is made available to external users (i.e. mercury server) +func Decode(report []byte) (*Report, error) { + values, err := schema.Unpack(report) + if err != nil { + return nil, fmt.Errorf("failed to decode report: %w", err) + } + decoded := new(Report) + if err = schema.Copy(decoded, values); err != nil { + return nil, fmt.Errorf("failed to copy report values to struct: %w", err) + } + return decoded, nil +} diff --git a/core/services/relay/evm/mercury_provider.go b/core/services/relay/evm/mercury_provider.go index 48882b701c9..9393f66b0dd 100644 --- a/core/services/relay/evm/mercury_provider.go +++ b/core/services/relay/evm/mercury_provider.go @@ -8,13 +8,12 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - mercurytypes "github.com/smartcontractkit/chainlink-common/pkg/types/mercury" v1 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v1" v2 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v2" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + v4 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v4" "github.com/smartcontractkit/chainlink-data-streams/mercury" - httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/logger" evmmercury "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" @@ -30,6 +29,7 @@ type mercuryProvider struct { reportCodecV1 v1.ReportCodec reportCodecV2 v2.ReportCodec reportCodecV3 v3.ReportCodec + reportCodecV4 v4.ReportCodec mercuryChainReader mercurytypes.ChainReader logger logger.Logger ms services.MultiStart @@ -44,6 +44,7 @@ func NewMercuryProvider( reportCodecV1 v1.ReportCodec, reportCodecV2 v2.ReportCodec, reportCodecV3 v3.ReportCodec, + reportCodecV4 v4.ReportCodec, lggr logger.Logger, ) *mercuryProvider { return &mercuryProvider{ @@ -54,6 +55,7 @@ func NewMercuryProvider( reportCodecV1, reportCodecV2, reportCodecV3, + reportCodecV4, mercuryChainReader, lggr, services.MultiStart{}, @@ -115,6 +117,10 @@ func (p *mercuryProvider) ReportCodecV3() v3.ReportCodec { return p.reportCodecV3 } +func (p *mercuryProvider) ReportCodecV4() v4.ReportCodec { + return p.reportCodecV4 +} + func (p *mercuryProvider) ContractTransmitter() ocrtypes.ContractTransmitter { return p.transmitter } diff --git a/core/services/synchronization/telem/telem.pb.go b/core/services/synchronization/telem/telem.pb.go index e1945bc26d3..d51b9628e22 100644 --- a/core/services/synchronization/telem/telem.pb.go +++ b/core/services/synchronization/telem/telem.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 +// protoc-gen-go v1.34.2 // protoc v4.25.1 // source: core/services/synchronization/telem/telem.proto @@ -264,7 +264,7 @@ func file_core_services_synchronization_telem_telem_proto_rawDescGZIP() []byte { } var file_core_services_synchronization_telem_telem_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_core_services_synchronization_telem_telem_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_proto_goTypes = []any{ (*TelemRequest)(nil), // 0: telem.TelemRequest (*TelemBatchRequest)(nil), // 1: telem.TelemBatchRequest (*TelemResponse)(nil), // 2: telem.TelemResponse @@ -287,7 +287,7 @@ func file_core_services_synchronization_telem_telem_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*TelemRequest); i { case 0: return &v.state @@ -299,7 +299,7 @@ func file_core_services_synchronization_telem_telem_proto_init() { return nil } } - file_core_services_synchronization_telem_telem_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*TelemBatchRequest); i { case 0: return &v.state @@ -311,7 +311,7 @@ func file_core_services_synchronization_telem_telem_proto_init() { return nil } } - file_core_services_synchronization_telem_telem_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*TelemResponse); i { case 0: return &v.state diff --git a/core/services/synchronization/telem/telem_automation_custom.pb.go b/core/services/synchronization/telem/telem_automation_custom.pb.go index a53339eda05..30ddce6f790 100644 --- a/core/services/synchronization/telem/telem_automation_custom.pb.go +++ b/core/services/synchronization/telem/telem_automation_custom.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 +// protoc-gen-go v1.34.2 // protoc v4.25.1 // source: core/services/synchronization/telem/telem_automation_custom.proto @@ -289,7 +289,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_rawD } var file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_core_services_synchronization_telem_telem_automation_custom_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_automation_custom_proto_goTypes = []any{ (*BlockNumber)(nil), // 0: telem.BlockNumber (*NodeVersion)(nil), // 1: telem.NodeVersion (*AutomationTelemWrapper)(nil), // 2: telem.AutomationTelemWrapper @@ -310,7 +310,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_init return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*BlockNumber); i { case 0: return &v.state @@ -322,7 +322,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_init return nil } } - file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*NodeVersion); i { case 0: return &v.state @@ -334,7 +334,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_init return nil } } - file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*AutomationTelemWrapper); i { case 0: return &v.state @@ -347,7 +347,7 @@ func file_core_services_synchronization_telem_telem_automation_custom_proto_init } } } - file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[2].OneofWrappers = []interface{}{ + file_core_services_synchronization_telem_telem_automation_custom_proto_msgTypes[2].OneofWrappers = []any{ (*AutomationTelemWrapper_BlockNumber)(nil), (*AutomationTelemWrapper_NodeVersion)(nil), } diff --git a/core/services/synchronization/telem/telem_enhanced_ea.pb.go b/core/services/synchronization/telem/telem_enhanced_ea.pb.go index a9a81dabfcc..c8983a06fea 100644 --- a/core/services/synchronization/telem/telem_enhanced_ea.pb.go +++ b/core/services/synchronization/telem/telem_enhanced_ea.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 +// protoc-gen-go v1.34.2 // protoc v4.25.1 // source: core/services/synchronization/telem/telem_enhanced_ea.proto @@ -239,7 +239,7 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_proto_rawDescGZI } var file_core_services_synchronization_telem_telem_enhanced_ea_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_core_services_synchronization_telem_telem_enhanced_ea_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_enhanced_ea_proto_goTypes = []any{ (*EnhancedEA)(nil), // 0: telem.EnhancedEA } var file_core_services_synchronization_telem_telem_enhanced_ea_proto_depIdxs = []int32{ @@ -256,7 +256,7 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_enhanced_ea_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_enhanced_ea_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*EnhancedEA); i { case 0: return &v.state diff --git a/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go b/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go index e152cb4b152..856619e1931 100644 --- a/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go +++ b/core/services/synchronization/telem/telem_enhanced_ea_mercury.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 +// protoc-gen-go v1.34.2 // protoc v4.25.1 // source: core/services/synchronization/telem/telem_enhanced_ea_mercury.proto @@ -20,6 +20,56 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type MarketStatus int32 + +const ( + // Same values as those used by OCR. + MarketStatus_UNKNOWN MarketStatus = 0 + MarketStatus_CLOSED MarketStatus = 1 + MarketStatus_OPEN MarketStatus = 2 +) + +// Enum value maps for MarketStatus. +var ( + MarketStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "CLOSED", + 2: "OPEN", + } + MarketStatus_value = map[string]int32{ + "UNKNOWN": 0, + "CLOSED": 1, + "OPEN": 2, + } +) + +func (x MarketStatus) Enum() *MarketStatus { + p := new(MarketStatus) + *p = x + return p +} + +func (x MarketStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MarketStatus) Descriptor() protoreflect.EnumDescriptor { + return file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_enumTypes[0].Descriptor() +} + +func (MarketStatus) Type() protoreflect.EnumType { + return &file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_enumTypes[0] +} + +func (x MarketStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MarketStatus.Descriptor instead. +func (MarketStatus) EnumDescriptor() ([]byte, []int) { + return file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescGZIP(), []int{0} +} + type EnhancedEAMercury struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -58,10 +108,12 @@ type EnhancedEAMercury struct { ObservationAsk int64 `protobuf:"varint,17,opt,name=observation_ask,json=observationAsk,proto3" json:"observation_ask,omitempty"` // This value overflows, will be reserved and removed in future versions ObservationBidString string `protobuf:"bytes,23,opt,name=observation_bid_string,json=observationBidString,proto3" json:"observation_bid_string,omitempty"` ObservationAskString string `protobuf:"bytes,24,opt,name=observation_ask_string,json=observationAskString,proto3" json:"observation_ask_string,omitempty"` - ConfigDigest string `protobuf:"bytes,18,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` - Round int64 `protobuf:"varint,19,opt,name=round,proto3" json:"round,omitempty"` - Epoch int64 `protobuf:"varint,20,opt,name=epoch,proto3" json:"epoch,omitempty"` - AssetSymbol string `protobuf:"bytes,21,opt,name=asset_symbol,json=assetSymbol,proto3" json:"asset_symbol,omitempty"` + // v4 + ObservationMarketStatus MarketStatus `protobuf:"varint,34,opt,name=observation_market_status,json=observationMarketStatus,proto3,enum=telem.MarketStatus" json:"observation_market_status,omitempty"` + ConfigDigest string `protobuf:"bytes,18,opt,name=config_digest,json=configDigest,proto3" json:"config_digest,omitempty"` + Round int64 `protobuf:"varint,19,opt,name=round,proto3" json:"round,omitempty"` + Epoch int64 `protobuf:"varint,20,opt,name=epoch,proto3" json:"epoch,omitempty"` + AssetSymbol string `protobuf:"bytes,21,opt,name=asset_symbol,json=assetSymbol,proto3" json:"asset_symbol,omitempty"` } func (x *EnhancedEAMercury) Reset() { @@ -299,6 +351,13 @@ func (x *EnhancedEAMercury) GetObservationAskString() string { return "" } +func (x *EnhancedEAMercury) GetObservationMarketStatus() MarketStatus { + if x != nil { + return x.ObservationMarketStatus + } + return MarketStatus_UNKNOWN +} + func (x *EnhancedEAMercury) GetConfigDigest() string { if x != nil { return x.ConfigDigest @@ -334,7 +393,7 @@ var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_raw 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x5f, 0x65, 0x6e, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x65, 0x61, 0x5f, 0x6d, 0x65, 0x72, 0x63, 0x75, 0x72, 0x79, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x22, 0xa9, 0x0c, 0x0a, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x22, 0xfa, 0x0c, 0x0a, 0x11, 0x45, 0x6e, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x45, 0x41, 0x4d, 0x65, 0x72, 0x63, 0x75, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, @@ -426,19 +485,28 @@ var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_raw 0x0a, 0x16, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x73, 0x6b, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, - 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, - 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x42, 0x4e, 0x5a, 0x4c, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x72, 0x74, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x61, 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x69, 0x6e, - 0x6b, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x69, 0x6e, 0x67, 0x12, 0x4f, 0x0a, 0x19, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x2e, + 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x17, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, + 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, + 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x2a, 0x31, 0x0a, 0x0c, 0x4d, 0x61, 0x72, + 0x6b, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, + 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x02, 0x42, 0x4e, 0x5a, 0x4c, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6d, 0x61, 0x72, 0x74, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6b, 0x69, 0x74, 0x2f, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -453,16 +521,19 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_ra return file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDescData } +var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes = []interface{}{ - (*EnhancedEAMercury)(nil), // 0: telem.EnhancedEAMercury +var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes = []any{ + (MarketStatus)(0), // 0: telem.MarketStatus + (*EnhancedEAMercury)(nil), // 1: telem.EnhancedEAMercury } var file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: telem.EnhancedEAMercury.observation_market_status:type_name -> telem.MarketStatus + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_init() } @@ -471,7 +542,7 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_in return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*EnhancedEAMercury); i { case 0: return &v.state @@ -489,13 +560,14 @@ func file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_in File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_rawDesc, - NumEnums: 0, + NumEnums: 1, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_goTypes, DependencyIndexes: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_depIdxs, + EnumInfos: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_enumTypes, MessageInfos: file_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto_msgTypes, }.Build() File_core_services_synchronization_telem_telem_enhanced_ea_mercury_proto = out.File diff --git a/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto b/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto index 8488eb1d509..bb41ff86ee3 100644 --- a/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto +++ b/core/services/synchronization/telem/telem_enhanced_ea_mercury.proto @@ -4,6 +4,13 @@ option go_package = "github.com/smartcontractkit/chainlink/v2/core/services/sync package telem; +enum MarketStatus { + // Same values as those used by OCR. + UNKNOWN = 0; + CLOSED = 1; + OPEN = 2; +} + message EnhancedEAMercury { uint32 version = 32; @@ -44,6 +51,8 @@ message EnhancedEAMercury { int64 observation_ask=17; // This value overflows, will be reserved and removed in future versions string observation_bid_string = 23; string observation_ask_string = 24; + // v4 + MarketStatus observation_market_status=34; string config_digest = 18; int64 round=19; diff --git a/core/services/synchronization/telem/telem_functions_request.pb.go b/core/services/synchronization/telem/telem_functions_request.pb.go index 0a4a2649b4e..89aa9e3fe37 100644 --- a/core/services/synchronization/telem/telem_functions_request.pb.go +++ b/core/services/synchronization/telem/telem_functions_request.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 +// protoc-gen-go v1.34.2 // protoc v4.25.1 // source: core/services/synchronization/telem/telem_functions_request.proto @@ -119,7 +119,7 @@ func file_core_services_synchronization_telem_telem_functions_request_proto_rawD } var file_core_services_synchronization_telem_telem_functions_request_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_core_services_synchronization_telem_telem_functions_request_proto_goTypes = []interface{}{ +var file_core_services_synchronization_telem_telem_functions_request_proto_goTypes = []any{ (*FunctionsRequest)(nil), // 0: telem.FunctionsRequest } var file_core_services_synchronization_telem_telem_functions_request_proto_depIdxs = []int32{ @@ -136,7 +136,7 @@ func file_core_services_synchronization_telem_telem_functions_request_proto_init return } if !protoimpl.UnsafeEnabled { - file_core_services_synchronization_telem_telem_functions_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_core_services_synchronization_telem_telem_functions_request_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*FunctionsRequest); i { case 0: return &v.state diff --git a/go.mod b/go.mod index 326c06396da..8e2103eb246 100644 --- a/go.mod +++ b/go.mod @@ -72,9 +72,9 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.10 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 - github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa + github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 diff --git a/go.sum b/go.sum index 2ec1753593f..73d6d5b227a 100644 --- a/go.sum +++ b/go.sum @@ -1136,12 +1136,12 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996 h1:6s4cTIE3NbATxWLrD5JLCq097PC5Y4GKK/Kk4fhURpY= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc h1:nNZqLasN8y5huDKX76JUZtni7WkUI36J61//czbJpDM= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa h1:g75H8oh2ws52s8BekwvGQ9XvBVu3E7WM1rfiA0PN0zk= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa/go.mod h1:wZvLHX/Sd9hskN51016cTFcT3G62KXVa6xbVDS7tRjc= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f h1:I9fTBJpHkeldFplXUy71eLIn6A6GxuR4xrABoUeD+CM= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f/go.mod h1:V/86loaFSH0dqqUEHqyXVbyNqDRSjvcf9BRomWFTljU= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 29af8b4c217..ed693f4fccc 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -28,7 +28,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc github.com/smartcontractkit/chainlink-testing-framework v1.33.0 github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240405215812-5a72bc9af239 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 @@ -377,7 +377,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.10 // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect - github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect + github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 57b55a8b017..ca3ce8d903e 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1486,12 +1486,12 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996 h1:6s4cTIE3NbATxWLrD5JLCq097PC5Y4GKK/Kk4fhURpY= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc h1:nNZqLasN8y5huDKX76JUZtni7WkUI36J61//czbJpDM= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa h1:g75H8oh2ws52s8BekwvGQ9XvBVu3E7WM1rfiA0PN0zk= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa/go.mod h1:wZvLHX/Sd9hskN51016cTFcT3G62KXVa6xbVDS7tRjc= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f h1:I9fTBJpHkeldFplXUy71eLIn6A6GxuR4xrABoUeD+CM= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f/go.mod h1:V/86loaFSH0dqqUEHqyXVbyNqDRSjvcf9BRomWFTljU= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 093a6ac6c58..3d1ae6c7a98 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -16,7 +16,7 @@ require ( github.com/rs/zerolog v1.31.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc github.com/smartcontractkit/chainlink-testing-framework v1.33.0 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240214231432-4ad5eb95178c github.com/smartcontractkit/chainlink/v2 v2.9.0-beta0.0.20240216210048-da02459ddad8 @@ -369,7 +369,7 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.10 // indirect - github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect + github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 0db884f178e..2a54ec9254f 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1468,12 +1468,12 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996 h1:6s4cTIE3NbATxWLrD5JLCq097PC5Y4GKK/Kk4fhURpY= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731121127-5ae22cf04996/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc h1:nNZqLasN8y5huDKX76JUZtni7WkUI36J61//czbJpDM= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240731184516-249ef7ad0cdc/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa h1:g75H8oh2ws52s8BekwvGQ9XvBVu3E7WM1rfiA0PN0zk= -github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa/go.mod h1:wZvLHX/Sd9hskN51016cTFcT3G62KXVa6xbVDS7tRjc= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f h1:I9fTBJpHkeldFplXUy71eLIn6A6GxuR4xrABoUeD+CM= +github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f/go.mod h1:V/86loaFSH0dqqUEHqyXVbyNqDRSjvcf9BRomWFTljU= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e h1:PzwzlHNv1YbJ6ZIdl/pIFRoOuOS4V4WLvjZvFUnZFL4=