From aa58557c088fa2bc8b3f05b6be1bfd2687cddcc6 Mon Sep 17 00:00:00 2001 From: Matthew Pendrey Date: Mon, 17 Jun 2024 12:49:08 +0100 Subject: [PATCH] keystone end to end integration tests --- .changeset/lucky-cameras-punch.md | 5 + .swp | Bin 0 -> 61440 bytes .../scripts/native_solc_compile_all_keystone | 1 + .../keystone_contracts_setup.go | 302 ++++++++ .../integration_tests/mock_dispatcher.go | 177 +++++ .../integration_tests/mock_libocr.go | 163 ++++ .../integration_tests/mock_trigger.go | 113 +++ core/capabilities/integration_tests/setup.go | 433 +++++++++++ .../integration_tests/streams_test.go | 99 +++ .../integration_tests/workflow.go | 75 ++ core/capabilities/targets/write_target.go | 3 +- core/cmd/shell_local_test.go | 6 +- .../feeds_consumer/feeds_consumer.go | 732 ++++++++++++++++++ ...rapper-dependency-versions-do-not-edit.txt | 1 + core/gethwrappers/keystone/go_generate.go | 1 + core/internal/cltest/cltest.go | 44 +- core/services/chainlink/application.go | 30 +- .../relayer_chain_interoperators_test.go | 8 +- core/services/chainlink/relayer_factory.go | 5 +- 19 files changed, 2177 insertions(+), 21 deletions(-) create mode 100644 .changeset/lucky-cameras-punch.md create mode 100644 .swp create mode 100644 core/capabilities/integration_tests/keystone_contracts_setup.go create mode 100644 core/capabilities/integration_tests/mock_dispatcher.go create mode 100644 core/capabilities/integration_tests/mock_libocr.go create mode 100644 core/capabilities/integration_tests/mock_trigger.go create mode 100644 core/capabilities/integration_tests/setup.go create mode 100644 core/capabilities/integration_tests/streams_test.go create mode 100644 core/capabilities/integration_tests/workflow.go create mode 100644 core/gethwrappers/keystone/generated/feeds_consumer/feeds_consumer.go diff --git a/.changeset/lucky-cameras-punch.md b/.changeset/lucky-cameras-punch.md new file mode 100644 index 00000000000..73dbc1e7c7e --- /dev/null +++ b/.changeset/lucky-cameras-punch.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#internal end to end test for streams capabilities diff --git a/.swp b/.swp new file mode 100644 index 0000000000000000000000000000000000000000..e2293baeb2c82c5dda684b915378632a283c71fb GIT binary patch literal 61440 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 HJo5hlrWzqI literal 0 HcmV?d00001 diff --git a/contracts/scripts/native_solc_compile_all_keystone b/contracts/scripts/native_solc_compile_all_keystone index 0516b2aedb0..a42b06b8f67 100755 --- a/contracts/scripts/native_solc_compile_all_keystone +++ b/contracts/scripts/native_solc_compile_all_keystone @@ -32,3 +32,4 @@ compileContract () { compileContract keystone/CapabilitiesRegistry.sol compileContract keystone/KeystoneForwarder.sol compileContract keystone/OCR3Capability.sol +compileContract keystone/KeystoneFeedsConsumer.sol diff --git a/core/capabilities/integration_tests/keystone_contracts_setup.go b/core/capabilities/integration_tests/keystone_contracts_setup.go new file mode 100644 index 00000000000..9dbe6d92530 --- /dev/null +++ b/core/capabilities/integration_tests/keystone_contracts_setup.go @@ -0,0 +1,302 @@ +package integration_tests + +import ( + "context" + "encoding/hex" + "fmt" + "log" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/feeds_consumer" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + + remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +type peer struct { + PeerID string + Signer string +} + +func peerIDToBytes(peerID string) ([32]byte, error) { + var peerIDB ragetypes.PeerID + err := peerIDB.UnmarshalText([]byte(peerID)) + if err != nil { + return [32]byte{}, err + } + + return peerIDB, nil +} + +func peers(ps []peer) ([][32]byte, error) { + out := [][32]byte{} + for _, p := range ps { + b, err := peerIDToBytes(p.PeerID) + if err != nil { + return nil, err + } + + out = append(out, b) + } + + return out, nil +} + +func peerToNode(nopID uint32, p peer) (kcr.CapabilitiesRegistryNodeParams, error) { + peerIDB, err := peerIDToBytes(p.PeerID) + if err != nil { + return kcr.CapabilitiesRegistryNodeParams{}, fmt.Errorf("failed to convert peerID: %w", err) + } + + sig := strings.TrimPrefix(p.Signer, "0x") + signerB, err := hex.DecodeString(sig) + if err != nil { + return kcr.CapabilitiesRegistryNodeParams{}, fmt.Errorf("failed to convert signer: %w", err) + } + + var sigb [32]byte + copy(sigb[:], signerB) + + return kcr.CapabilitiesRegistryNodeParams{ + NodeOperatorId: nopID, + P2pId: peerIDB, + Signer: sigb, + }, nil +} + +func setupCapabilitiesRegistryContract(ctx context.Context, t *testing.T, workflowDonPeers []peer, triggerDonPeers []peer, + targetDonPeerIDs []peer, + transactOpts *bind.TransactOpts, backend *ethBackend) common.Address { + addr, _, reg, err := kcr.DeployCapabilitiesRegistry(transactOpts, backend) + require.NoError(t, err) + + backend.Commit() + + streamsTrigger := kcr.CapabilitiesRegistryCapability{ + LabelledName: "streams-trigger", + Version: "1.0.0", + CapabilityType: uint8(capabilities.CapabilityTypeTrigger), + } + sid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, streamsTrigger.LabelledName, streamsTrigger.Version) + require.NoError(t, err) + + writeChain := kcr.CapabilitiesRegistryCapability{ + LabelledName: "write_geth-testnet", + Version: "1.0.0", + CapabilityType: uint8(capabilities.CapabilityTypeTarget), + } + wid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, writeChain.LabelledName, writeChain.Version) + if err != nil { + log.Printf("failed to call GetHashedCapabilityId: %s", err) + } + + ocr := kcr.CapabilitiesRegistryCapability{ + LabelledName: "offchain_reporting", + Version: "1.0.0", + CapabilityType: uint8(capabilities.CapabilityTypeConsensus), + } + ocrid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, ocr.LabelledName, ocr.Version) + require.NoError(t, err) + + _, err = reg.AddCapabilities(transactOpts, []kcr.CapabilitiesRegistryCapability{ + streamsTrigger, + writeChain, + ocr, + }) + require.NoError(t, err) + backend.Commit() + + _, err = reg.AddNodeOperators(transactOpts, []kcr.CapabilitiesRegistryNodeOperator{ + { + Admin: transactOpts.From, + Name: "TEST_NODE_OPERATOR", + }, + }) + require.NoError(t, err) + blockHash := backend.Commit() + + logs, err := backend.FilterLogs(ctx, ethereum.FilterQuery{ + BlockHash: &blockHash, + FromBlock: nil, + ToBlock: nil, + Addresses: nil, + Topics: nil, + }) + + require.NoError(t, err) + + recLog, err := reg.ParseNodeOperatorAdded(logs[0]) + require.NoError(t, err) + + nopID := recLog.NodeOperatorId + nodes := []kcr.CapabilitiesRegistryNodeParams{} + for _, wfPeer := range workflowDonPeers { + n, innerErr := peerToNode(nopID, wfPeer) + require.NoError(t, innerErr) + + n.HashedCapabilityIds = [][32]byte{ocrid} + nodes = append(nodes, n) + } + + for _, triggerPeer := range triggerDonPeers { + n, innerErr := peerToNode(nopID, triggerPeer) + require.NoError(t, innerErr) + + n.HashedCapabilityIds = [][32]byte{sid} + nodes = append(nodes, n) + } + + for _, targetPeer := range targetDonPeerIDs { + n, innerErr := peerToNode(nopID, targetPeer) + require.NoError(t, innerErr) + + n.HashedCapabilityIds = [][32]byte{wid} + nodes = append(nodes, n) + } + + _, err = reg.AddNodes(transactOpts, nodes) + require.NoError(t, err) + + // workflow DON + ps, err := peers(workflowDonPeers) + require.NoError(t, err) + + cfgs := []kcr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: ocrid, + }, + } + + workflowDonF := uint8(2) + _, err = reg.AddDON(transactOpts, ps, cfgs, false, true, workflowDonF) + require.NoError(t, err) + + // trigger DON + ps, err = peers(triggerDonPeers) + require.NoError(t, err) + + triggerDonF := 1 + config := &remotetypes.RemoteTriggerConfig{ + RegistrationRefreshMs: 20000, + RegistrationExpiryMs: 60000, + // F + 1 + MinResponsesToAggregate: uint32(triggerDonF) + 1, + } + configb, err := proto.Marshal(config) + require.NoError(t, err) + + cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: sid, + Config: configb, + }, + } + + _, err = reg.AddDON(transactOpts, ps, cfgs, true, false, uint8(triggerDonF)) + require.NoError(t, err) + + // target DON + ps, err = peers(targetDonPeerIDs) + require.NoError(t, err) + + cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: wid, + }, + } + + targetDonF := uint8(1) + _, err = reg.AddDON(transactOpts, ps, cfgs, true, false, targetDonF) + require.NoError(t, err) + + backend.Commit() + + return addr +} + +func setupForwarderContract(t *testing.T, workflowDonPeers []peer, workflowDonId uint32, + configVersion uint32, f uint8, + transactOpts *bind.TransactOpts, backend *ethBackend) (common.Address, *forwarder.KeystoneForwarder) { + addr, _, fwd, err := forwarder.DeployKeystoneForwarder(transactOpts, backend) + require.NoError(t, err) + backend.Commit() + + var signers []common.Address + for _, p := range workflowDonPeers { + signers = append(signers, common.HexToAddress(p.Signer)) + } + + _, err = fwd.SetConfig(transactOpts, workflowDonId, configVersion, f, signers) + require.NoError(t, err) + backend.Commit() + + return addr, fwd +} + +func setupConsumerContract(t *testing.T, transactOpts *bind.TransactOpts, backend *ethBackend, + forwarderAddress common.Address, workflowOwner string, workflowName string) (common.Address, *feeds_consumer.KeystoneFeedsConsumer) { + addr, _, consumer, err := feeds_consumer.DeployKeystoneFeedsConsumer(transactOpts, backend) + require.NoError(t, err) + backend.Commit() + + var nameBytes [10]byte + copy(nameBytes[:], workflowName) + + ownerAddr := common.HexToAddress(workflowOwner) + + _, err = consumer.SetConfig(transactOpts, []common.Address{forwarderAddress}, []common.Address{ownerAddr}, [][10]byte{nameBytes}) + require.NoError(t, err) + + backend.Commit() + + return addr, consumer +} + +type ethBackend struct { + *backends.SimulatedBackend +} + +func setupBlockchain(t *testing.T, initialEth int) (*ethBackend, *bind.TransactOpts) { + transactOpts := testutils.MustNewSimTransactor(t) // config contract deployer and owner + genesisData := core.GenesisAlloc{transactOpts.From: {Balance: assets.Ether(initialEth).ToInt()}} + backend := cltest.NewSimulatedBackend(t, genesisData, uint32(ethconfig.Defaults.Miner.GasCeil)) + backend.Commit() + + return ðBackend{backend}, transactOpts +} + +func (b ethBackend) Start(ctx context.Context, blockTimeProcessingTime time.Duration) { + go func() { + ticker := time.NewTicker(blockTimeProcessingTime) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + b.SimulatedBackend.Commit() + } + } + }() +} diff --git a/core/capabilities/integration_tests/mock_dispatcher.go b/core/capabilities/integration_tests/mock_dispatcher.go new file mode 100644 index 00000000000..371615b74ab --- /dev/null +++ b/core/capabilities/integration_tests/mock_dispatcher.go @@ -0,0 +1,177 @@ +package integration_tests + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" + + "google.golang.org/protobuf/proto" +) + +type receiverKey struct { + capabilityId string + donId uint32 +} + +// testAsyncMessageBroker backs the dispatchers created for each node in the test and effectively +// acts as the rageP2P network layer. +type testAsyncMessageBroker struct { + services.StateMachine + t *testing.T + + nodes map[p2ptypes.PeerID]*dispatcherNode + + sendCh chan *remotetypes.MessageBody + + chanBufferSize int + + stopCh services.StopChan + wg sync.WaitGroup +} + +// NewDispatcherForNode creates a new dispatcher for a node with the given peer ID. +func (a *testAsyncMessageBroker) NewDispatcherForNode(nodePeerID p2ptypes.PeerID) remotetypes.Dispatcher { + return &nodeDispatcher{ + callerPeerID: nodePeerID, + broker: a, + } +} + +func (a *testAsyncMessageBroker) HealthReport() map[string]error { + return nil +} + +func (a *testAsyncMessageBroker) Name() string { + return "testAsyncMessageBroker" +} + +func newTestAsyncMessageBroker(t *testing.T, chanBufferSize int) *testAsyncMessageBroker { + return &testAsyncMessageBroker{ + t: t, + nodes: make(map[p2ptypes.PeerID]*dispatcherNode), + stopCh: make(services.StopChan), + sendCh: make(chan *remotetypes.MessageBody, chanBufferSize), + chanBufferSize: chanBufferSize, + } +} + +func (a *testAsyncMessageBroker) Start(ctx context.Context) error { + return a.StartOnce("testAsyncMessageBroker", func() error { + a.wg.Add(1) + go func() { + defer a.wg.Done() + + for { + select { + case <-a.stopCh: + return + case msg := <-a.sendCh: + peerID := toPeerID(msg.Receiver) + node, ok := a.nodes[peerID] + if !ok { + panic("node not found for peer id") + } + + node.receiveCh <- msg + } + } + }() + return nil + }) +} + +func (a *testAsyncMessageBroker) Close() error { + return a.StopOnce("testAsyncMessageBroker", func() error { + close(a.stopCh) + + a.wg.Wait() + return nil + }) +} + +type dispatcherNode struct { + receivers map[receiverKey]remotetypes.Receiver + receiveCh chan *remotetypes.MessageBody +} + +func (a *testAsyncMessageBroker) registerReceiverNode(nodePeerID p2ptypes.PeerID, capabilityId string, capabilityDonID uint32, receiver remotetypes.Receiver) { + key := receiverKey{ + capabilityId: capabilityId, + donId: capabilityDonID, + } + + node, nodeExists := a.nodes[nodePeerID] + if !nodeExists { + node = &dispatcherNode{ + receivers: make(map[receiverKey]remotetypes.Receiver), + receiveCh: make(chan *remotetypes.MessageBody, a.chanBufferSize), + } + + a.wg.Add(1) + go func() { + defer a.wg.Done() + + for { + select { + case <-a.stopCh: + return + case msg := <-node.receiveCh: + k := receiverKey{ + capabilityId: msg.CapabilityId, + donId: msg.CapabilityDonId, + } + + r, ok := node.receivers[k] + if !ok { + panic("receiver not found for key") + } + + r.Receive(tests.Context(a.t), msg) + } + } + }() + + a.nodes[nodePeerID] = node + } + + node.receivers[key] = receiver +} + +func (a *testAsyncMessageBroker) Send(msg *remotetypes.MessageBody) { + a.sendCh <- msg +} + +func toPeerID(id []byte) p2ptypes.PeerID { + return [32]byte(id) +} + +type broker interface { + Send(msg *remotetypes.MessageBody) +} + +type nodeDispatcher struct { + callerPeerID p2ptypes.PeerID + broker broker +} + +func (t *nodeDispatcher) Send(peerID p2ptypes.PeerID, msgBody *remotetypes.MessageBody) error { + clonedMsg := proto.Clone(msgBody).(*remotetypes.MessageBody) + clonedMsg.Version = 1 + clonedMsg.Sender = t.callerPeerID[:] + clonedMsg.Receiver = peerID[:] + clonedMsg.Timestamp = time.Now().UnixMilli() + t.broker.Send(clonedMsg) + return nil +} + +func (t *nodeDispatcher) SetReceiver(capabilityId string, donId uint32, receiver remotetypes.Receiver) error { + t.broker.(*testAsyncMessageBroker).registerReceiverNode(t.callerPeerID, capabilityId, donId, receiver) + return nil +} +func (t *nodeDispatcher) RemoveReceiver(capabilityId string, donId uint32) {} diff --git a/core/capabilities/integration_tests/mock_libocr.go b/core/capabilities/integration_tests/mock_libocr.go new file mode 100644 index 00000000000..a36686c6bde --- /dev/null +++ b/core/capabilities/integration_tests/mock_libocr.go @@ -0,0 +1,163 @@ +package integration_tests + +import ( + "bytes" + "context" + "fmt" + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" +) + +type node struct { + ocr3types.ReportingPlugin[[]byte] + *ocr3.ContractTransmitter + key ocr2key.KeyBundle +} + +// mockLibOCR is a mock libocr implementation for testing purposes that simulates libocr protocol rounds without having +// to setup the libocr network +type mockLibOCR struct { + nodes []*node + f uint8 +} + +func newMockLibOCR(f uint8) *mockLibOCR { + return &mockLibOCR{f: f} +} + +func (m *mockLibOCR) Start(ctx context.Context, t *testing.T, protocolRoundInterval time.Duration) { + go func() { + ticker := time.NewTicker(protocolRoundInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + err := m.simulateProtocolRound(ctx) + if err != nil { + require.FailNow(t, err.Error()) + } + } + } + }() +} + +func (m *mockLibOCR) AddNode(plugin ocr3types.ReportingPlugin[[]byte], transmitter *ocr3.ContractTransmitter, key ocr2key.KeyBundle) { + m.nodes = append(m.nodes, &node{plugin, transmitter, key}) +} + +func (m *mockLibOCR) simulateProtocolRound(ctx context.Context) error { + var seqNr uint64 + + // randomly select a leader + leader := m.nodes[rand.Intn(len(m.nodes))] + + outcomeCtx := ocr3types.OutcomeContext{ + SeqNr: seqNr, + PreviousOutcome: nil, + Epoch: 0, + Round: 0, + } + + // get the query + query, err := leader.Query(ctx, outcomeCtx) + if err != nil { + return fmt.Errorf("failed to get query: %w", err) + } + + var observations []types.AttributedObservation + for oracleID, node := range m.nodes { + obs, err2 := node.Observation(ctx, outcomeCtx, query) + if err2 != nil { + return fmt.Errorf("failed to get observation: %w", err) + } + + observations = append(observations, types.AttributedObservation{ + Observation: obs, + Observer: commontypes.OracleID(oracleID), + }) + } + + var outcomes []ocr3types.Outcome + for _, node := range m.nodes { + outcome, err2 := node.Outcome(outcomeCtx, query, observations) + if err2 != nil { + return fmt.Errorf("failed to get outcome: %w", err) + } + + if len(outcome) == 0 { + return nil // wait until all nodes have an outcome for testing purposes + } + + outcomes = append(outcomes, outcome) + } + + // if all outcomes are equal proceed to reports + for _, outcome := range outcomes { + if !bytes.Equal(outcome, outcomes[0]) { + return nil + } + } + + reports, err := leader.Reports(0, outcomes[0]) + if err != nil { + return fmt.Errorf("failed to get reports: %w", err) + } + for _, report := range reports { + // create signatures + var signatures []types.AttributedOnchainSignature + for i, node := range m.nodes { + sig, err := node.key.Sign(types.ReportContext{}, report.Report) + if err != nil { + return fmt.Errorf("failed to sign report: %w", err) + } + + signatures = append(signatures, types.AttributedOnchainSignature{ + Signer: commontypes.OracleID(i), + Signature: sig, + }) + + if uint8(len(signatures)) == m.f+1 { + break + } + } + + for _, node := range m.nodes { + accept, err := node.ShouldAcceptAttestedReport(ctx, seqNr, report) + if err != nil { + return fmt.Errorf("failed to check if report should be accepted: %w", err) + } + if !accept { + continue + } + + transmit, err := node.ShouldTransmitAcceptedReport(ctx, seqNr, report) + if err != nil { + return fmt.Errorf("failed to check if report should be transmitted: %w", err) + } + + if !transmit { + continue + } + + err = node.Transmit(ctx, types.ConfigDigest{}, 0, report, signatures) + if err != nil { + return fmt.Errorf("failed to transmit report: %w", err) + } + } + } + + return nil +} diff --git a/core/capabilities/integration_tests/mock_trigger.go b/core/capabilities/integration_tests/mock_trigger.go new file mode 100644 index 00000000000..073760803d1 --- /dev/null +++ b/core/capabilities/integration_tests/mock_trigger.go @@ -0,0 +1,113 @@ +package integration_tests + +import ( + "context" + "fmt" + "strconv" + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/datastreams" + "github.com/smartcontractkit/chainlink-common/pkg/values" +) + +const triggerID = "streams-trigger@1.0.0" + +type reportsSink struct { + triggers []streamsTrigger +} + +func (r *reportsSink) sendReports(reportList []*datastreams.FeedReport) { + for _, trigger := range r.triggers { + resp, err := wrapReports(reportList, "1", 12, datastreams.SignersMetadata{}) + if err != nil { + panic(err) + } + trigger.sendResponse(resp) + } +} + +func (r *reportsSink) getNewTrigger(t *testing.T) *streamsTrigger { + trigger := streamsTrigger{t: t, toSend: make(chan capabilities.CapabilityResponse, 1000)} + r.triggers = append(r.triggers, trigger) + return &trigger +} + +type streamsTrigger struct { + t *testing.T + cancel context.CancelFunc + toSend chan capabilities.CapabilityResponse +} + +func (s *streamsTrigger) sendResponse(resp capabilities.CapabilityResponse) { + s.toSend <- resp +} + +func (s *streamsTrigger) Info(ctx context.Context) (capabilities.CapabilityInfo, error) { + return capabilities.MustNewCapabilityInfo( + triggerID, + capabilities.CapabilityTypeTrigger, + "issues a trigger when a report is received.", + ), nil +} + +func (s *streamsTrigger) RegisterTrigger(ctx context.Context, request capabilities.CapabilityRequest) (<-chan capabilities.CapabilityResponse, error) { + if s.cancel != nil { + s.t.Fatal("trigger already registered") + } + + responseCh := make(chan capabilities.CapabilityResponse) + + ctxWithCancel, cancel := context.WithCancel(ctx) + s.cancel = cancel + go func() { + select { + case <-ctxWithCancel.Done(): + return + case resp := <-s.toSend: + responseCh <- resp + } + }() + + return responseCh, nil +} + +func (s *streamsTrigger) UnregisterTrigger(ctx context.Context, request capabilities.CapabilityRequest) error { + if s.cancel == nil { + s.t.Fatal("trigger not registered") + } + + s.cancel() + s.cancel = nil + return nil +} + +func wrapReports(reportList []*datastreams.FeedReport, eventID string, timestamp int64, meta datastreams.SignersMetadata) (capabilities.CapabilityResponse, error) { + val, err := values.Wrap(reportList) + if err != nil { + return capabilities.CapabilityResponse{}, err + } + + metaVal, err := values.Wrap(meta) + if err != nil { + return capabilities.CapabilityResponse{}, err + } + + triggerEvent := capabilities.TriggerEvent{ + TriggerType: triggerID, + ID: eventID, + Timestamp: strconv.FormatInt(timestamp, 10), + Metadata: metaVal, + Payload: val, + } + + triggerEventMapValue, err := values.WrapMap(triggerEvent) + if err != nil { + return capabilities.CapabilityResponse{}, fmt.Errorf("failed to wrap trigger event: %w", err) + } + + // Create a new CapabilityResponse with the MercuryTriggerEvent + return capabilities.CapabilityResponse{ + Value: triggerEventMapValue, + }, nil +} diff --git a/core/capabilities/integration_tests/setup.go b/core/capabilities/integration_tests/setup.go new file mode 100644 index 00000000000..9411bc77d78 --- /dev/null +++ b/core/capabilities/integration_tests/setup.go @@ -0,0 +1,433 @@ +package integration_tests + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/mr-tron/base58" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/chains/evmutil" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + ocrTypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/feeds_consumer" + + commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/datastreams" + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + coretypes "github.com/smartcontractkit/chainlink-common/pkg/types/core" + v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" + "github.com/smartcontractkit/chainlink/v2/core/capabilities" + remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" + p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" +) + +var ( + workflowName = "abcdef0123" + workflowOwnerID = "0100000000000000000000000000000000000001" +) + +type donInfo struct { + commoncap.DON + keys []ethkey.KeyV2 + keyBundles []ocr2key.KeyBundle + peerIDs []peer +} + +func setupStreamDonsWithTransmissionSchedule(ctx context.Context, t *testing.T, workflowDonInfo donInfo, triggerDonInfo donInfo, targetDonInfo donInfo, + feedCount int, deltaStage string, schedule string) (*feeds_consumer.KeystoneFeedsConsumer, []string, *reportsSink) { + ethBlockchain, transactor := setupBlockchain(t, 1000) + capabilitiesRegistryAddr := setupCapabilitiesRegistryContract(ctx, t, workflowDonInfo.peerIDs, triggerDonInfo.peerIDs, targetDonInfo.peerIDs, transactor, ethBlockchain) + forwarderAddr, _ := setupForwarderContract(t, workflowDonInfo.peerIDs, workflowDonInfo.ID, 1, workflowDonInfo.F, transactor, ethBlockchain) + consumerAddr, consumer := setupConsumerContract(t, transactor, ethBlockchain, forwarderAddr, workflowOwnerID, workflowName) + + var feedIDs []string + for i := 0; i < feedCount; i++ { + feedIDs = append(feedIDs, newFeedID(t)) + } + + sink := &reportsSink{} + + libocr := newMockLibOCR(workflowDonInfo.F) + workflowDonNodes, _, _ := createDons(ctx, t, sink, + workflowDonInfo, triggerDonInfo, targetDonInfo, + ethBlockchain, capabilitiesRegistryAddr, forwarderAddr, + workflowDonInfo.keyBundles, transactor, libocr) + for _, node := range workflowDonNodes { + addWorkflowJob(t, node, workflowName, workflowOwnerID, feedIDs, consumerAddr, deltaStage, schedule) + } + + ethBlockchain.Start(ctx, 1*time.Second) + libocr.Start(ctx, t, 1*time.Second) + return consumer, feedIDs, sink +} + +func createDons(ctx context.Context, t *testing.T, reportsSink *reportsSink, + workflowDon donInfo, + triggerDon donInfo, + targetDon donInfo, + simulatedEthBlockchain *ethBackend, + capRegistryAddr common.Address, + forwarderAddr common.Address, + workflowNodeKeyBundles []ocr2key.KeyBundle, + transactor *bind.TransactOpts, + libocr *mockLibOCR, +) ([]*cltest.TestApplication, []*cltest.TestApplication, []*cltest.TestApplication) { + lggr := logger.TestLogger(t) + + broker := newTestAsyncMessageBroker(t, 1000) + + var triggerNodes []*cltest.TestApplication + for i, triggerPeer := range triggerDon.Members { + triggerPeerDispatcher := broker.NewDispatcherForNode(triggerPeer) + nodeInfo := commoncap.Node{ + PeerID: &triggerPeer, + } + + capabilityRegistry := capabilities.NewRegistry(lggr) + trigger := reportsSink.getNewTrigger(t) + err := capabilityRegistry.Add(ctx, trigger) + require.NoError(t, err) + + triggerNode := startNewNode(ctx, t, nodeInfo, simulatedEthBlockchain, capRegistryAddr, triggerPeerDispatcher, + testPeerWrapper{peer: testPeer{triggerPeer}}, capabilityRegistry, nil, transactor, + triggerDon.keys[i]) + + require.NoError(t, triggerNode.Start(testutils.Context(t))) + triggerNodes = append(triggerNodes, triggerNode) + } + + var targetNodes []*cltest.TestApplication + for i, targetPeer := range targetDon.Members { + targetPeerDispatcher := broker.NewDispatcherForNode(targetPeer) + nodeInfo := commoncap.Node{ + PeerID: &targetPeer, + } + + capabilityRegistry := capabilities.NewRegistry(lggr) + + targetNode := startNewNode(ctx, t, nodeInfo, simulatedEthBlockchain, capRegistryAddr, targetPeerDispatcher, + testPeerWrapper{peer: testPeer{targetPeer}}, capabilityRegistry, &forwarderAddr, transactor, + targetDon.keys[i]) + + require.NoError(t, targetNode.Start(testutils.Context(t))) + targetNodes = append(triggerNodes, targetNode) + } + + var workflowNodes []*cltest.TestApplication + for i, workflowPeer := range workflowDon.Members { + workflowPeerDispatcher := broker.NewDispatcherForNode(workflowPeer) + capabilityRegistry := capabilities.NewRegistry(lggr) + + requestTimeout := 10 * time.Minute + cfg := ocr3.Config{ + Logger: lggr, + EncoderFactory: evm.NewEVMEncoder, + AggregatorFactory: capabilities.NewAggregator, + RequestTimeout: &requestTimeout, + } + + ocr3Capability := ocr3.NewOCR3(cfg) + servicetest.Run(t, ocr3Capability) + + pluginCfg := coretypes.ReportingPluginServiceConfig{} + pluginFactory, err := ocr3Capability.NewReportingPluginFactory(ctx, pluginCfg, nil, + nil, nil, nil, capabilityRegistry, nil, nil) + require.NoError(t, err) + + repConfig := ocr3types.ReportingPluginConfig{ + F: int(workflowDon.F), + } + plugin, _, err := pluginFactory.NewReportingPlugin(repConfig) + require.NoError(t, err) + + transmitter := ocr3.NewContractTransmitter(lggr, capabilityRegistry, "") + + libocr.AddNode(plugin, transmitter, workflowNodeKeyBundles[i]) + + nodeInfo := commoncap.Node{ + PeerID: &workflowPeer, + WorkflowDON: workflowDon.DON, + CapabilityDONs: []commoncap.DON{triggerDon.DON, targetDon.DON}, + } + + workflowNode := startNewNode(ctx, t, nodeInfo, simulatedEthBlockchain, capRegistryAddr, workflowPeerDispatcher, + testPeerWrapper{peer: testPeer{workflowPeer}}, capabilityRegistry, nil, transactor, + workflowDon.keys[i]) + + require.NoError(t, workflowNode.Start(testutils.Context(t))) + workflowNodes = append(workflowNodes, workflowNode) + } + + servicetest.Run(t, broker) + + return workflowNodes, triggerNodes, targetNodes +} + +func startNewNode(ctx context.Context, + t *testing.T, nodeInfo commoncap.Node, + backend *ethBackend, capRegistryAddr common.Address, + dispatcher remotetypes.Dispatcher, + peerWrapper p2ptypes.PeerWrapper, + localCapabilities coretypes.CapabilitiesRegistry, + forwarderAddress *common.Address, + transactor *bind.TransactOpts, + keyV2 ethkey.KeyV2, +) *cltest.TestApplication { + config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.Capabilities.ExternalRegistry.ChainID = ptr(fmt.Sprintf("%d", testutils.SimulatedChainID)) + c.Capabilities.ExternalRegistry.Address = ptr(capRegistryAddr.String()) + c.Capabilities.Peering.V2.Enabled = ptr(true) + + c.Log.Level = ptr(toml.LogLevel(zapcore.WarnLevel)) + + if forwarderAddress != nil { + eip55Address := types.EIP55AddressFromAddress(*forwarderAddress) + c.EVM[0].Chain.Workflow.ForwarderAddress = &eip55Address + c.EVM[0].Chain.Workflow.FromAddress = &keyV2.EIP55Address + } + + c.Feature.FeedsManager = ptr(false) + }) + + n, err := backend.NonceAt(ctx, transactor.From, nil) + require.NoError(t, err) + + tx := cltest.NewLegacyTransaction( + n, keyV2.Address, + assets.Ether(1).ToInt(), + 21000, + assets.GWei(1).ToInt(), + nil) + signedTx, err := transactor.Signer(transactor.From, tx) + require.NoError(t, err) + err = backend.SendTransaction(ctx, signedTx) + require.NoError(t, err) + backend.Commit() + + return cltest.NewApplicationWithConfigV2AndKeyOnSimulatedBlockchain(t, config, backend.SimulatedBackend, nodeInfo, + dispatcher, peerWrapper, localCapabilities, keyV2) +} + +type don struct { + id uint32 + numNodes int + f uint8 +} + +func createDonInfo(t *testing.T, don don) donInfo { + keyBundles, peerIDs := getKeyBundlesAndPeerIDs(t, don.numNodes) + + donPeers := make([]p2ptypes.PeerID, len(peerIDs)) + var donKeys []ethkey.KeyV2 + for i := 0; i < len(peerIDs); i++ { + peerID := p2ptypes.PeerID{} + require.NoError(t, peerID.UnmarshalText([]byte(peerIDs[i].PeerID))) + donPeers[i] = peerID + newKey, err := ethkey.NewV2() + require.NoError(t, err) + donKeys = append(donKeys, newKey) + } + + triggerDonInfo := donInfo{ + DON: commoncap.DON{ + ID: don.id, + Members: donPeers, + F: don.f, + }, + peerIDs: peerIDs, + keys: donKeys, + keyBundles: keyBundles, + } + return triggerDonInfo +} + +func createFeedReport(t *testing.T, price *big.Int, observationTimestamp int64, + feedIDString string, + keyBundles []ocr2key.KeyBundle) *datastreams.FeedReport { + reportCtx := ocrTypes.ReportContext{} + rawCtx := RawReportContext(reportCtx) + + bytes, err := hex.DecodeString(feedIDString[2:]) + require.NoError(t, err) + var feedIDBytes [32]byte + copy(feedIDBytes[:], bytes) + + report := &datastreams.FeedReport{ + FeedID: feedIDString, + FullReport: newReport(t, feedIDBytes, price, observationTimestamp), + BenchmarkPrice: price.Bytes(), + ObservationTimestamp: observationTimestamp, + Signatures: [][]byte{}, + ReportContext: rawCtx, + } + + for _, key := range keyBundles { + sig, err := key.Sign(reportCtx, report.FullReport) + require.NoError(t, err) + report.Signatures = append(report.Signatures, sig) + } + + return report +} + +func getKeyBundlesAndPeerIDs(t *testing.T, numNodes int) ([]ocr2key.KeyBundle, []peer) { + var keyBundles []ocr2key.KeyBundle + var donPeerIDs []peer + for i := 0; i < numNodes; i++ { + peerID := NewPeerID() + + keyBundle, err := ocr2key.New(chaintype.EVM) + require.NoError(t, err) + keyBundles = append(keyBundles, keyBundle) + + pk := keyBundle.PublicKey() + + p := peer{ + PeerID: peerID, + Signer: fmt.Sprintf("0x%x", pk), + } + + donPeerIDs = append(donPeerIDs, p) + } + return keyBundles, donPeerIDs +} + +func newFeedID(t *testing.T) string { + buf := [32]byte{} + _, err := rand.Read(buf[:]) + require.NoError(t, err) + return "0x" + hex.EncodeToString(buf[:]) +} + +func newReport(t *testing.T, feedID [32]byte, price *big.Int, timestamp int64) []byte { + v3Codec := reportcodec.NewReportCodec(feedID, logger.TestLogger(t)) + raw, err := v3Codec.BuildReport(v3.ReportFields{ + BenchmarkPrice: price, + Timestamp: uint32(timestamp), + Bid: big.NewInt(0), + Ask: big.NewInt(0), + LinkFee: big.NewInt(0), + NativeFee: big.NewInt(0), + }) + require.NoError(t, err) + return raw +} + +type testPeerWrapper struct { + peer testPeer +} + +func (t testPeerWrapper) Start(ctx context.Context) error { + return nil +} + +func (t testPeerWrapper) Close() error { + return nil +} + +func (t testPeerWrapper) Ready() error { + return nil +} + +func (t testPeerWrapper) HealthReport() map[string]error { + return nil +} + +func (t testPeerWrapper) Name() string { + return "testPeerWrapper" +} + +func (t testPeerWrapper) GetPeer() p2ptypes.Peer { + return t.peer +} + +type testPeer struct { + id p2ptypes.PeerID +} + +func (t testPeer) Start(ctx context.Context) error { + return nil +} + +func (t testPeer) Close() error { + return nil +} + +func (t testPeer) Ready() error { + return nil +} + +func (t testPeer) HealthReport() map[string]error { + return nil +} + +func (t testPeer) Name() string { + return "testPeer" +} + +func (t testPeer) ID() p2ptypes.PeerID { + return t.id +} + +func (t testPeer) UpdateConnections(peers map[p2ptypes.PeerID]p2ptypes.StreamConfig) error { + return nil +} + +func (t testPeer) Send(peerID p2ptypes.PeerID, msg []byte) error { + return nil +} + +func (t testPeer) Receive() <-chan p2ptypes.Message { + return nil +} + +func NewPeerID() string { + var privKey [32]byte + _, err := rand.Read(privKey[:]) + if err != nil { + panic(err) + } + + peerID := append(libp2pMagic(), privKey[:]...) + + return base58.Encode(peerID[:]) +} + +func libp2pMagic() []byte { + return []byte{0x00, 0x24, 0x08, 0x01, 0x12, 0x20} +} + +func ptr[T any](t T) *T { return &t } + +func RawReportContext(reportCtx ocrTypes.ReportContext) []byte { + rc := evmutil.RawReportContext(reportCtx) + flat := []byte{} + for _, r := range rc { + flat = append(flat, r[:]...) + } + return flat +} diff --git a/core/capabilities/integration_tests/streams_test.go b/core/capabilities/integration_tests/streams_test.go new file mode 100644 index 00000000000..6216e36c856 --- /dev/null +++ b/core/capabilities/integration_tests/streams_test.go @@ -0,0 +1,99 @@ +package integration_tests + +import ( + "context" + "encoding/hex" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/datastreams" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/feeds_consumer" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + reporttypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/types" +) + +func Test_AllAtOnceTransmissionSchedule(t *testing.T) { + ctx := testutils.Context(t) + + // The don IDs set in the below calls are inferred from the order in which the dons are added to the capabilities registry + // in the setupCapabilitiesRegistryContract function, should this order change the don IDs will need updating. + workflowDonInfo := createDonInfo(t, don{id: 1, numNodes: 5, f: 1}) + triggerDonInfo := createDonInfo(t, don{id: 2, numNodes: 7, f: 1}) + targetDonInfo := createDonInfo(t, don{id: 3, numNodes: 4, f: 1}) + + consumer, feedIDs, triggerSink := setupStreamDonsWithTransmissionSchedule(ctx, t, workflowDonInfo, triggerDonInfo, targetDonInfo, 3, + "2s", "allAtOnce") + + reports := []*datastreams.FeedReport{ + createFeedReport(t, big.NewInt(1), 5, feedIDs[0], triggerDonInfo.keyBundles), + createFeedReport(t, big.NewInt(3), 7, feedIDs[1], triggerDonInfo.keyBundles), + createFeedReport(t, big.NewInt(2), 6, feedIDs[2], triggerDonInfo.keyBundles), + } + + triggerSink.sendReports(reports) + + waitForConsumerReports(ctx, t, consumer, reports) +} + +func Test_OneAtATimeTransmissionSchedule(t *testing.T) { + ctx := testutils.Context(t) + + // The don IDs set in the below calls are inferred from the order in which the dons are added to the capabilities registry + // in the setupCapabilitiesRegistryContract function, should this order change the don IDs will need updating. + workflowDonInfo := createDonInfo(t, don{id: 1, numNodes: 5, f: 1}) + triggerDonInfo := createDonInfo(t, don{id: 2, numNodes: 7, f: 1}) + targetDonInfo := createDonInfo(t, don{id: 3, numNodes: 4, f: 1}) + + consumer, feedIDs, triggerSink := setupStreamDonsWithTransmissionSchedule(ctx, t, workflowDonInfo, triggerDonInfo, targetDonInfo, 3, + "2s", "oneAtATime") + + reports := []*datastreams.FeedReport{ + createFeedReport(t, big.NewInt(1), 5, feedIDs[0], triggerDonInfo.keyBundles), + createFeedReport(t, big.NewInt(3), 7, feedIDs[1], triggerDonInfo.keyBundles), + createFeedReport(t, big.NewInt(2), 6, feedIDs[2], triggerDonInfo.keyBundles), + } + + triggerSink.sendReports(reports) + + waitForConsumerReports(ctx, t, consumer, reports) +} + +func waitForConsumerReports(ctx context.Context, t *testing.T, consumer *feeds_consumer.KeystoneFeedsConsumer, triggerFeedReports []*datastreams.FeedReport) { + feedsReceived := make(chan *feeds_consumer.KeystoneFeedsConsumerFeedReceived, 1000) + feedsSub, err := consumer.WatchFeedReceived(&bind.WatchOpts{}, feedsReceived, nil) + require.NoError(t, err) + + feedToReport := map[string]*datastreams.FeedReport{} + for _, report := range triggerFeedReports { + feedToReport[report.FeedID] = report + } + + ctxWithTimeout, cancel := context.WithTimeout(ctx, 5*time.Minute) + defer cancel() + feedCount := 0 + for { + select { + case <-ctxWithTimeout.Done(): + t.Fatalf("timed out waiting for feed reports, expected %d, received %d", len(triggerFeedReports), feedCount) + case err := <-feedsSub.Err(): + require.NoError(t, err) + case feed := <-feedsReceived: + feedID := "0x" + hex.EncodeToString(feed.FeedId[:]) + report := feedToReport[feedID] + decodedReport, err := reporttypes.Decode(report.FullReport) + require.NoError(t, err) + assert.Equal(t, decodedReport.BenchmarkPrice, feed.Price) + assert.Equal(t, decodedReport.ObservationsTimestamp, feed.Timestamp) + + feedCount++ + if feedCount == len(triggerFeedReports) { + return + } + } + } +} diff --git a/core/capabilities/integration_tests/workflow.go b/core/capabilities/integration_tests/workflow.go new file mode 100644 index 00000000000..d116a1ec639 --- /dev/null +++ b/core/capabilities/integration_tests/workflow.go @@ -0,0 +1,75 @@ +package integration_tests + +import ( + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/testdata/testspecs" +) + +const hardcodedWorkflow = ` +name: "%s" +owner: "0x%s" +triggers: + - id: "streams-trigger@1.0.0" + config: + feedIds: +%s + +consensus: + - id: "offchain_reporting@1.0.0" + ref: "evm_median" + inputs: + observations: + - "$(trigger.outputs)" + config: + report_id: "0001" + aggregation_method: "data_feeds" + aggregation_config: + feeds: +%s + encoder: "EVM" + encoder_config: + abi: "(bytes32 FeedID, uint224 Price, uint32 Timestamp)[] Reports" + +targets: + - id: "write_geth-testnet@1.0.0" + inputs: + signed_report: "$(evm_median.outputs)" + config: + address: "%s" + params: ["$(report)"] + abi: "receive(report bytes)" + deltaStage: %s + schedule: %s +` + +func addWorkflowJob(t *testing.T, app *cltest.TestApplication, + workflowName string, + workflowOwner string, + feedIDs []string, + consumerAddr common.Address, + deltaStage string, + schedule string) { + triggerFeedIDs := "" + for _, feedID := range feedIDs { + triggerFeedIDs += fmt.Sprintf(" - \"%s\"\n", feedID) + } + + aggregationFeeds := "" + for _, feedID := range feedIDs { + aggregationFeeds += fmt.Sprintf(" \"%s\":\n deviation: \"0.001\"\n heartbeat: 3600\n", feedID) + } + + workflowJobSpec := testspecs.GenerateWorkflowJobSpec(t, fmt.Sprintf(hardcodedWorkflow, workflowName, workflowOwner, triggerFeedIDs, aggregationFeeds, + consumerAddr.String(), deltaStage, schedule)) + job := workflowJobSpec.Job() + + err := app.AddJobV2(testutils.Context(t), &job) + require.NoError(t, err) +} diff --git a/core/capabilities/targets/write_target.go b/core/capabilities/targets/write_target.go index da2de59a8a4..330f15872d6 100644 --- a/core/capabilities/targets/write_target.go +++ b/core/capabilities/targets/write_target.go @@ -29,6 +29,7 @@ type WriteTarget struct { cw commontypes.ChainWriter forwarderAddress string capabilities.CapabilityInfo + lggr logger.Logger bound bool @@ -132,7 +133,7 @@ func (cap *WriteTarget) Execute(ctx context.Context, request capabilities.Capabi } var transmitter common.Address if err = cap.cr.GetLatestValue(ctx, "forwarder", "getTransmitter", primitives.Unconfirmed, queryInputs, &transmitter); err != nil { - return nil, err + return nil, fmt.Errorf("failed to getTransmitter latest value: %w", err) } if transmitter != common.HexToAddress("0x0") { cap.lggr.Infow("WriteTarget report already onchain - returning without a tranmission attempt", "executionID", request.Metadata.WorkflowExecutionID) diff --git a/core/cmd/shell_local_test.go b/core/cmd/shell_local_test.go index 5fbbff4260f..60545269e29 100644 --- a/core/cmd/shell_local_test.go +++ b/core/cmd/shell_local_test.go @@ -11,6 +11,7 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" + "github.com/smartcontractkit/chainlink/v2/core/capabilities" "github.com/smartcontractkit/chainlink/v2/common/client" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" @@ -44,8 +45,9 @@ import ( func genTestEVMRelayers(t *testing.T, opts legacyevm.ChainRelayExtenderConfig, ks evmrelayer.CSAETHKeystore) *chainlink.CoreRelayerChainInteroperators { f := chainlink.RelayerFactory{ - Logger: opts.Logger, - LoopRegistry: plugins.NewLoopRegistry(opts.Logger, opts.AppConfig.Tracing()), + Logger: opts.Logger, + LoopRegistry: plugins.NewLoopRegistry(opts.Logger, opts.AppConfig.Tracing()), + CapabilitiesRegistry: capabilities.NewRegistry(opts.Logger), } relayers, err := chainlink.NewCoreRelayerChainInteroperators(chainlink.InitEVM(testutils.Context(t), f, chainlink.EVMFactoryConfig{ diff --git a/core/gethwrappers/keystone/generated/feeds_consumer/feeds_consumer.go b/core/gethwrappers/keystone/generated/feeds_consumer/feeds_consumer.go new file mode 100644 index 00000000000..f4d52eedb9d --- /dev/null +++ b/core/gethwrappers/keystone/generated/feeds_consumer/feeds_consumer.go @@ -0,0 +1,732 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package feeds_consumer + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +var KeystoneFeedsConsumerMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"UnauthorizedSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes10\",\"name\":\"workflowName\",\"type\":\"bytes10\"}],\"name\":\"UnauthorizedWorkflowName\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"workflowOwner\",\"type\":\"address\"}],\"name\":\"UnauthorizedWorkflowOwner\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint224\",\"name\":\"price\",\"type\":\"uint224\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"timestamp\",\"type\":\"uint32\"}],\"name\":\"FeedReceived\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"feedId\",\"type\":\"bytes32\"}],\"name\":\"getPrice\",\"outputs\":[{\"internalType\":\"uint224\",\"name\":\"\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"metadata\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rawReport\",\"type\":\"bytes\"}],\"name\":\"onReport\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_allowedSendersList\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_allowedWorkflowOwnersList\",\"type\":\"address[]\"},{\"internalType\":\"bytes10[]\",\"name\":\"_allowedWorkflowNamesList\",\"type\":\"bytes10[]\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6111cf806101576000396000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c80638da5cb5b116100505780638da5cb5b1461014e578063e340171114610176578063f2fde38b1461018957600080fd5b806331d98b3f1461007757806379ba509714610131578063805f21321461013b575b600080fd5b6100f3610085366004610d64565b6000908152600260209081526040918290208251808401909352547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff81168084527c010000000000000000000000000000000000000000000000000000000090910463ffffffff169290910182905291565b604080517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff909316835263ffffffff9091166020830152015b60405180910390f35b61013961019c565b005b610139610149366004610dc6565b61029e565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610128565b610139610184366004610e77565b61061d565b610139610197366004610f11565b610a5d565b60015473ffffffffffffffffffffffffffffffffffffffff163314610222576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b3360009081526004602052604090205460ff166102e9576040517f3fcc3f17000000000000000000000000000000000000000000000000000000008152336004820152602401610219565b60008061032b86868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610a7192505050565b7fffffffffffffffffffff000000000000000000000000000000000000000000008216600090815260086020526040902054919350915060ff166103bf576040517f4b942f800000000000000000000000000000000000000000000000000000000081527fffffffffffffffffffff0000000000000000000000000000000000000000000083166004820152602401610219565b73ffffffffffffffffffffffffffffffffffffffff811660009081526006602052604090205460ff16610436576040517fbf24162300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610219565b600061044484860186610ff5565b905060005b815181101561061357604051806040016040528083838151811061046f5761046f611107565b6020026020010151602001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1681526020018383815181106104b0576104b0611107565b60200260200101516040015163ffffffff16815250600260008484815181106104db576104db611107565b602090810291909101810151518252818101929092526040016000208251929091015163ffffffff167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff909216919091179055815182908290811061055d5761055d611107565b6020026020010151600001517f2c30f5cb3caf4239d0f994ce539d7ef24817fa550169c388e3a110f02e40197d83838151811061059c5761059c611107565b6020026020010151602001518484815181106105ba576105ba611107565b6020026020010151604001516040516106039291907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216825263ffffffff16602082015260400190565b60405180910390a2600101610449565b5050505050505050565b610625610a87565b60005b60035463ffffffff821610156106c65760006004600060038463ffffffff168154811061065757610657611107565b60009182526020808320919091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556106bf81611136565b9050610628565b5060005b63ffffffff811686111561076e5760016004600089898563ffffffff168181106106f6576106f6611107565b905060200201602081019061070b9190610f11565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905561076781611136565b90506106ca565b5061077b60038787610bff565b5060005b60055463ffffffff8216101561081d5760006006600060058463ffffffff16815481106107ae576107ae611107565b60009182526020808320919091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905561081681611136565b905061077f565b5060005b63ffffffff81168411156108c55760016006600087878563ffffffff1681811061084d5761084d611107565b90506020020160208101906108629190610f11565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556108be81611136565b9050610821565b506108d260058585610bff565b5060005b60075463ffffffff821610156109935760006008600060078463ffffffff168154811061090557610905611107565b600091825260208083206003808404909101549206600a026101000a90910460b01b7fffffffffffffffffffff00000000000000000000000000000000000000000000168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905561098c81611136565b90506108d6565b5060005b63ffffffff8116821115610a475760016008600085858563ffffffff168181106109c3576109c3611107565b90506020020160208101906109d89190611180565b7fffffffffffffffffffff00000000000000000000000000000000000000000000168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055610a4081611136565b9050610997565b50610a5460078383610c87565b50505050505050565b610a65610a87565b610a6e81610b0a565b50565b6040810151604a90910151909160609190911c90565b60005473ffffffffffffffffffffffffffffffffffffffff163314610b08576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610219565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610b89576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610219565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b828054828255906000526020600020908101928215610c77579160200282015b82811115610c775781547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff843516178255602090920191600190910190610c1f565b50610c83929150610d4f565b5090565b82805482825590600052602060002090600201600390048101928215610c775791602002820160005b83821115610d1057833575ffffffffffffffffffffffffffffffffffffffffffff191683826101000a81548169ffffffffffffffffffff021916908360b01c02179055509260200192600a01602081600901049283019260010302610cb0565b8015610d465782816101000a81549069ffffffffffffffffffff0219169055600a01602081600901049283019260010302610d10565b5050610c839291505b5b80821115610c835760008155600101610d50565b600060208284031215610d7657600080fd5b5035919050565b60008083601f840112610d8f57600080fd5b50813567ffffffffffffffff811115610da757600080fd5b602083019150836020828501011115610dbf57600080fd5b9250929050565b60008060008060408587031215610ddc57600080fd5b843567ffffffffffffffff80821115610df457600080fd5b610e0088838901610d7d565b90965094506020870135915080821115610e1957600080fd5b50610e2687828801610d7d565b95989497509550505050565b60008083601f840112610e4457600080fd5b50813567ffffffffffffffff811115610e5c57600080fd5b6020830191508360208260051b8501011115610dbf57600080fd5b60008060008060008060608789031215610e9057600080fd5b863567ffffffffffffffff80821115610ea857600080fd5b610eb48a838b01610e32565b90985096506020890135915080821115610ecd57600080fd5b610ed98a838b01610e32565b90965094506040890135915080821115610ef257600080fd5b50610eff89828a01610e32565b979a9699509497509295939492505050565b600060208284031215610f2357600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610f4757600080fd5b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516060810167ffffffffffffffff81118282101715610fa057610fa0610f4e565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610fed57610fed610f4e565b604052919050565b6000602080838503121561100857600080fd5b823567ffffffffffffffff8082111561102057600080fd5b818501915085601f83011261103457600080fd5b81358181111561104657611046610f4e565b611054848260051b01610fa6565b8181528481019250606091820284018501918883111561107357600080fd5b938501935b828510156110fb5780858a0312156110905760008081fd5b611098610f7d565b85358152868601357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811681146110cb5760008081fd5b8188015260408681013563ffffffff811681146110e85760008081fd5b9082015284529384019392850192611078565b50979650505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600063ffffffff808316818103611176577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6001019392505050565b60006020828403121561119257600080fd5b81357fffffffffffffffffffff0000000000000000000000000000000000000000000081168114610f4757600080fdfea164736f6c6343000818000a", +} + +var KeystoneFeedsConsumerABI = KeystoneFeedsConsumerMetaData.ABI + +var KeystoneFeedsConsumerBin = KeystoneFeedsConsumerMetaData.Bin + +func DeployKeystoneFeedsConsumer(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *KeystoneFeedsConsumer, error) { + parsed, err := KeystoneFeedsConsumerMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(KeystoneFeedsConsumerBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &KeystoneFeedsConsumer{address: address, abi: *parsed, KeystoneFeedsConsumerCaller: KeystoneFeedsConsumerCaller{contract: contract}, KeystoneFeedsConsumerTransactor: KeystoneFeedsConsumerTransactor{contract: contract}, KeystoneFeedsConsumerFilterer: KeystoneFeedsConsumerFilterer{contract: contract}}, nil +} + +type KeystoneFeedsConsumer struct { + address common.Address + abi abi.ABI + KeystoneFeedsConsumerCaller + KeystoneFeedsConsumerTransactor + KeystoneFeedsConsumerFilterer +} + +type KeystoneFeedsConsumerCaller struct { + contract *bind.BoundContract +} + +type KeystoneFeedsConsumerTransactor struct { + contract *bind.BoundContract +} + +type KeystoneFeedsConsumerFilterer struct { + contract *bind.BoundContract +} + +type KeystoneFeedsConsumerSession struct { + Contract *KeystoneFeedsConsumer + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type KeystoneFeedsConsumerCallerSession struct { + Contract *KeystoneFeedsConsumerCaller + CallOpts bind.CallOpts +} + +type KeystoneFeedsConsumerTransactorSession struct { + Contract *KeystoneFeedsConsumerTransactor + TransactOpts bind.TransactOpts +} + +type KeystoneFeedsConsumerRaw struct { + Contract *KeystoneFeedsConsumer +} + +type KeystoneFeedsConsumerCallerRaw struct { + Contract *KeystoneFeedsConsumerCaller +} + +type KeystoneFeedsConsumerTransactorRaw struct { + Contract *KeystoneFeedsConsumerTransactor +} + +func NewKeystoneFeedsConsumer(address common.Address, backend bind.ContractBackend) (*KeystoneFeedsConsumer, error) { + abi, err := abi.JSON(strings.NewReader(KeystoneFeedsConsumerABI)) + if err != nil { + return nil, err + } + contract, err := bindKeystoneFeedsConsumer(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &KeystoneFeedsConsumer{address: address, abi: abi, KeystoneFeedsConsumerCaller: KeystoneFeedsConsumerCaller{contract: contract}, KeystoneFeedsConsumerTransactor: KeystoneFeedsConsumerTransactor{contract: contract}, KeystoneFeedsConsumerFilterer: KeystoneFeedsConsumerFilterer{contract: contract}}, nil +} + +func NewKeystoneFeedsConsumerCaller(address common.Address, caller bind.ContractCaller) (*KeystoneFeedsConsumerCaller, error) { + contract, err := bindKeystoneFeedsConsumer(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &KeystoneFeedsConsumerCaller{contract: contract}, nil +} + +func NewKeystoneFeedsConsumerTransactor(address common.Address, transactor bind.ContractTransactor) (*KeystoneFeedsConsumerTransactor, error) { + contract, err := bindKeystoneFeedsConsumer(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &KeystoneFeedsConsumerTransactor{contract: contract}, nil +} + +func NewKeystoneFeedsConsumerFilterer(address common.Address, filterer bind.ContractFilterer) (*KeystoneFeedsConsumerFilterer, error) { + contract, err := bindKeystoneFeedsConsumer(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &KeystoneFeedsConsumerFilterer{contract: contract}, nil +} + +func bindKeystoneFeedsConsumer(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := KeystoneFeedsConsumerMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _KeystoneFeedsConsumer.Contract.KeystoneFeedsConsumerCaller.contract.Call(opts, result, method, params...) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.KeystoneFeedsConsumerTransactor.contract.Transfer(opts) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.KeystoneFeedsConsumerTransactor.contract.Transact(opts, method, params...) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _KeystoneFeedsConsumer.Contract.contract.Call(opts, result, method, params...) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.contract.Transfer(opts) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.contract.Transact(opts, method, params...) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerCaller) GetPrice(opts *bind.CallOpts, feedId [32]byte) (*big.Int, uint32, error) { + var out []interface{} + err := _KeystoneFeedsConsumer.contract.Call(opts, &out, "getPrice", feedId) + + if err != nil { + return *new(*big.Int), *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + out1 := *abi.ConvertType(out[1], new(uint32)).(*uint32) + + return out0, out1, err + +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerSession) GetPrice(feedId [32]byte) (*big.Int, uint32, error) { + return _KeystoneFeedsConsumer.Contract.GetPrice(&_KeystoneFeedsConsumer.CallOpts, feedId) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerCallerSession) GetPrice(feedId [32]byte) (*big.Int, uint32, error) { + return _KeystoneFeedsConsumer.Contract.GetPrice(&_KeystoneFeedsConsumer.CallOpts, feedId) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _KeystoneFeedsConsumer.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerSession) Owner() (common.Address, error) { + return _KeystoneFeedsConsumer.Contract.Owner(&_KeystoneFeedsConsumer.CallOpts) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerCallerSession) Owner() (common.Address, error) { + return _KeystoneFeedsConsumer.Contract.Owner(&_KeystoneFeedsConsumer.CallOpts) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.contract.Transact(opts, "acceptOwnership") +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerSession) AcceptOwnership() (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.AcceptOwnership(&_KeystoneFeedsConsumer.TransactOpts) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.AcceptOwnership(&_KeystoneFeedsConsumer.TransactOpts) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactor) OnReport(opts *bind.TransactOpts, metadata []byte, rawReport []byte) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.contract.Transact(opts, "onReport", metadata, rawReport) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerSession) OnReport(metadata []byte, rawReport []byte) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.OnReport(&_KeystoneFeedsConsumer.TransactOpts, metadata, rawReport) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactorSession) OnReport(metadata []byte, rawReport []byte) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.OnReport(&_KeystoneFeedsConsumer.TransactOpts, metadata, rawReport) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactor) SetConfig(opts *bind.TransactOpts, _allowedSendersList []common.Address, _allowedWorkflowOwnersList []common.Address, _allowedWorkflowNamesList [][10]byte) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.contract.Transact(opts, "setConfig", _allowedSendersList, _allowedWorkflowOwnersList, _allowedWorkflowNamesList) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerSession) SetConfig(_allowedSendersList []common.Address, _allowedWorkflowOwnersList []common.Address, _allowedWorkflowNamesList [][10]byte) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.SetConfig(&_KeystoneFeedsConsumer.TransactOpts, _allowedSendersList, _allowedWorkflowOwnersList, _allowedWorkflowNamesList) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactorSession) SetConfig(_allowedSendersList []common.Address, _allowedWorkflowOwnersList []common.Address, _allowedWorkflowNamesList [][10]byte) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.SetConfig(&_KeystoneFeedsConsumer.TransactOpts, _allowedSendersList, _allowedWorkflowOwnersList, _allowedWorkflowNamesList) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.contract.Transact(opts, "transferOwnership", to) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.TransferOwnership(&_KeystoneFeedsConsumer.TransactOpts, to) +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerTransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _KeystoneFeedsConsumer.Contract.TransferOwnership(&_KeystoneFeedsConsumer.TransactOpts, to) +} + +type KeystoneFeedsConsumerFeedReceivedIterator struct { + Event *KeystoneFeedsConsumerFeedReceived + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeystoneFeedsConsumerFeedReceivedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeystoneFeedsConsumerFeedReceived) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeystoneFeedsConsumerFeedReceived) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeystoneFeedsConsumerFeedReceivedIterator) Error() error { + return it.fail +} + +func (it *KeystoneFeedsConsumerFeedReceivedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeystoneFeedsConsumerFeedReceived struct { + FeedId [32]byte + Price *big.Int + Timestamp uint32 + Raw types.Log +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) FilterFeedReceived(opts *bind.FilterOpts, feedId [][32]byte) (*KeystoneFeedsConsumerFeedReceivedIterator, error) { + + var feedIdRule []interface{} + for _, feedIdItem := range feedId { + feedIdRule = append(feedIdRule, feedIdItem) + } + + logs, sub, err := _KeystoneFeedsConsumer.contract.FilterLogs(opts, "FeedReceived", feedIdRule) + if err != nil { + return nil, err + } + return &KeystoneFeedsConsumerFeedReceivedIterator{contract: _KeystoneFeedsConsumer.contract, event: "FeedReceived", logs: logs, sub: sub}, nil +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) WatchFeedReceived(opts *bind.WatchOpts, sink chan<- *KeystoneFeedsConsumerFeedReceived, feedId [][32]byte) (event.Subscription, error) { + + var feedIdRule []interface{} + for _, feedIdItem := range feedId { + feedIdRule = append(feedIdRule, feedIdItem) + } + + logs, sub, err := _KeystoneFeedsConsumer.contract.WatchLogs(opts, "FeedReceived", feedIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeystoneFeedsConsumerFeedReceived) + if err := _KeystoneFeedsConsumer.contract.UnpackLog(event, "FeedReceived", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) ParseFeedReceived(log types.Log) (*KeystoneFeedsConsumerFeedReceived, error) { + event := new(KeystoneFeedsConsumerFeedReceived) + if err := _KeystoneFeedsConsumer.contract.UnpackLog(event, "FeedReceived", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeystoneFeedsConsumerOwnershipTransferRequestedIterator struct { + Event *KeystoneFeedsConsumerOwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeystoneFeedsConsumerOwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeystoneFeedsConsumerOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeystoneFeedsConsumerOwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeystoneFeedsConsumerOwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *KeystoneFeedsConsumerOwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeystoneFeedsConsumerOwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeystoneFeedsConsumerOwnershipTransferRequestedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeystoneFeedsConsumer.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &KeystoneFeedsConsumerOwnershipTransferRequestedIterator{contract: _KeystoneFeedsConsumer.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *KeystoneFeedsConsumerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeystoneFeedsConsumer.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeystoneFeedsConsumerOwnershipTransferRequested) + if err := _KeystoneFeedsConsumer.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) ParseOwnershipTransferRequested(log types.Log) (*KeystoneFeedsConsumerOwnershipTransferRequested, error) { + event := new(KeystoneFeedsConsumerOwnershipTransferRequested) + if err := _KeystoneFeedsConsumer.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type KeystoneFeedsConsumerOwnershipTransferredIterator struct { + Event *KeystoneFeedsConsumerOwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *KeystoneFeedsConsumerOwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(KeystoneFeedsConsumerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(KeystoneFeedsConsumerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *KeystoneFeedsConsumerOwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *KeystoneFeedsConsumerOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type KeystoneFeedsConsumerOwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeystoneFeedsConsumerOwnershipTransferredIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeystoneFeedsConsumer.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &KeystoneFeedsConsumerOwnershipTransferredIterator{contract: _KeystoneFeedsConsumer.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *KeystoneFeedsConsumerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _KeystoneFeedsConsumer.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(KeystoneFeedsConsumerOwnershipTransferred) + if err := _KeystoneFeedsConsumer.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumerFilterer) ParseOwnershipTransferred(log types.Log) (*KeystoneFeedsConsumerOwnershipTransferred, error) { + event := new(KeystoneFeedsConsumerOwnershipTransferred) + if err := _KeystoneFeedsConsumer.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumer) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _KeystoneFeedsConsumer.abi.Events["FeedReceived"].ID: + return _KeystoneFeedsConsumer.ParseFeedReceived(log) + case _KeystoneFeedsConsumer.abi.Events["OwnershipTransferRequested"].ID: + return _KeystoneFeedsConsumer.ParseOwnershipTransferRequested(log) + case _KeystoneFeedsConsumer.abi.Events["OwnershipTransferred"].ID: + return _KeystoneFeedsConsumer.ParseOwnershipTransferred(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (KeystoneFeedsConsumerFeedReceived) Topic() common.Hash { + return common.HexToHash("0x2c30f5cb3caf4239d0f994ce539d7ef24817fa550169c388e3a110f02e40197d") +} + +func (KeystoneFeedsConsumerOwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (KeystoneFeedsConsumerOwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (_KeystoneFeedsConsumer *KeystoneFeedsConsumer) Address() common.Address { + return _KeystoneFeedsConsumer.address +} + +type KeystoneFeedsConsumerInterface interface { + GetPrice(opts *bind.CallOpts, feedId [32]byte) (*big.Int, uint32, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + OnReport(opts *bind.TransactOpts, metadata []byte, rawReport []byte) (*types.Transaction, error) + + SetConfig(opts *bind.TransactOpts, _allowedSendersList []common.Address, _allowedWorkflowOwnersList []common.Address, _allowedWorkflowNamesList [][10]byte) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + FilterFeedReceived(opts *bind.FilterOpts, feedId [][32]byte) (*KeystoneFeedsConsumerFeedReceivedIterator, error) + + WatchFeedReceived(opts *bind.WatchOpts, sink chan<- *KeystoneFeedsConsumerFeedReceived, feedId [][32]byte) (event.Subscription, error) + + ParseFeedReceived(log types.Log) (*KeystoneFeedsConsumerFeedReceived, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeystoneFeedsConsumerOwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *KeystoneFeedsConsumerOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*KeystoneFeedsConsumerOwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*KeystoneFeedsConsumerOwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *KeystoneFeedsConsumerOwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*KeystoneFeedsConsumerOwnershipTransferred, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 4051f950208..53b8a64daff 100644 --- a/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/keystone/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,4 +1,5 @@ GETH_VERSION: 1.13.8 capabilities_registry: ../../../contracts/solc/v0.8.24/CapabilitiesRegistry/CapabilitiesRegistry.abi ../../../contracts/solc/v0.8.24/CapabilitiesRegistry/CapabilitiesRegistry.bin 3a082f0307411f41c30db26e61d59adcd5b003141a5aa8fe79d7779619028e26 +feeds_consumer: ../../../contracts/solc/v0.8.24/KeystoneFeedsConsumer/KeystoneFeedsConsumer.abi ../../../contracts/solc/v0.8.24/KeystoneFeedsConsumer/KeystoneFeedsConsumer.bin f098e25df6afc100425fcad7f5107aec0844cc98315117e49da139a179d0eead forwarder: ../../../contracts/solc/v0.8.24/KeystoneForwarder/KeystoneForwarder.abi ../../../contracts/solc/v0.8.24/KeystoneForwarder/KeystoneForwarder.bin dc98a86a3775ead987b79d5b6079ee0e26f31c0626032bdd6508f986e2423227 ocr3_capability: ../../../contracts/solc/v0.8.24/OCR3Capability/OCR3Capability.abi ../../../contracts/solc/v0.8.24/OCR3Capability/OCR3Capability.bin 8bf0f53f222efce7143dea6134552eb26ea1eef845407b4475a0d79b7d7ba9f8 diff --git a/core/gethwrappers/keystone/go_generate.go b/core/gethwrappers/keystone/go_generate.go index 36b27852ad6..79f49264b39 100644 --- a/core/gethwrappers/keystone/go_generate.go +++ b/core/gethwrappers/keystone/go_generate.go @@ -7,3 +7,4 @@ package gethwrappers //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/KeystoneForwarder/KeystoneForwarder.abi ../../../contracts/solc/v0.8.24/KeystoneForwarder/KeystoneForwarder.bin KeystoneForwarder forwarder //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/OCR3Capability/OCR3Capability.abi ../../../contracts/solc/v0.8.24/OCR3Capability/OCR3Capability.bin OCR3Capability ocr3_capability //go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/CapabilitiesRegistry/CapabilitiesRegistry.abi ../../../contracts/solc/v0.8.24/CapabilitiesRegistry/CapabilitiesRegistry.bin CapabilitiesRegistry capabilities_registry +//go:generate go run ../generation/generate/wrap.go ../../../contracts/solc/v0.8.24/KeystoneFeedsConsumer/KeystoneFeedsConsumer.abi ../../../contracts/solc/v0.8.24/KeystoneFeedsConsumer/KeystoneFeedsConsumer.bin KeystoneFeedsConsumer feeds_consumer diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 508dde86a05..0e66290a2c3 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -37,6 +37,11 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting/types" + coretypes "github.com/smartcontractkit/chainlink-common/pkg/types/core" + "github.com/smartcontractkit/chainlink/v2/core/capabilities" + remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" + "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" @@ -316,6 +321,31 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn auditLogger = audit.NoopLogger } + var capabilitiesRegistry coretypes.CapabilitiesRegistry + capabilitiesRegistry = capabilities.NewRegistry(lggr) + for _, dep := range flagsAndDeps { + registry, _ := dep.(coretypes.CapabilitiesRegistry) + if registry != nil { + capabilitiesRegistry = registry + } + } + + var dispatcher remotetypes.Dispatcher + for _, dep := range flagsAndDeps { + dispatcher, _ = dep.(remotetypes.Dispatcher) + if dispatcher != nil { + break + } + } + + var peerWrapper p2ptypes.PeerWrapper + for _, dep := range flagsAndDeps { + peerWrapper, _ = dep.(p2ptypes.PeerWrapper) + if peerWrapper != nil { + break + } + } + url := cfg.Database().URL() db, err := pg.NewConnection(url.String(), cfg.Database().Dialect(), cfg.Database()) require.NoError(t, err) @@ -354,10 +384,11 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn }) relayerFactory := chainlink.RelayerFactory{ - Logger: lggr, - LoopRegistry: loopRegistry, - GRPCOpts: loop.GRPCOpts{}, - MercuryPool: mercuryPool, + Logger: lggr, + LoopRegistry: loopRegistry, + GRPCOpts: loop.GRPCOpts{}, + MercuryPool: mercuryPool, + CapabilitiesRegistry: capabilitiesRegistry, } evmOpts := chainlink.EVMFactoryConfig{ @@ -409,6 +440,7 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn } initOps = append(initOps, chainlink.InitStarknet(testCtx, relayerFactory, starkCfg)) } + relayChainInterops, err := chainlink.NewCoreRelayerChainInteroperators(initOps...) if err != nil { t.Fatal(err) @@ -429,7 +461,11 @@ func NewApplicationWithConfig(t testing.TB, cfg chainlink.GeneralConfig, flagsAn SecretGenerator: MockSecretGenerator{}, LoopRegistry: plugins.NewLoopRegistry(lggr, nil), MercuryPool: mercuryPool, + CapabilitiesRegistry: capabilitiesRegistry, + CapabilitiesDispatcher: dispatcher, + CapabilitiesPeerWrapper: peerWrapper, }) + require.NoError(t, err) app := appInstance.(*chainlink.ChainlinkApplication) ta := &TestApplication{ diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index fd263470a8d..8ecf44b6cfe 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -33,6 +33,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/build" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" + remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" evmutils "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" @@ -181,6 +182,8 @@ type ApplicationOpts struct { GRPCOpts loop.GRPCOpts MercuryPool wsrpc.Pool CapabilitiesRegistry coretypes.CapabilitiesRegistry + CapabilitiesDispatcher remotetypes.Dispatcher + CapabilitiesPeerWrapper p2ptypes.PeerWrapper } // NewApplication initializes a new store if one is not already @@ -200,21 +203,32 @@ func NewApplication(opts ApplicationOpts) (Application, error) { restrictedHTTPClient := opts.RestrictedHTTPClient unrestrictedHTTPClient := opts.UnrestrictedHTTPClient - if opts.CapabilitiesRegistry == nil { // for tests only, in prod Registry is always set at this point + if opts.CapabilitiesRegistry == nil { + // for tests only, in prod Registry should always be set at this point opts.CapabilitiesRegistry = capabilities.NewRegistry(globalLogger) } - var externalPeerWrapper p2ptypes.PeerWrapper var getLocalNode func(ctx context.Context) (pkgcapabilities.Node, error) + + var externalPeerWrapper p2ptypes.PeerWrapper + if cfg.Capabilities().Peering().Enabled() { - externalPeer := externalp2p.NewExternalPeerWrapper(keyStore.P2P(), cfg.Capabilities().Peering(), opts.DS, globalLogger) - signer := externalPeer - externalPeerWrapper = externalPeer + var dispatcher remotetypes.Dispatcher + if opts.CapabilitiesDispatcher == nil { + externalPeer := externalp2p.NewExternalPeerWrapper(keyStore.P2P(), cfg.Capabilities().Peering(), opts.DS, globalLogger) + signer := externalPeer + externalPeerWrapper = externalPeer + remoteDispatcher := remote.NewDispatcher(externalPeerWrapper, signer, opts.CapabilitiesRegistry, globalLogger) + srvcs = append(srvcs, remoteDispatcher) + + dispatcher = remoteDispatcher + } else { + dispatcher = opts.CapabilitiesDispatcher + externalPeerWrapper = opts.CapabilitiesPeerWrapper + } srvcs = append(srvcs, externalPeerWrapper) - dispatcher := remote.NewDispatcher(externalPeerWrapper, signer, opts.CapabilitiesRegistry, globalLogger) - rid := cfg.Capabilities().ExternalRegistry().RelayID() registryAddress := cfg.Capabilities().ExternalRegistry().Address() relayer, err := relayerChainInterops.Get(rid) @@ -240,7 +254,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { registrySyncer.AddLauncher(wfLauncher) getLocalNode = wfLauncher.LocalNode - srvcs = append(srvcs, dispatcher, wfLauncher, registrySyncer) + srvcs = append(srvcs, wfLauncher, registrySyncer) } // LOOPs can be created as options, in the case of LOOP relayers, or diff --git a/core/services/chainlink/relayer_chain_interoperators_test.go b/core/services/chainlink/relayer_chain_interoperators_test.go index aaf458c76c8..5aaf6e16dd4 100644 --- a/core/services/chainlink/relayer_chain_interoperators_test.go +++ b/core/services/chainlink/relayer_chain_interoperators_test.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" + "github.com/smartcontractkit/chainlink/v2/core/capabilities" coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" @@ -174,9 +175,10 @@ func TestCoreRelayerChainInteroperators(t *testing.T) { lggr := logger.TestLogger(t) factory := chainlink.RelayerFactory{ - Logger: lggr, - LoopRegistry: plugins.NewLoopRegistry(lggr, nil), - GRPCOpts: loop.GRPCOpts{}, + Logger: lggr, + LoopRegistry: plugins.NewLoopRegistry(lggr, nil), + GRPCOpts: loop.GRPCOpts{}, + CapabilitiesRegistry: capabilities.NewRegistry(lggr), } testctx := testutils.Context(t) diff --git a/core/services/chainlink/relayer_factory.go b/core/services/chainlink/relayer_factory.go index ae222f56c61..849964f9bec 100644 --- a/core/services/chainlink/relayer_factory.go +++ b/core/services/chainlink/relayer_factory.go @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/relay" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/types" + coretypes "github.com/smartcontractkit/chainlink-common/pkg/types/core" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" coscfg "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana" @@ -18,8 +19,6 @@ import ( pkgstarknet "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink" starkchain "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/chain" "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - - "github.com/smartcontractkit/chainlink/v2/core/capabilities" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" coreconfig "github.com/smartcontractkit/chainlink/v2/core/config" "github.com/smartcontractkit/chainlink/v2/core/config/env" @@ -37,7 +36,7 @@ type RelayerFactory struct { *plugins.LoopRegistry loop.GRPCOpts MercuryPool wsrpc.Pool - CapabilitiesRegistry *capabilities.Registry + CapabilitiesRegistry coretypes.CapabilitiesRegistry } type DummyFactoryConfig struct {