diff --git a/.changeset/beige-socks-cover.md b/.changeset/beige-socks-cover.md new file mode 100644 index 00000000000..0b7c22d01e5 --- /dev/null +++ b/.changeset/beige-socks-cover.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +#internal changes to core required by change BCF3168 in common to add relayer set diff --git a/.changeset/curvy-weeks-cover.md b/.changeset/curvy-weeks-cover.md new file mode 100644 index 00000000000..0b19df8ad16 --- /dev/null +++ b/.changeset/curvy-weeks-cover.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#wip Keystone contract wrappers updated diff --git a/.changeset/early-paws-end.md b/.changeset/early-paws-end.md new file mode 100644 index 00000000000..1a3edb5083f --- /dev/null +++ b/.changeset/early-paws-end.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +generate gethwrappers for updating node operators in capability registry #internal diff --git a/.changeset/fast-students-accept.md b/.changeset/fast-students-accept.md new file mode 100644 index 00000000000..8813f3a7812 --- /dev/null +++ b/.changeset/fast-students-accept.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#internal Optimize workflow engine tests diff --git a/.changeset/fresh-rice-learn.md b/.changeset/fresh-rice-learn.md new file mode 100644 index 00000000000..6425cdd4581 --- /dev/null +++ b/.changeset/fresh-rice-learn.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Moved test functions under evm package to support evm extraction #internal diff --git a/.changeset/proud-toys-travel.md b/.changeset/proud-toys-travel.md new file mode 100644 index 00000000000..e2b1f0c7269 --- /dev/null +++ b/.changeset/proud-toys-travel.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Improving LogPoller read queries by properly sorting by multiple columns #updated diff --git a/.changeset/quick-fishes-heal.md b/.changeset/quick-fishes-heal.md new file mode 100644 index 00000000000..966e74c843a --- /dev/null +++ b/.changeset/quick-fishes-heal.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- +#changed +Added prefix `RPCClient returned error ({RPC_NAME})` to RPC errors to simplify filtering of RPC related issues. diff --git a/.changeset/tidy-trees-tie.md b/.changeset/tidy-trees-tie.md new file mode 100644 index 00000000000..7ff415e9de4 --- /dev/null +++ b/.changeset/tidy-trees-tie.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#changed Updating the log trigger log provider's readMaxBatchSize to 56 diff --git a/.changeset/witty-numbers-sleep.md b/.changeset/witty-numbers-sleep.md new file mode 100644 index 00000000000..d42664d9f76 --- /dev/null +++ b/.changeset/witty-numbers-sleep.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Support for retention in LogPoller's filters registered by ContractTransmitter #changed diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 394faf71e2f..e1891770930 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -77,6 +77,10 @@ jobs: slack-message: "golangci-lint failed: ${{ job.html_url }}\n${{ format('https://github.com/smartcontractkit/chainlink/actions/runs/{0}', github.run_id) }}" core: + env: + # We explicitly have this env var not be "CL_DATABASE_URL" to avoid having it be used by core related tests + # when they should not be using it, while still allowing us to DRY up the setup + DB_URL: postgresql://postgres:postgres@localhost:5432/chainlink_test?sslmode=disable strategy: fail-fast: false matrix: @@ -92,8 +96,6 @@ jobs: if: github.actor != 'dependabot[bot]' needs: [filter] runs-on: ubuntu-latest-64cores-256GB - env: - CL_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/chainlink_test?sslmode=disable steps: - name: Checkout the repo uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 @@ -136,6 +138,8 @@ jobs: - name: Setup DB if: ${{ needs.filter.outputs.changes == 'true' }} run: ./chainlink.test local db preparetest + env: + CL_DATABASE_URL: ${{ env.DB_URL }} - name: Install LOOP Plugins if: ${{ needs.filter.outputs.changes == 'true' }} run: | @@ -165,6 +169,7 @@ jobs: env: OUTPUT_FILE: ./output.txt USE_TEE: false + CL_DATABASE_URL: ${{ env.DB_URL }} run: ./tools/bin/${{ matrix.type.cmd }} ./... - name: Print Filtered Test Results if: ${{ failure() && matrix.type.cmd == 'go_core_tests' && needs.filter.outputs.changes == 'true' }} diff --git a/.github/workflows/cicd-changesets.yml b/.github/workflows/cicd-changesets.yml index 0bfec7b22f2..96363588319 100644 --- a/.github/workflows/cicd-changesets.yml +++ b/.github/workflows/cicd-changesets.yml @@ -16,6 +16,7 @@ jobs: permissions: id-token: write contents: read + actions: read steps: - name: Checkout repository uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 @@ -28,6 +29,31 @@ jobs: core-changeset: - added: '.changeset/**' + - name: Setup pnpm + uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3.0.0 + if: steps.changeset-added.outputs.core-changeset == 'true' + with: + version: ^8.0.0 + + - name: Setup node + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + if: steps.changeset-added.outputs.core-changeset == 'true' + with: + node-version: 20 + cache: pnpm + cache-dependency-path: ./pnpm-lock.yaml + + - name: Run changeset version + run: pnpm install && pnpm changeset version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: steps.changeset-added.outputs.core-changeset == 'true' + + - name: Get release version + if: steps.changeset-added.outputs.core-changeset == 'true' + id: get-release-version + run: echo "version=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT + - name: cicd-changesets if: steps.changeset-added.outputs.core-changeset == 'true' uses: smartcontractkit/.github/actions/cicd-changesets@6da79c7b9f14bec077df2c1ad40d53823b409d9c # cicd-changesets@0.3.3 @@ -37,7 +63,7 @@ jobs: git-email: app-token-issuer-releng[bot]@users.noreply.github.com pnpm-use-cache: false pr-draft: true - pr-title: "[DO NOT MERGE] Release Preview - Changeset" + pr-title: "[DO NOT MERGE] Changeset Release Preview - v${{ steps.get-release-version.outputs.version }}" # aws inputs aws-region: ${{ secrets.AWS_REGION }} aws-role-arn: ${{ secrets.AWS_OIDC_CHAINLINK_CI_AUTO_PR_TOKEN_ISSUER_ROLE_ARN }} diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index a70f1961e36..00000000000 --- a/codecov.yml +++ /dev/null @@ -1,15 +0,0 @@ -comment: false - -coverage: - status: - project: - default: - threshold: 1% - -github_checks: - annotations: false - -ignore: - - 'contracts/' # Disabled due to solidity-coverage not reporting coverage - - 'core/internal' - - 'core/scripts' diff --git a/common/client/models.go b/common/client/models.go index 66f1e9cf88b..fd0c3915940 100644 --- a/common/client/models.go +++ b/common/client/models.go @@ -28,6 +28,35 @@ var sendTxSevereErrors = []SendTxReturnCode{Fatal, Underpriced, Unsupported, Exc // sendTxSuccessfulCodes - error codes which signal that transaction was accepted by the node var sendTxSuccessfulCodes = []SendTxReturnCode{Successful, TransactionAlreadyKnown} +func (c SendTxReturnCode) String() string { + switch c { + case Successful: + return "Successful" + case Fatal: + return "Fatal" + case Retryable: + return "Retryable" + case Underpriced: + return "Underpriced" + case Unknown: + return "Unknown" + case Unsupported: + return "Unsupported" + case TransactionAlreadyKnown: + return "TransactionAlreadyKnown" + case InsufficientFunds: + return "InsufficientFunds" + case ExceedsMaxFee: + return "ExceedsMaxFee" + case FeeOutOfValidRange: + return "FeeOutOfValidRange" + case OutOfCounters: + return "OutOfCounters" + default: + return fmt.Sprintf("SendTxReturnCode(%d)", c) + } +} + type NodeTier int const ( diff --git a/common/client/models_test.go b/common/client/models_test.go new file mode 100644 index 00000000000..2d5dc31b373 --- /dev/null +++ b/common/client/models_test.go @@ -0,0 +1,16 @@ +package client + +import ( + "strings" + "testing" +) + +func TestSendTxReturnCode_String(t *testing.T) { + // ensure all the SendTxReturnCodes have proper name + for c := 1; c < int(sendTxReturnCodeLen); c++ { + strC := SendTxReturnCode(c).String() + if strings.Contains(strC, "SendTxReturnCode(") { + t.Errorf("Expected %s to have a proper string representation", strC) + } + } +} diff --git a/common/client/multi_node.go b/common/client/multi_node.go index cc8daed599c..fa413df91aa 100644 --- a/common/client/multi_node.go +++ b/common/client/multi_node.go @@ -561,6 +561,13 @@ func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OP return n.RPC().PendingSequenceAt(ctx, addr) } +type sendTxErrors map[SendTxReturnCode][]error + +// String - returns string representation of the errors map. Required by logger to properly represent the value +func (errs sendTxErrors) String() string { + return fmt.Sprint(map[SendTxReturnCode][]error(errs)) +} + func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT, BATCH_ELEM]) SendEmptyTransaction( ctx context.Context, newTxAttempt func(seq SEQ, feeLimit uint32, fee FEE, fromAddress ADDR) (attempt any, err error), @@ -602,7 +609,7 @@ func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OP ctx, cancel := c.chStop.Ctx(ctx) defer cancel() requiredResults := int(math.Ceil(float64(healthyNodesNum) * sendTxQuorum)) - errorsByCode := map[SendTxReturnCode][]error{} + errorsByCode := sendTxErrors{} var softTimeoutChan <-chan time.Time var resultsCount int loop: @@ -639,7 +646,7 @@ loop: func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OPS, TX_RECEIPT, FEE, HEAD, RPC_CLIENT, BATCH_ELEM]) reportSendTxAnomalies(tx TX, txResults <-chan sendTxResult) { defer c.wg.Done() - resultsByCode := map[SendTxReturnCode][]error{} + resultsByCode := sendTxErrors{} // txResults eventually will be closed for txResult := range txResults { resultsByCode[txResult.ResultCode] = append(resultsByCode[txResult.ResultCode], txResult.Err) @@ -653,7 +660,7 @@ func (c *multiNode[CHAIN_ID, SEQ, ADDR, BLOCK_HASH, TX, TX_HASH, EVENT, EVENT_OP } } -func aggregateTxResults(resultsByCode map[SendTxReturnCode][]error) (txResult error, err error) { +func aggregateTxResults(resultsByCode sendTxErrors) (txResult error, err error) { severeErrors, hasSevereErrors := findFirstIn(resultsByCode, sendTxSevereErrors) successResults, hasSuccess := findFirstIn(resultsByCode, sendTxSuccessfulCodes) if hasSuccess { diff --git a/common/client/multi_node_test.go b/common/client/multi_node_test.go index 9c09bd57d70..9f6904fcaf2 100644 --- a/common/client/multi_node_test.go +++ b/common/client/multi_node_test.go @@ -796,13 +796,13 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { Name string ExpectedTxResult string ExpectedCriticalErr string - ResultsByCode map[SendTxReturnCode][]error + ResultsByCode sendTxErrors }{ { Name: "Returns success and logs critical error on success and Fatal", ExpectedTxResult: "success", ExpectedCriticalErr: "found contradictions in nodes replies on SendTransaction: got success and severe error", - ResultsByCode: map[SendTxReturnCode][]error{ + ResultsByCode: sendTxErrors{ Successful: {errors.New("success")}, Fatal: {errors.New("fatal")}, }, @@ -811,7 +811,7 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { Name: "Returns TransactionAlreadyKnown and logs critical error on TransactionAlreadyKnown and Fatal", ExpectedTxResult: "tx_already_known", ExpectedCriticalErr: "found contradictions in nodes replies on SendTransaction: got success and severe error", - ResultsByCode: map[SendTxReturnCode][]error{ + ResultsByCode: sendTxErrors{ TransactionAlreadyKnown: {errors.New("tx_already_known")}, Unsupported: {errors.New("unsupported")}, }, @@ -820,7 +820,7 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { Name: "Prefers sever error to temporary", ExpectedTxResult: "underpriced", ExpectedCriticalErr: "", - ResultsByCode: map[SendTxReturnCode][]error{ + ResultsByCode: sendTxErrors{ Retryable: {errors.New("retryable")}, Underpriced: {errors.New("underpriced")}, }, @@ -829,7 +829,7 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { Name: "Returns temporary error", ExpectedTxResult: "retryable", ExpectedCriticalErr: "", - ResultsByCode: map[SendTxReturnCode][]error{ + ResultsByCode: sendTxErrors{ Retryable: {errors.New("retryable")}, }, }, @@ -837,7 +837,7 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { Name: "Insufficient funds is treated as error", ExpectedTxResult: "", ExpectedCriticalErr: "", - ResultsByCode: map[SendTxReturnCode][]error{ + ResultsByCode: sendTxErrors{ Successful: {nil}, InsufficientFunds: {errors.New("insufficientFunds")}, }, @@ -846,13 +846,13 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { Name: "Logs critical error on empty ResultsByCode", ExpectedTxResult: "expected at least one response on SendTransaction", ExpectedCriticalErr: "expected at least one response on SendTransaction", - ResultsByCode: map[SendTxReturnCode][]error{}, + ResultsByCode: sendTxErrors{}, }, { Name: "Zk out of counter error", ExpectedTxResult: "not enough keccak counters to continue the execution", ExpectedCriticalErr: "", - ResultsByCode: map[SendTxReturnCode][]error{ + ResultsByCode: sendTxErrors{ OutOfCounters: {errors.New("not enough keccak counters to continue the execution")}, }, }, @@ -870,6 +870,9 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { assert.EqualError(t, txResult, testCase.ExpectedTxResult) } + logger.Sugared(logger.Test(t)).Info("Map: " + fmt.Sprint(testCase.ResultsByCode)) + logger.Sugared(logger.Test(t)).Criticalw("observed invariant violation on SendTransaction", "resultsByCode", testCase.ResultsByCode, "err", err) + if testCase.ExpectedCriticalErr == "" { assert.NoError(t, err) } else { @@ -884,5 +887,4 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { delete(codesToCover, codeToIgnore) } assert.Empty(t, codesToCover, "all of the SendTxReturnCode must be covered by this test") - } diff --git a/common/client/poller.go b/common/client/poller.go new file mode 100644 index 00000000000..b21f28fe604 --- /dev/null +++ b/common/client/poller.go @@ -0,0 +1,98 @@ +package client + +import ( + "context" + "sync" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + + "github.com/smartcontractkit/chainlink/v2/common/types" +) + +// Poller is a component that polls a function at a given interval +// and delivers the result to a channel. It is used by multinode to poll +// for new heads and implements the Subscription interface. +type Poller[T any] struct { + services.StateMachine + pollingInterval time.Duration + pollingFunc func(ctx context.Context) (T, error) + pollingTimeout time.Duration + logger logger.Logger + channel chan<- T + errCh chan error + + stopCh services.StopChan + wg sync.WaitGroup +} + +// NewPoller creates a new Poller instance +func NewPoller[ + T any, +](pollingInterval time.Duration, pollingFunc func(ctx context.Context) (T, error), pollingTimeout time.Duration, channel chan<- T, logger logger.Logger) Poller[T] { + return Poller[T]{ + pollingInterval: pollingInterval, + pollingFunc: pollingFunc, + pollingTimeout: pollingTimeout, + channel: channel, + logger: logger, + errCh: make(chan error), + stopCh: make(chan struct{}), + } +} + +var _ types.Subscription = &Poller[any]{} + +func (p *Poller[T]) Start() error { + return p.StartOnce("Poller", func() error { + p.wg.Add(1) + go p.pollingLoop() + return nil + }) +} + +// Unsubscribe cancels the sending of events to the data channel +func (p *Poller[T]) Unsubscribe() { + _ = p.StopOnce("Poller", func() error { + close(p.stopCh) + p.wg.Wait() + close(p.errCh) + return nil + }) +} + +func (p *Poller[T]) Err() <-chan error { + return p.errCh +} + +func (p *Poller[T]) pollingLoop() { + defer p.wg.Done() + + ticker := time.NewTicker(p.pollingInterval) + defer ticker.Stop() + + for { + select { + case <-p.stopCh: + return + case <-ticker.C: + // Set polling timeout + pollingCtx, cancelPolling := context.WithTimeout(context.Background(), p.pollingTimeout) + p.stopCh.CtxCancel(pollingCtx, cancelPolling) + // Execute polling function + result, err := p.pollingFunc(pollingCtx) + cancelPolling() + if err != nil { + p.logger.Warnf("polling error: %v", err) + continue + } + // Send result to channel or block if channel is full + select { + case p.channel <- result: + case <-p.stopCh: + return + } + } + } +} diff --git a/common/client/poller_test.go b/common/client/poller_test.go new file mode 100644 index 00000000000..3f11c759adb --- /dev/null +++ b/common/client/poller_test.go @@ -0,0 +1,207 @@ +package client + +import ( + "context" + "fmt" + "math/big" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" +) + +func Test_Poller(t *testing.T) { + lggr := logger.Test(t) + + t.Run("Test multiple start", func(t *testing.T) { + pollFunc := func(ctx context.Context) (Head, error) { + return nil, nil + } + + channel := make(chan Head, 1) + defer close(channel) + + poller := NewPoller[Head](time.Millisecond, pollFunc, time.Second, channel, lggr) + err := poller.Start() + require.NoError(t, err) + + err = poller.Start() + require.Error(t, err) + poller.Unsubscribe() + }) + + t.Run("Test polling for heads", func(t *testing.T) { + // Mock polling function that returns a new value every time it's called + var pollNumber int + pollLock := sync.Mutex{} + pollFunc := func(ctx context.Context) (Head, error) { + pollLock.Lock() + defer pollLock.Unlock() + pollNumber++ + h := head{ + BlockNumber: int64(pollNumber), + BlockDifficulty: big.NewInt(int64(pollNumber)), + } + return h.ToMockHead(t), nil + } + + // data channel to receive updates from the poller + channel := make(chan Head, 1) + defer close(channel) + + // Create poller and start to receive data + poller := NewPoller[Head](time.Millisecond, pollFunc, time.Second, channel, lggr) + require.NoError(t, poller.Start()) + defer poller.Unsubscribe() + + // Receive updates from the poller + pollCount := 0 + pollMax := 50 + for ; pollCount < pollMax; pollCount++ { + h := <-channel + assert.Equal(t, int64(pollCount+1), h.BlockNumber()) + } + }) + + t.Run("Test polling errors", func(t *testing.T) { + // Mock polling function that returns an error + var pollNumber int + pollLock := sync.Mutex{} + pollFunc := func(ctx context.Context) (Head, error) { + pollLock.Lock() + defer pollLock.Unlock() + pollNumber++ + return nil, fmt.Errorf("polling error %d", pollNumber) + } + + // data channel to receive updates from the poller + channel := make(chan Head, 1) + defer close(channel) + + olggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + + // Create poller and subscribe to receive data + poller := NewPoller[Head](time.Millisecond, pollFunc, time.Second, channel, olggr) + require.NoError(t, poller.Start()) + defer poller.Unsubscribe() + + // Ensure that all errors were logged as expected + logsSeen := func() bool { + for pollCount := 0; pollCount < 50; pollCount++ { + numLogs := observedLogs.FilterMessage(fmt.Sprintf("polling error: polling error %d", pollCount+1)).Len() + if numLogs != 1 { + return false + } + } + return true + } + require.Eventually(t, logsSeen, time.Second, time.Millisecond) + }) + + t.Run("Test polling timeout", func(t *testing.T) { + pollFunc := func(ctx context.Context) (Head, error) { + if <-ctx.Done(); true { + return nil, ctx.Err() + } + return nil, nil + } + + // Set instant timeout + pollingTimeout := time.Duration(0) + + // data channel to receive updates from the poller + channel := make(chan Head, 1) + defer close(channel) + + olggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + + // Create poller and subscribe to receive data + poller := NewPoller[Head](time.Millisecond, pollFunc, pollingTimeout, channel, olggr) + require.NoError(t, poller.Start()) + defer poller.Unsubscribe() + + // Ensure that timeout errors were logged as expected + logsSeen := func() bool { + return observedLogs.FilterMessage("polling error: context deadline exceeded").Len() >= 1 + } + require.Eventually(t, logsSeen, time.Second, time.Millisecond) + }) + + t.Run("Test unsubscribe during polling", func(t *testing.T) { + wait := make(chan struct{}) + pollFunc := func(ctx context.Context) (Head, error) { + close(wait) + // Block in polling function until context is cancelled + if <-ctx.Done(); true { + return nil, ctx.Err() + } + return nil, nil + } + + // Set long timeout + pollingTimeout := time.Minute + + // data channel to receive updates from the poller + channel := make(chan Head, 1) + defer close(channel) + + olggr, observedLogs := logger.TestObserved(t, zap.WarnLevel) + + // Create poller and subscribe to receive data + poller := NewPoller[Head](time.Millisecond, pollFunc, pollingTimeout, channel, olggr) + require.NoError(t, poller.Start()) + + // Unsubscribe while blocked in polling function + <-wait + poller.Unsubscribe() + + // Ensure error was logged + logsSeen := func() bool { + return observedLogs.FilterMessage("polling error: context canceled").Len() >= 1 + } + require.Eventually(t, logsSeen, time.Second, time.Millisecond) + }) +} + +func Test_Poller_Unsubscribe(t *testing.T) { + lggr := logger.Test(t) + pollFunc := func(ctx context.Context) (Head, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + h := head{ + BlockNumber: 0, + BlockDifficulty: big.NewInt(0), + } + return h.ToMockHead(t), nil + } + } + + t.Run("Test multiple unsubscribe", func(t *testing.T) { + channel := make(chan Head, 1) + poller := NewPoller[Head](time.Millisecond, pollFunc, time.Second, channel, lggr) + err := poller.Start() + require.NoError(t, err) + + <-channel + poller.Unsubscribe() + poller.Unsubscribe() + }) + + t.Run("Test unsubscribe with closed channel", func(t *testing.T) { + channel := make(chan Head, 1) + poller := NewPoller[Head](time.Millisecond, pollFunc, time.Second, channel, lggr) + err := poller.Start() + require.NoError(t, err) + + <-channel + close(channel) + poller.Unsubscribe() + }) +} diff --git a/contracts/.changeset/funny-eagles-know.md b/contracts/.changeset/funny-eagles-know.md new file mode 100644 index 00000000000..46827824ad8 --- /dev/null +++ b/contracts/.changeset/funny-eagles-know.md @@ -0,0 +1,5 @@ +--- +"@chainlink/contracts": patch +--- + +#wip addCapability udpates diff --git a/contracts/.changeset/lucky-bugs-buy.md b/contracts/.changeset/lucky-bugs-buy.md new file mode 100644 index 00000000000..98a17d74af9 --- /dev/null +++ b/contracts/.changeset/lucky-bugs-buy.md @@ -0,0 +1,5 @@ +--- +"@chainlink/contracts": patch +--- + +Add function to update node operator' diff --git a/contracts/CHANGELOG.md b/contracts/CHANGELOG.md index 667a5ad2529..3139312e325 100644 --- a/contracts/CHANGELOG.md +++ b/contracts/CHANGELOG.md @@ -1,6 +1,6 @@ -# @chainlink/contracts CHANGELOG.md +# @chainlink/contracts -## 1.1.0 +## 1.1.0 - 2024-04-23 ### Minor Changes diff --git a/contracts/README.md b/contracts/README.md index 8df69057229..26b0a823298 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -50,6 +50,22 @@ contribution information. Thank you! +### Changesets + +We use [changesets](https://github.com/changesets/changesets) to manage versioning the contracts. + +Every PR that modifies any configuration or code, should most likely accompanied by a changeset file. + +To install `changesets`: + 1. Install `pnpm` if it is not already installed - [docs](https://pnpm.io/installation). + 2. Run `pnpm install`. + +Either after or before you create a commit, run the `pnpm changeset` command in the `contracts` directory to create an accompanying changeset entry which will reflect on the CHANGELOG for the next release. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + ## License [MIT](https://choosealicense.com/licenses/mit/) diff --git a/contracts/src/v0.8/keystone/CapabilityRegistry.sol b/contracts/src/v0.8/keystone/CapabilityRegistry.sol index faedd858bef..ddf1d9b5635 100644 --- a/contracts/src/v0.8/keystone/CapabilityRegistry.sol +++ b/contracts/src/v0.8/keystone/CapabilityRegistry.sol @@ -3,8 +3,14 @@ pragma solidity ^0.8.0; import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol"; import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol"; +import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; +import {EnumerableSet} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; +import {ICapabilityConfiguration} from "./interfaces/ICapabilityConfiguration.sol"; contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { + // Add the library methods + using EnumerableSet for EnumerableSet.Bytes32Set; + struct NodeOperator { /// @notice The address of the admin that can manage a node /// operator @@ -13,6 +19,16 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { string name; } + // CapabilityResponseType indicates whether remote response requires + // aggregation or is an already aggregated report. There are multiple + // possible ways to aggregate. + enum CapabilityResponseType { + // No additional aggregation is needed on the remote response. + REPORT, + // A number of identical observations need to be aggregated. + OBSERVATION_IDENTICAL + } + struct Capability { // Capability type, e.g. "data-streams-reports" // bytes32(string); validation regex: ^[a-z0-9_\-:]{1,32}$ @@ -21,12 +37,49 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { // Semver, e.g., "1.2.3" // bytes32(string); must be valid Semver + max 32 characters. bytes32 version; + // responseType indicates whether remote response requires + // aggregation or is an OCR report. There are multiple possible + // ways to aggregate. + CapabilityResponseType responseType; + // An address to the capability configuration contract. Having this defined + // on a capability enforces consistent configuration across DON instances + // serving the same capability. Configuration contract MUST implement + // CapabilityConfigurationContractInterface. + // + // The main use cases are: + // 1) Sharing capability configuration across DON instances + // 2) Inspect and modify on-chain configuration without off-chain + // capability code. + // + // It is not recommended to store configuration which requires knowledge of + // the DON membership. + address configurationContract; } + /// @notice This error is thrown when a caller is not allowed + /// to execute the transaction + error AccessForbidden(); + + /// @notice This error is thrown when there is a mismatch between + /// array arguments + /// @param lengthOne The length of the first array argument + /// @param lengthTwo The length of the second array argument + error LengthMismatch(uint256 lengthOne, uint256 lengthTwo); + /// @notice This error is thrown when trying to set a node operator's /// admin address to the zero address error InvalidNodeOperatorAdmin(); + /// @notice This error is thrown when trying add a capability that already + /// exists. + error CapabilityAlreadyExists(); + + /// @notice This error is thrown when trying to add a capability with a + /// configuration contract that does not implement the required interface. + /// @param proposedConfigurationContract The address of the proposed + /// configuration contract. + error InvalidCapabilityConfigurationContractInterface(address proposedConfigurationContract); + /// @notice This event is emitted when a new node operator is added /// @param nodeOperatorId The ID of the newly added node operator /// @param admin The address of the admin that can manage the node @@ -38,11 +91,18 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { /// @param nodeOperatorId The ID of the node operator that was removed event NodeOperatorRemoved(uint256 nodeOperatorId); + /// @notice This event is emitted when a node operator is updated + /// @param nodeOperatorId The ID of the node operator that was updated + /// @param admin The address of the node operator's admin + /// @param name The node operator's human readable name + event NodeOperatorUpdated(uint256 nodeOperatorId, address indexed admin, string name); + /// @notice This event is emitted when a new capability is added /// @param capabilityId The ID of the newly added capability event CapabilityAdded(bytes32 indexed capabilityId); mapping(bytes32 => Capability) private s_capabilities; + EnumerableSet.Bytes32Set private s_capabilityIds; /// @notice Mapping of node operators mapping(uint256 nodeOperatorId => NodeOperator) private s_nodeOperators; @@ -78,6 +138,30 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { } } + /// @notice Updates a node operator + /// @param nodeOperatorIds The ID of the node operator being updated + function updateNodeOperators(uint256[] calldata nodeOperatorIds, NodeOperator[] calldata nodeOperators) external { + if (nodeOperatorIds.length != nodeOperators.length) + revert LengthMismatch(nodeOperatorIds.length, nodeOperators.length); + + address owner = owner(); + for (uint256 i; i < nodeOperatorIds.length; ++i) { + uint256 nodeOperatorId = nodeOperatorIds[i]; + NodeOperator memory nodeOperator = nodeOperators[i]; + if (nodeOperator.admin == address(0)) revert InvalidNodeOperatorAdmin(); + if (msg.sender != nodeOperator.admin && msg.sender != owner) revert AccessForbidden(); + + if ( + s_nodeOperators[nodeOperatorId].admin != nodeOperator.admin || + keccak256(abi.encode(s_nodeOperators[nodeOperatorId].name)) != keccak256(abi.encode(nodeOperator.name)) + ) { + s_nodeOperators[nodeOperatorId].admin = nodeOperator.admin; + s_nodeOperators[nodeOperatorId].name = nodeOperator.name; + emit NodeOperatorUpdated(nodeOperatorId, nodeOperator.admin, nodeOperator.name); + } + } + } + /// @notice Gets a node operator's data /// @param nodeOperatorId The ID of the node operator to query for /// @return NodeOperator The node operator data @@ -87,7 +171,21 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { function addCapability(Capability calldata capability) external onlyOwner { bytes32 capabilityId = getCapabilityID(capability.capabilityType, capability.version); + + if (s_capabilityIds.contains(capabilityId)) revert CapabilityAlreadyExists(); + + if (capability.configurationContract != address(0)) { + if ( + capability.configurationContract.code.length == 0 || + !IERC165(capability.configurationContract).supportsInterface( + ICapabilityConfiguration.getCapabilityConfiguration.selector + ) + ) revert InvalidCapabilityConfigurationContractInterface(capability.configurationContract); + } + + s_capabilityIds.add(capabilityId); s_capabilities[capabilityId] = capability; + emit CapabilityAdded(capabilityId); } diff --git a/contracts/src/v0.8/keystone/interfaces/ICapabilityConfiguration.sol b/contracts/src/v0.8/keystone/interfaces/ICapabilityConfiguration.sol new file mode 100644 index 00000000000..20447c9680a --- /dev/null +++ b/contracts/src/v0.8/keystone/interfaces/ICapabilityConfiguration.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/// @notice Interface for capability configuration contract. It MUST be +/// implemented for a contract to be used as a capability configuration. +/// The contract MAY store configuration that is shared across multiple +/// DON instances and capability versions. +/// @dev This interface does not guarantee the configuration contract's +/// correctness. It is the responsibility of the contract owner to ensure +/// that the configuration contract emits the CapabilityConfigurationSet +/// event when the configuration is set. +interface ICapabilityConfiguration { + /// @notice Emitted when a capability configuration is set. + event CapabilityConfigurationSet(); + + /// @notice Returns the capability configuration for a particular DON instance. + /// @dev donId is required to get DON-specific configuration. It avoids a + /// situation where configuration size grows too large. + /// @param donId The DON instance ID. These are stored in the CapabilityRegistry. + /// @return configuration DON's configuration for the capability. + function getCapabilityConfiguration(uint256 donId) external view returns (bytes memory configuration); + + // Solidity does not support generic returns types, so this cannot be part of + // the interface. However, the implementation contract MAY implement this + // function to enable configuration decoding on-chain. + // function decodeCapabilityConfiguration(bytes configuration) external returns (TypedCapabilityConfigStruct config) +} diff --git a/contracts/src/v0.8/keystone/test/BaseTest.t.sol b/contracts/src/v0.8/keystone/test/BaseTest.t.sol index 5c4b4c91809..4517a256b15 100644 --- a/contracts/src/v0.8/keystone/test/BaseTest.t.sol +++ b/contracts/src/v0.8/keystone/test/BaseTest.t.sol @@ -3,14 +3,17 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {Constants} from "./Constants.t.sol"; +import {CapabilityConfigurationContract} from "./mocks/CapabilityConfigurationContract.sol"; import {CapabilityRegistry} from "../CapabilityRegistry.sol"; contract BaseTest is Test, Constants { CapabilityRegistry internal s_capabilityRegistry; + CapabilityConfigurationContract internal s_capabilityConfigurationContract; function setUp() public virtual { vm.startPrank(ADMIN); s_capabilityRegistry = new CapabilityRegistry(); + s_capabilityConfigurationContract = new CapabilityConfigurationContract(); } function _getNodeOperators() internal view returns (CapabilityRegistry.NodeOperator[] memory) { diff --git a/contracts/src/v0.8/keystone/test/CapabilityRegistry_AddCapabilityTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilityRegistry_AddCapabilityTest.t.sol index 6fbdc43a2c7..d9e4b6b8383 100644 --- a/contracts/src/v0.8/keystone/test/CapabilityRegistry_AddCapabilityTest.t.sol +++ b/contracts/src/v0.8/keystone/test/CapabilityRegistry_AddCapabilityTest.t.sol @@ -2,16 +2,88 @@ pragma solidity ^0.8.19; import {BaseTest} from "./BaseTest.t.sol"; +import {CapabilityConfigurationContract} from "./mocks/CapabilityConfigurationContract.sol"; + import {CapabilityRegistry} from "../CapabilityRegistry.sol"; contract CapabilityRegistry_AddCapabilityTest is BaseTest { - function test_AddCapability() public { - s_capabilityRegistry.addCapability(CapabilityRegistry.Capability("data-streams-reports", "1.0.0")); + CapabilityRegistry.Capability private basicCapability = + CapabilityRegistry.Capability({ + capabilityType: "data-streams-reports", + version: "1.0.0", + responseType: CapabilityRegistry.CapabilityResponseType.REPORT, + configurationContract: address(0) + }); + + CapabilityRegistry.Capability private capabilityWithConfigurationContract = + CapabilityRegistry.Capability({ + capabilityType: "read-ethereum-mainnet-gas-price", + version: "1.0.2", + responseType: CapabilityRegistry.CapabilityResponseType.OBSERVATION_IDENTICAL, + configurationContract: address(s_capabilityConfigurationContract) + }); + + function test_RevertWhen_CalledByNonAdmin() public { + changePrank(STRANGER); + + vm.expectRevert("Only callable by owner"); + s_capabilityRegistry.addCapability(basicCapability); + } + + function test_RevertWhen_CapabilityExists() public { + // Successfully add the capability the first time + s_capabilityRegistry.addCapability(basicCapability); + + // Try to add the same capability again + vm.expectRevert(CapabilityRegistry.CapabilityAlreadyExists.selector); + s_capabilityRegistry.addCapability(basicCapability); + } + + function test_RevertWhen_ConfigurationContractNotDeployed() public { + address nonExistentContract = address(1); + capabilityWithConfigurationContract.configurationContract = nonExistentContract; + + vm.expectRevert( + abi.encodeWithSelector( + CapabilityRegistry.InvalidCapabilityConfigurationContractInterface.selector, + nonExistentContract + ) + ); + s_capabilityRegistry.addCapability(capabilityWithConfigurationContract); + } + + function test_RevertWhen_ConfigurationContractDoesNotMatchInterface() public { + CapabilityRegistry contractWithoutERC165 = new CapabilityRegistry(); + + vm.expectRevert(); + capabilityWithConfigurationContract.configurationContract = address(contractWithoutERC165); + s_capabilityRegistry.addCapability(capabilityWithConfigurationContract); + } + + function test_AddCapability_NoConfigurationContract() public { + s_capabilityRegistry.addCapability(basicCapability); bytes32 capabilityId = s_capabilityRegistry.getCapabilityID(bytes32("data-streams-reports"), bytes32("1.0.0")); - CapabilityRegistry.Capability memory capability = s_capabilityRegistry.getCapability(capabilityId); + CapabilityRegistry.Capability memory storedCapability = s_capabilityRegistry.getCapability(capabilityId); + + assertEq(storedCapability.capabilityType, basicCapability.capabilityType); + assertEq(storedCapability.version, basicCapability.version); + assertEq(uint256(storedCapability.responseType), uint256(basicCapability.responseType)); + assertEq(storedCapability.configurationContract, basicCapability.configurationContract); + } + + function test_AddCapability_WithConfiguration() public { + s_capabilityRegistry.addCapability(capabilityWithConfigurationContract); + + bytes32 capabilityId = s_capabilityRegistry.getCapabilityID( + bytes32(capabilityWithConfigurationContract.capabilityType), + bytes32(capabilityWithConfigurationContract.version) + ); + CapabilityRegistry.Capability memory storedCapability = s_capabilityRegistry.getCapability(capabilityId); - assertEq(capability.capabilityType, "data-streams-reports"); - assertEq(capability.version, "1.0.0"); + assertEq(storedCapability.capabilityType, capabilityWithConfigurationContract.capabilityType); + assertEq(storedCapability.version, capabilityWithConfigurationContract.version); + assertEq(uint256(storedCapability.responseType), uint256(capabilityWithConfigurationContract.responseType)); + assertEq(storedCapability.configurationContract, capabilityWithConfigurationContract.configurationContract); } } diff --git a/contracts/src/v0.8/keystone/test/CapabilityRegistry_UpdateNodeOperatorsTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilityRegistry_UpdateNodeOperatorsTest.t.sol new file mode 100644 index 00000000000..4be8010dcb1 --- /dev/null +++ b/contracts/src/v0.8/keystone/test/CapabilityRegistry_UpdateNodeOperatorsTest.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseTest} from "./BaseTest.t.sol"; +import {CapabilityRegistry} from "../CapabilityRegistry.sol"; + +contract CapabilityRegistry_UpdateNodeOperatorTest is BaseTest { + event NodeOperatorUpdated(uint256 nodeOperatorId, address indexed admin, string name); + + uint256 private constant TEST_NODE_OPERATOR_ID = 0; + address private constant NEW_NODE_OPERATOR_ADMIN = address(3); + string private constant NEW_NODE_OPERATOR_NAME = "new-node-operator"; + + function setUp() public override { + BaseTest.setUp(); + changePrank(ADMIN); + s_capabilityRegistry.addNodeOperators(_getNodeOperators()); + } + + function test_RevertWhen_CalledByNonAdminAndNonOwner() public { + changePrank(STRANGER); + vm.expectRevert(CapabilityRegistry.AccessForbidden.selector); + + CapabilityRegistry.NodeOperator[] memory nodeOperators = new CapabilityRegistry.NodeOperator[](1); + nodeOperators[0] = CapabilityRegistry.NodeOperator({admin: NEW_NODE_OPERATOR_ADMIN, name: NEW_NODE_OPERATOR_NAME}); + + uint256[] memory nodeOperatorIds = new uint256[](1); + nodeOperatorIds[0] = TEST_NODE_OPERATOR_ID; + s_capabilityRegistry.updateNodeOperators(nodeOperatorIds, nodeOperators); + } + + function test_RevertWhen_NodeOperatorAdminIsZeroAddress() public { + changePrank(ADMIN); + vm.expectRevert(CapabilityRegistry.InvalidNodeOperatorAdmin.selector); + CapabilityRegistry.NodeOperator[] memory nodeOperators = new CapabilityRegistry.NodeOperator[](1); + nodeOperators[0] = CapabilityRegistry.NodeOperator({admin: address(0), name: NEW_NODE_OPERATOR_NAME}); + + uint256[] memory nodeOperatorIds = new uint256[](1); + nodeOperatorIds[0] = TEST_NODE_OPERATOR_ID; + s_capabilityRegistry.updateNodeOperators(nodeOperatorIds, nodeOperators); + } + + function test_UpdatesNodeOperator() public { + changePrank(ADMIN); + + CapabilityRegistry.NodeOperator[] memory nodeOperators = new CapabilityRegistry.NodeOperator[](1); + nodeOperators[0] = CapabilityRegistry.NodeOperator({admin: NEW_NODE_OPERATOR_ADMIN, name: NEW_NODE_OPERATOR_NAME}); + + uint256[] memory nodeOperatorIds = new uint256[](1); + nodeOperatorIds[0] = TEST_NODE_OPERATOR_ID; + + vm.expectEmit(true, true, true, true, address(s_capabilityRegistry)); + emit NodeOperatorUpdated(TEST_NODE_OPERATOR_ID, NEW_NODE_OPERATOR_ADMIN, NEW_NODE_OPERATOR_NAME); + s_capabilityRegistry.updateNodeOperators(nodeOperatorIds, nodeOperators); + + CapabilityRegistry.NodeOperator memory nodeOperator = s_capabilityRegistry.getNodeOperator(0); + assertEq(nodeOperator.admin, NEW_NODE_OPERATOR_ADMIN); + assertEq(nodeOperator.name, NEW_NODE_OPERATOR_NAME); + } +} diff --git a/contracts/src/v0.8/keystone/test/mocks/CapabilityConfigurationContract.sol b/contracts/src/v0.8/keystone/test/mocks/CapabilityConfigurationContract.sol new file mode 100644 index 00000000000..0c3d8e0597d --- /dev/null +++ b/contracts/src/v0.8/keystone/test/mocks/CapabilityConfigurationContract.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ICapabilityConfiguration} from "../../interfaces/ICapabilityConfiguration.sol"; +import {ERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/ERC165.sol"; + +contract CapabilityConfigurationContract is ICapabilityConfiguration, ERC165 { + mapping(uint256 => bytes) private s_donConfiguration; + + function getCapabilityConfiguration(uint256 donId) external view returns (bytes memory configuration) { + return s_donConfiguration[donId]; + } +} diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index 255b038037a..548acf3206c 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -130,7 +130,7 @@ func (r *rpcClient) Dial(callerCtx context.Context) error { wsrpc, err := rpc.DialWebsocket(ctx, r.ws.uri.String(), "") if err != nil { promEVMPoolRPCNodeDialsFailed.WithLabelValues(r.chainID.String(), r.name).Inc() - return pkgerrors.Wrapf(err, "error while dialing websocket: %v", r.ws.uri.Redacted()) + return r.wrapRPCClientError(pkgerrors.Wrapf(err, "error while dialing websocket: %v", r.ws.uri.Redacted())) } r.ws.rpc = wsrpc @@ -159,7 +159,7 @@ func (r *rpcClient) DialHTTP() error { httprpc, err := rpc.DialHTTP(r.http.uri.String()) if err != nil { promEVMPoolRPCNodeDialsFailed.WithLabelValues(r.chainID.String(), r.name).Inc() - return pkgerrors.Wrapf(err, "error while dialing HTTP: %v", r.http.uri.Redacted()) + return r.wrapRPCClientError(pkgerrors.Wrapf(err, "error while dialing HTTP: %v", r.http.uri.Redacted())) } r.http.rpc = httprpc @@ -295,10 +295,7 @@ func (r *rpcClient) UnsubscribeAllExceptAliveLoop() { // CallContext implementation func (r *rpcClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With( "method", method, @@ -307,6 +304,7 @@ func (r *rpcClient) CallContext(ctx context.Context, result interface{}, method lggr.Debug("RPC call: evmclient.Client#CallContext") start := time.Now() + var err error if http != nil { err = r.wrapHTTP(http.rpc.CallContext(ctx, result, method, args...)) } else { @@ -320,15 +318,13 @@ func (r *rpcClient) CallContext(ctx context.Context, result interface{}, method } func (r *rpcClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("nBatchElems", len(b), "batchElems", b) lggr.Trace("RPC call: evmclient.Client#BatchCallContext") start := time.Now() + var err error if http != nil { err = r.wrapHTTP(http.rpc.BatchCallContext(ctx, b)) } else { @@ -342,24 +338,23 @@ func (r *rpcClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) err } func (r *rpcClient) Subscribe(ctx context.Context, channel chan<- *evmtypes.Head, args ...interface{}) (commontypes.Subscription, error) { - ctx, cancel, ws, _, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, _ := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("args", args) lggr.Debug("RPC call: evmclient.Client#EthSubscribe") start := time.Now() + var sub commontypes.Subscription sub, err := ws.rpc.EthSubscribe(ctx, channel, args...) if err == nil { + sub = newSubscriptionErrorWrapper(sub, r.rpcClientErrorPrefix()) r.registerSub(sub) } duration := time.Since(start) r.logResult(lggr, err, duration, r.getRPCDomain(), "EthSubscribe") - return sub, err + return sub, r.wrapWS(err) } // GethClient wrappers @@ -370,17 +365,14 @@ func (r *rpcClient) TransactionReceipt(ctx context.Context, txHash common.Hash) return nil, err } if receipt == nil { - err = ethereum.NotFound + err = r.wrapRPCClientError(ethereum.NotFound) return } return } func (r *rpcClient) TransactionReceiptGeth(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("txHash", txHash) @@ -403,10 +395,7 @@ func (r *rpcClient) TransactionReceiptGeth(ctx context.Context, txHash common.Ha return } func (r *rpcClient) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("txHash", txHash) @@ -430,10 +419,7 @@ func (r *rpcClient) TransactionByHash(ctx context.Context, txHash common.Hash) ( } func (r *rpcClient) HeaderByNumber(ctx context.Context, number *big.Int) (header *types.Header, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("number", number) @@ -454,10 +440,7 @@ func (r *rpcClient) HeaderByNumber(ctx context.Context, number *big.Int) (header } func (r *rpcClient) HeaderByHash(ctx context.Context, hash common.Hash) (header *types.Header, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("hash", hash) @@ -494,7 +477,7 @@ func (r *rpcClient) blockByNumber(ctx context.Context, number string) (head *evm return nil, err } if head == nil { - err = ethereum.NotFound + err = r.wrapRPCClientError(ethereum.NotFound) return } head.EVMChainID = ubig.New(r.chainID) @@ -507,7 +490,7 @@ func (r *rpcClient) BlockByHash(ctx context.Context, hash common.Hash) (head *ev return nil, err } if head == nil { - err = ethereum.NotFound + err = r.wrapRPCClientError(ethereum.NotFound) return } head.EVMChainID = ubig.New(r.chainID) @@ -515,10 +498,7 @@ func (r *rpcClient) BlockByHash(ctx context.Context, hash common.Hash) (head *ev } func (r *rpcClient) BlockByHashGeth(ctx context.Context, hash common.Hash) (block *types.Block, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("hash", hash) @@ -541,10 +521,7 @@ func (r *rpcClient) BlockByHashGeth(ctx context.Context, hash common.Hash) (bloc } func (r *rpcClient) BlockByNumberGeth(ctx context.Context, number *big.Int) (block *types.Block, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("number", number) @@ -567,15 +544,13 @@ func (r *rpcClient) BlockByNumberGeth(ctx context.Context, number *big.Int) (blo } func (r *rpcClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("tx", tx) lggr.Debug("RPC call: evmclient.Client#SendTransaction") start := time.Now() + var err error if http != nil { err = r.wrapHTTP(http.geth.SendTransaction(ctx, tx)) } else { @@ -607,10 +582,7 @@ func (r *rpcClient) SendEmptyTransaction( // PendingSequenceAt returns one higher than the highest nonce from both mempool and mined transactions func (r *rpcClient) PendingSequenceAt(ctx context.Context, account common.Address) (nonce evmtypes.Nonce, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return 0, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("account", account) @@ -639,10 +611,7 @@ func (r *rpcClient) PendingSequenceAt(ctx context.Context, account common.Addres // mined nonce at the given block number, but it actually returns the total // transaction count which is the highest mined nonce + 1 func (r *rpcClient) SequenceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (nonce evmtypes.Nonce, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return 0, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("account", account, "blockNumber", blockNumber) @@ -668,10 +637,7 @@ func (r *rpcClient) SequenceAt(ctx context.Context, account common.Address, bloc } func (r *rpcClient) PendingCodeAt(ctx context.Context, account common.Address) (code []byte, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("account", account) @@ -694,10 +660,7 @@ func (r *rpcClient) PendingCodeAt(ctx context.Context, account common.Address) ( } func (r *rpcClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) (code []byte, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("account", account, "blockNumber", blockNumber) @@ -720,10 +683,7 @@ func (r *rpcClient) CodeAt(ctx context.Context, account common.Address, blockNum } func (r *rpcClient) EstimateGas(ctx context.Context, c interface{}) (gas uint64, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return 0, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() call := c.(ethereum.CallMsg) lggr := r.newRqLggr().With("call", call) @@ -747,10 +707,7 @@ func (r *rpcClient) EstimateGas(ctx context.Context, c interface{}) (gas uint64, } func (r *rpcClient) SuggestGasPrice(ctx context.Context) (price *big.Int, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr() @@ -773,10 +730,7 @@ func (r *rpcClient) SuggestGasPrice(ctx context.Context) (price *big.Int, err er } func (r *rpcClient) CallContract(ctx context.Context, msg interface{}, blockNumber *big.Int) (val []byte, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("callMsg", msg, "blockNumber", blockNumber) message := msg.(ethereum.CallMsg) @@ -804,10 +758,7 @@ func (r *rpcClient) CallContract(ctx context.Context, msg interface{}, blockNumb } func (r *rpcClient) PendingCallContract(ctx context.Context, msg interface{}) (val []byte, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("callMsg", msg) message := msg.(ethereum.CallMsg) @@ -841,10 +792,7 @@ func (r *rpcClient) LatestBlockHeight(ctx context.Context) (*big.Int, error) { } func (r *rpcClient) BlockNumber(ctx context.Context) (height uint64, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return 0, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr() @@ -867,10 +815,7 @@ func (r *rpcClient) BlockNumber(ctx context.Context) (height uint64, err error) } func (r *rpcClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (balance *big.Int, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("account", account.Hex(), "blockNumber", blockNumber) @@ -907,7 +852,7 @@ func (r *rpcClient) TokenBalance(ctx context.Context, address common.Address, co return numLinkBigInt, err } if _, ok := numLinkBigInt.SetString(result, 0); !ok { - return nil, fmt.Errorf("failed to parse int: %s", result) + return nil, r.wrapRPCClientError(fmt.Errorf("failed to parse int: %s", result)) } return numLinkBigInt, nil } @@ -926,10 +871,7 @@ func (r *rpcClient) FilterEvents(ctx context.Context, q ethereum.FilterQuery) ([ } func (r *rpcClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (l []types.Log, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("q", q) @@ -957,10 +899,7 @@ func (r *rpcClient) ClientVersion(ctx context.Context) (version string, err erro } func (r *rpcClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (sub ethereum.Subscription, err error) { - ctx, cancel, ws, _, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, _ := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr().With("q", q) @@ -968,6 +907,7 @@ func (r *rpcClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQu start := time.Now() sub, err = ws.geth.SubscribeFilterLogs(ctx, q, ch) if err == nil { + sub = newSubscriptionErrorWrapper(sub, r.rpcClientErrorPrefix()) r.registerSub(sub) } err = r.wrapWS(err) @@ -979,10 +919,7 @@ func (r *rpcClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQu } func (r *rpcClient) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return nil, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr() @@ -1007,7 +944,7 @@ func (r *rpcClient) SuggestGasTipCap(ctx context.Context) (tipCap *big.Int, err // Returns the ChainID according to the geth client. This is useful for functions like verify() // the common node. func (r *rpcClient) ChainID(ctx context.Context) (chainID *big.Int, err error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() @@ -1026,6 +963,15 @@ func (r *rpcClient) newRqLggr() logger.SugaredLogger { return r.rpcLog.With("requestID", uuid.New()) } +func (r *rpcClient) wrapRPCClientError(err error) error { + // simple add msg to the error without adding new stack trace + return pkgerrors.WithMessage(err, r.rpcClientErrorPrefix()) +} + +func (r *rpcClient) rpcClientErrorPrefix() string { + return fmt.Sprintf("RPCClient returned error (%s)", r.name) +} + func wrapCallError(err error, tp string) error { if err == nil { return nil @@ -1038,11 +984,12 @@ func wrapCallError(err error, tp string) error { func (r *rpcClient) wrapWS(err error) error { err = wrapCallError(err, fmt.Sprintf("%s websocket (%s)", r.tier.String(), r.ws.uri.Redacted())) - return err + return r.wrapRPCClientError(err) } func (r *rpcClient) wrapHTTP(err error) error { err = wrapCallError(err, fmt.Sprintf("%s http (%s)", r.tier.String(), r.http.uri.Redacted())) + err = r.wrapRPCClientError(err) if err != nil { r.rpcLog.Debugw("Call failed", "err", err) } else { @@ -1052,7 +999,7 @@ func (r *rpcClient) wrapHTTP(err error) error { } // makeLiveQueryCtxAndSafeGetClients wraps makeQueryCtx -func (r *rpcClient) makeLiveQueryCtxAndSafeGetClients(parentCtx context.Context) (ctx context.Context, cancel context.CancelFunc, ws rawclient, http *rawclient, err error) { +func (r *rpcClient) makeLiveQueryCtxAndSafeGetClients(parentCtx context.Context) (ctx context.Context, cancel context.CancelFunc, ws rawclient, http *rawclient) { // Need to wrap in mutex because state transition can cancel and replace the // context r.stateMu.RLock() @@ -1072,16 +1019,14 @@ func (r *rpcClient) makeQueryCtx(ctx context.Context) (context.Context, context. } func (r *rpcClient) IsSyncing(ctx context.Context) (bool, error) { - ctx, cancel, ws, http, err := r.makeLiveQueryCtxAndSafeGetClients(ctx) - if err != nil { - return false, err - } + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() lggr := r.newRqLggr() lggr.Debug("RPC call: evmclient.Client#SyncProgress") var syncProgress *ethereum.SyncProgress start := time.Now() + var err error if http != nil { syncProgress, err = http.geth.SyncProgress(ctx) err = r.wrapHTTP(err) diff --git a/core/chains/evm/client/sub_error_wrapper.go b/core/chains/evm/client/sub_error_wrapper.go new file mode 100644 index 00000000000..689991ce70f --- /dev/null +++ b/core/chains/evm/client/sub_error_wrapper.go @@ -0,0 +1,77 @@ +package client + +import ( + "fmt" + + commontypes "github.com/smartcontractkit/chainlink/v2/common/types" +) + +// subErrorWrapper - adds specified prefix to a subscription error +type subErrorWrapper struct { + sub commontypes.Subscription + errorPrefix string + + done chan struct{} + unSub chan struct{} + errorCh chan error +} + +func newSubscriptionErrorWrapper(sub commontypes.Subscription, errorPrefix string) *subErrorWrapper { + s := &subErrorWrapper{ + sub: sub, + errorPrefix: errorPrefix, + done: make(chan struct{}), + unSub: make(chan struct{}), + errorCh: make(chan error), + } + + go func() { + for { + select { + // sub.Err channel is closed by sub.Unsubscribe + case err, ok := <-sub.Err(): + if !ok { + // might only happen if someone terminated wrapped subscription + // in any case - do our best to release resources + // we can't call Unsubscribe on root sub as this might cause panic + close(s.errorCh) + close(s.done) + return + } + + select { + case s.errorCh <- fmt.Errorf("%s: %w", s.errorPrefix, err): + case <-s.unSub: + s.close() + return + } + case <-s.unSub: + s.close() + return + } + } + }() + + return s +} + +func (s *subErrorWrapper) close() { + s.sub.Unsubscribe() + close(s.errorCh) + close(s.done) +} + +func (s *subErrorWrapper) Unsubscribe() { + select { + // already unsubscribed + case <-s.done: + // signal unsubscribe + case s.unSub <- struct{}{}: + // wait for unsubscribe to complete + <-s.done + } +} + +func (s *subErrorWrapper) Err() <-chan error { + return s.errorCh +} diff --git a/core/chains/evm/client/sub_error_wrapper_test.go b/core/chains/evm/client/sub_error_wrapper_test.go new file mode 100644 index 00000000000..457d392a50e --- /dev/null +++ b/core/chains/evm/client/sub_error_wrapper_test.go @@ -0,0 +1,75 @@ +package client + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" +) + +func TestSubscriptionErrorWrapper(t *testing.T) { + t.Parallel() + t.Run("Unsubscribe wrapper releases resources", func(t *testing.T) { + t.Parallel() + + mockedSub := NewMockSubscription() + const prefix = "RPC returned error" + wrapper := newSubscriptionErrorWrapper(mockedSub, prefix) + wrapper.Unsubscribe() + + // mock's resources were relased + assert.True(t, mockedSub.unsubscribed) + _, ok := <-mockedSub.Err() + assert.False(t, ok) + // wrapper's channels are closed + _, ok = <-wrapper.Err() + assert.False(t, ok) + // subsequence unsubscribe does not causes panic + wrapper.Unsubscribe() + }) + t.Run("Unsubscribe interrupts error delivery", func(t *testing.T) { + t.Parallel() + sub := NewMockSubscription() + const prefix = "RPC returned error" + wrapper := newSubscriptionErrorWrapper(sub, prefix) + sub.Errors <- fmt.Errorf("error") + + wrapper.Unsubscribe() + _, ok := <-wrapper.Err() + assert.False(t, ok) + }) + t.Run("Successfully wraps error", func(t *testing.T) { + t.Parallel() + sub := NewMockSubscription() + const prefix = "RPC returned error" + wrapper := newSubscriptionErrorWrapper(sub, prefix) + sub.Errors <- fmt.Errorf("root error") + + err, ok := <-wrapper.Err() + assert.True(t, ok) + assert.Equal(t, "RPC returned error: root error", err.Error()) + + wrapper.Unsubscribe() + _, ok = <-wrapper.Err() + assert.False(t, ok) + }) + t.Run("Unsubscribe on root does not cause panic", func(t *testing.T) { + t.Parallel() + mockedSub := NewMockSubscription() + wrapper := newSubscriptionErrorWrapper(mockedSub, "") + + mockedSub.Unsubscribe() + // mock's resources were released + assert.True(t, mockedSub.unsubscribed) + _, ok := <-mockedSub.Err() + assert.False(t, ok) + // wrapper's channels are eventually closed + tests.AssertEventually(t, func() bool { + _, ok = <-wrapper.Err() + return !ok + }) + + }) +} diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go index e1c7a9f48cc..9553f59ad61 100644 --- a/core/chains/evm/config/config_test.go +++ b/core/chains/evm/config/config_test.go @@ -23,6 +23,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/configtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" + "github.com/smartcontractkit/chainlink/v2/core/store/models" ) func TestChainScopedConfig(t *testing.T) { @@ -380,6 +381,7 @@ func TestChainScopedConfig_HeadTracker(t *testing.T) { func Test_chainScopedConfig_Validate(t *testing.T) { configWithChains := func(t *testing.T, id int64, chains ...*toml.Chain) config.AppConfig { return configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { + s.Database.URL = models.MustSecretURL("postgresql://doesnotexist:justtopassvalidationtests@localhost:5432/chainlink_na_test") chainID := ubig.NewI(id) c.EVM[0] = &toml.EVMConfig{ChainID: chainID, Enabled: ptr(true), Chain: toml.Defaults(chainID, chains...), Nodes: toml.EVMNodes{{ diff --git a/core/chains/evm/gas/block_history_estimator.go b/core/chains/evm/gas/block_history_estimator.go index 8b8c626f725..0ae067e45bf 100644 --- a/core/chains/evm/gas/block_history_estimator.go +++ b/core/chains/evm/gas/block_history_estimator.go @@ -721,7 +721,7 @@ func (b *BlockHistoryEstimator) batchFetch(ctx context.Context, reqs []rpc.Batch err := b.ethClient.BatchCallContext(ctx, reqs[i:j]) if pkgerrors.Is(err, context.DeadlineExceeded) { // We ran out of time, return what we have - b.logger.Warnf("Batch fetching timed out; loaded %d/%d results", i, len(reqs)) + b.logger.Warnf("Batch fetching timed out; loaded %d/%d results: %v", i, len(reqs), err) for k := i; k < len(reqs); k++ { if k < j { reqs[k].Error = pkgerrors.Wrap(err, "request failed") diff --git a/core/chains/evm/logpoller/orm.go b/core/chains/evm/logpoller/orm.go index 5e0a74a9183..d065553886e 100644 --- a/core/chains/evm/logpoller/orm.go +++ b/core/chains/evm/logpoller/orm.go @@ -224,7 +224,9 @@ func (o *DSORM) SelectLatestLogByEventSigWithConfs(ctx context.Context, eventSig AND event_sig = :event_sig AND address = :address AND block_number <= %s - ORDER BY (block_number, log_index) DESC LIMIT 1`, nestedBlockNumberQuery(confs)) + ORDER BY block_number desc, log_index DESC + LIMIT 1 + `, nestedBlockNumberQuery(confs)) var l Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -425,7 +427,7 @@ func (o *DSORM) SelectLogsByBlockRange(ctx context.Context, start, end int64) ([ WHERE evm_chain_id = :evm_chain_id AND block_number >= :start_block AND block_number <= :end_block - ORDER BY (block_number, log_index)` + ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -456,7 +458,7 @@ func (o *DSORM) SelectLogs(ctx context.Context, start, end int64, address common AND event_sig = :event_sig AND block_number >= :start_block AND block_number <= :end_block - ORDER BY (block_number, log_index)` + ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -488,7 +490,7 @@ func (o *DSORM) SelectLogsCreatedAfter(ctx context.Context, address common.Addre AND event_sig = :event_sig AND block_timestamp > :block_timestamp_after AND block_number <= %s - ORDER BY (block_number, log_index)`, nestedBlockNumberQuery(confs)) + ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -520,7 +522,7 @@ func (o *DSORM) SelectLogsWithSigs(ctx context.Context, start, end int64, addres AND address = :address AND event_sig = ANY(:event_sig_array) AND block_number BETWEEN :start_block AND :end_block - ORDER BY (block_number, log_index)` + ORDER BY block_number, log_index` query, sqlArgs, err := o.ds.BindNamed(query, args) if err != nil { @@ -647,7 +649,7 @@ func (o *DSORM) SelectLogsDataWordRange(ctx context.Context, address common.Addr AND substring(data from 32*:word_index+1 for 32) >= :word_value_min AND substring(data from 32*:word_index+1 for 32) <= :word_value_max AND block_number <= %s - ORDER BY (block_number, log_index)`, nestedBlockNumberQuery(confs)) + ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -678,7 +680,7 @@ func (o *DSORM) SelectLogsDataWordGreaterThan(ctx context.Context, address commo AND event_sig = :event_sig AND substring(data from 32*:word_index+1 for 32) >= :word_value_min AND block_number <= %s - ORDER BY (block_number, log_index)`, nestedBlockNumberQuery(confs)) + ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -710,7 +712,7 @@ func (o *DSORM) SelectLogsDataWordBetween(ctx context.Context, address common.Ad AND substring(data from 32*:word_index_min+1 for 32) <= :word_value AND substring(data from 32*:word_index_max+1 for 32) >= :word_value AND block_number <= %s - ORDER BY (block_number, log_index)`, nestedBlockNumberQuery(confs)) + ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -741,7 +743,7 @@ func (o *DSORM) SelectIndexedLogsTopicGreaterThan(ctx context.Context, address c AND event_sig = :event_sig AND topics[:topic_index] >= :topic_value_min AND block_number <= %s - ORDER BY (block_number, log_index)`, nestedBlockNumberQuery(confs)) + ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -774,7 +776,7 @@ func (o *DSORM) SelectIndexedLogsTopicRange(ctx context.Context, address common. AND topics[:topic_index] >= :topic_value_min AND topics[:topic_index] <= :topic_value_max AND block_number <= %s - ORDER BY (evm.logs.block_number, evm.logs.log_index)`, nestedBlockNumberQuery(confs)) + ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -805,7 +807,7 @@ func (o *DSORM) SelectIndexedLogs(ctx context.Context, address common.Address, e AND event_sig = :event_sig AND topics[:topic_index] = ANY(:topic_values) AND block_number <= %s - ORDER BY (block_number, log_index)`, nestedBlockNumberQuery(confs)) + ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -838,7 +840,7 @@ func (o *DSORM) SelectIndexedLogsByBlockRange(ctx context.Context, start, end in AND topics[:topic_index] = ANY(:topic_values) AND block_number >= :start_block AND block_number <= :end_block - ORDER BY (block_number, log_index)` + ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -872,7 +874,8 @@ func (o *DSORM) SelectIndexedLogsCreatedAfter(ctx context.Context, address commo AND topics[:topic_index] = ANY(:topic_values) AND block_timestamp > :block_timestamp_after AND block_number <= %s - ORDER BY (block_number, log_index)`, nestedBlockNumberQuery(confs)) + ORDER BY block_number, log_index + `, nestedBlockNumberQuery(confs)) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -901,7 +904,7 @@ func (o *DSORM) SelectIndexedLogsByTxHash(ctx context.Context, address common.Ad AND address = :address AND event_sig = :event_sig AND tx_hash = :tx_hash - ORDER BY (block_number, log_index)` + ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -949,7 +952,7 @@ func (o *DSORM) SelectIndexedLogsWithSigsExcluding(ctx context.Context, sigA, si AND b.event_sig = :sigB AND b.block_number BETWEEN :start_block AND :end_block AND b.block_number <= %s - ORDER BY block_number,log_index ASC`, nestedQuery, nestedQuery) + ORDER BY block_number, log_index`, nestedQuery, nestedQuery) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) diff --git a/core/chains/evm/testutils/client.go b/core/chains/evm/testutils/client.go new file mode 100644 index 00000000000..31ad23eeb81 --- /dev/null +++ b/core/chains/evm/testutils/client.go @@ -0,0 +1,198 @@ +package testutils + +import ( + "fmt" + "math/big" + "net/http" + "net/http/httptest" + "net/url" + "sync" + "testing" + "time" + + "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" + + evmclmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" +) + +func NewEthClientMock(t *testing.T) *evmclmocks.Client { + return evmclmocks.NewClient(t) +} + +func NewEthClientMockWithDefaultChain(t *testing.T) *evmclmocks.Client { + c := NewEthClientMock(t) + c.On("ConfiguredChainID").Return(FixtureChainID).Maybe() + //c.On("IsL2").Return(false).Maybe() + return c +} + +// JSONRPCHandler is called with the method and request param(s). +// respResult will be sent immediately. notifyResult is optional, and sent after a short delay. +type JSONRPCHandler func(reqMethod string, reqParams gjson.Result) JSONRPCResponse + +type JSONRPCResponse struct { + Result, Notify string // raw JSON (i.e. quoted strings etc.) + + Error struct { + Code int + Message string + } +} + +type testWSServer struct { + t *testing.T + s *httptest.Server + mu sync.RWMutex + wsconns []*websocket.Conn + wg sync.WaitGroup +} + +// NewWSServer starts a websocket server which invokes callback for each message received. +// If chainID is set, then eth_chainId calls will be automatically handled. +func NewWSServer(t *testing.T, chainID *big.Int, callback JSONRPCHandler) (ts *testWSServer) { + ts = new(testWSServer) + ts.t = t + ts.wsconns = make([]*websocket.Conn, 0) + handler := ts.newWSHandler(chainID, callback) + ts.s = httptest.NewServer(handler) + t.Cleanup(ts.Close) + return +} + +func (ts *testWSServer) Close() { + if func() bool { + ts.mu.Lock() + defer ts.mu.Unlock() + if ts.wsconns == nil { + ts.t.Log("Test WS server already closed") + return false + } + ts.s.CloseClientConnections() + ts.s.Close() + for _, ws := range ts.wsconns { + ws.Close() + } + ts.wsconns = nil // nil indicates server closed + return true + }() { + ts.wg.Wait() + } +} + +func (ts *testWSServer) WSURL() *url.URL { + return WSServerURL(ts.t, ts.s) +} + +// WSServerURL returns a ws:// url for the server +func WSServerURL(t *testing.T, s *httptest.Server) *url.URL { + u, err := url.Parse(s.URL) + require.NoError(t, err, "Failed to parse url") + u.Scheme = "ws" + return u +} + +func (ts *testWSServer) MustWriteBinaryMessageSync(t *testing.T, msg string) { + ts.mu.Lock() + defer ts.mu.Unlock() + conns := ts.wsconns + if len(conns) != 1 { + t.Fatalf("expected 1 conn, got %d", len(conns)) + } + conn := conns[0] + err := conn.WriteMessage(websocket.BinaryMessage, []byte(msg)) + require.NoError(t, err) +} + +func (ts *testWSServer) newWSHandler(chainID *big.Int, callback JSONRPCHandler) (handler http.HandlerFunc) { + if callback == nil { + callback = func(method string, params gjson.Result) (resp JSONRPCResponse) { return } + } + t := ts.t + upgrader := websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, + } + return func(w http.ResponseWriter, r *http.Request) { + ts.mu.Lock() + if ts.wsconns == nil { // closed + ts.mu.Unlock() + return + } + ts.wg.Add(1) + defer ts.wg.Done() + conn, err := upgrader.Upgrade(w, r, nil) + if !assert.NoError(t, err, "Failed to upgrade WS connection") { + ts.mu.Unlock() + return + } + defer conn.Close() + ts.wsconns = append(ts.wsconns, conn) + ts.mu.Unlock() + + for { + _, data, err := conn.ReadMessage() + if err != nil { + if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseAbnormalClosure) { + ts.t.Log("Websocket closing") + return + } + ts.t.Logf("Failed to read message: %v", err) + return + } + ts.t.Log("Received message", string(data)) + req := gjson.ParseBytes(data) + if !req.IsObject() { + ts.t.Logf("Request must be object: %v", req.Type) + return + } + if e := req.Get("error"); e.Exists() { + ts.t.Logf("Received jsonrpc error: %v", e) + continue + } + m := req.Get("method") + if m.Type != gjson.String { + ts.t.Logf("Method must be string: %v", m.Type) + return + } + + var resp JSONRPCResponse + if chainID != nil && m.String() == "eth_chainId" { + resp.Result = `"0x` + chainID.Text(16) + `"` + } else if m.String() == "eth_syncing" { + resp.Result = "false" + } else { + resp = callback(m.String(), req.Get("params")) + } + id := req.Get("id") + var msg string + if resp.Error.Message != "" { + msg = fmt.Sprintf(`{"jsonrpc":"2.0","id":%s,"error":{"code":%d,"message":"%s"}}`, id, resp.Error.Code, resp.Error.Message) + } else { + msg = fmt.Sprintf(`{"jsonrpc":"2.0","id":%s,"result":%s}`, id, resp.Result) + } + ts.t.Logf("Sending message: %v", msg) + ts.mu.Lock() + err = conn.WriteMessage(websocket.BinaryMessage, []byte(msg)) + ts.mu.Unlock() + if err != nil { + ts.t.Logf("Failed to write message: %v", err) + return + } + + if resp.Notify != "" { + time.Sleep(100 * time.Millisecond) + msg := fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x00","result":%s}}`, resp.Notify) + ts.t.Log("Sending message", msg) + ts.mu.Lock() + err = conn.WriteMessage(websocket.BinaryMessage, []byte(msg)) + ts.mu.Unlock() + if err != nil { + ts.t.Logf("Failed to write message: %v", err) + return + } + } + } + } +} diff --git a/core/chains/evm/testutils/config.go b/core/chains/evm/testutils/config.go new file mode 100644 index 00000000000..3d1425c5333 --- /dev/null +++ b/core/chains/evm/testutils/config.go @@ -0,0 +1,29 @@ +package testutils + +import ( + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" +) + +func NewTestChainScopedConfig(t testing.TB, overrideFn func(c *toml.EVMConfig)) config.ChainScopedConfig { + var chainID = (*big.Big)(FixtureChainID) + evmCfg := &toml.EVMConfig{ + ChainID: chainID, + Chain: toml.Defaults(chainID), + } + + if overrideFn != nil { + // We need to get the chainID from the override function first to load the correct chain defaults. + // Then we apply the override values on top + overrideFn(evmCfg) + evmCfg.Chain = toml.Defaults(evmCfg.ChainID) + overrideFn(evmCfg) + } + + return config.NewTOMLChainScopedConfig(evmCfg, logger.Test(t)) +} diff --git a/core/chains/evm/testutils/config_test.go b/core/chains/evm/testutils/config_test.go new file mode 100644 index 00000000000..0cbcc5eb63b --- /dev/null +++ b/core/chains/evm/testutils/config_test.go @@ -0,0 +1,22 @@ +package testutils + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" +) + +func TestNewTestChainScopedConfigOverride(t *testing.T) { + c := NewTestChainScopedConfig(t, func(c *toml.EVMConfig) { + finalityDepth := uint32(100) + c.FinalityDepth = &finalityDepth + }) + + // Overrides values + assert.Equal(t, uint32(100), c.EVM().FinalityDepth()) + // fallback.toml values + assert.Equal(t, false, c.EVM().GasEstimator().EIP1559DynamicFees()) + +} diff --git a/core/chains/evm/testutils/evmtypes.go b/core/chains/evm/testutils/evmtypes.go new file mode 100644 index 00000000000..5f9ae83d1b7 --- /dev/null +++ b/core/chains/evm/testutils/evmtypes.go @@ -0,0 +1,76 @@ +package testutils + +import ( + "crypto/rand" + "fmt" + "math" + "math/big" + mrand "math/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + evmutils "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" +) + +// FixtureChainID matches the chain always added by fixtures.sql +// It is set to 0 since no real chain ever has this ID and allows a virtual +// "test" chain ID to be used without clashes +var FixtureChainID = big.NewInt(0) + +// SimulatedChainID is the chain ID for the go-ethereum simulated backend +var SimulatedChainID = big.NewInt(1337) + +// NewRandomEVMChainID returns a suitable random chain ID that will not conflict +// with fixtures +func NewRandomEVMChainID() *big.Int { + id := mrand.Int63n(math.MaxInt32) + 10000 + return big.NewInt(id) +} + +// NewAddress return a random new address +func NewAddress() common.Address { + return common.BytesToAddress(randomBytes(20)) +} + +func randomBytes(n int) []byte { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + return b +} + +// Head given the value convert it into an Head +func Head(val interface{}) *evmtypes.Head { + var h evmtypes.Head + time := uint64(0) + switch t := val.(type) { + case int: + h = evmtypes.NewHead(big.NewInt(int64(t)), evmutils.NewHash(), evmutils.NewHash(), time, ubig.New(FixtureChainID)) + case uint64: + h = evmtypes.NewHead(big.NewInt(int64(t)), evmutils.NewHash(), evmutils.NewHash(), time, ubig.New(FixtureChainID)) + case int64: + h = evmtypes.NewHead(big.NewInt(t), evmutils.NewHash(), evmutils.NewHash(), time, ubig.New(FixtureChainID)) + case *big.Int: + h = evmtypes.NewHead(t, evmutils.NewHash(), evmutils.NewHash(), time, ubig.New(FixtureChainID)) + default: + panic(fmt.Sprintf("Could not convert %v of type %T to Head", val, val)) + } + return &h +} + +func NewLegacyTransaction(nonce uint64, to common.Address, value *big.Int, gasLimit uint32, gasPrice *big.Int, data []byte) *types.Transaction { + tx := types.LegacyTx{ + Nonce: nonce, + To: &to, + Value: value, + Gas: uint64(gasLimit), + GasPrice: gasPrice, + Data: data, + } + return types.NewTx(&tx) +} diff --git a/core/chains/evm/testutils/timeout.go b/core/chains/evm/testutils/timeout.go new file mode 100644 index 00000000000..4c7864f1191 --- /dev/null +++ b/core/chains/evm/testutils/timeout.go @@ -0,0 +1,41 @@ +package testutils + +import ( + "testing" + "time" +) + +type Awaiter chan struct{} + +func NewAwaiter() Awaiter { return make(Awaiter) } + +func (a Awaiter) ItHappened() { close(a) } + +func (a Awaiter) AssertHappened(t *testing.T, expected bool) { + t.Helper() + select { + case <-a: + if !expected { + t.Fatal("It happened") + } + default: + if expected { + t.Fatal("It didn't happen") + } + } +} + +func (a Awaiter) AwaitOrFail(t testing.TB, durationParams ...time.Duration) { + t.Helper() + + duration := 10 * time.Second + if len(durationParams) > 0 { + duration = durationParams[0] + } + + select { + case <-a: + case <-time.After(duration): + t.Fatal("Timed out waiting for Awaiter to get ItHappened") + } +} diff --git a/core/gethwrappers/keystone/generated/keystone_capability_registry/keystone_capability_registry.go b/core/gethwrappers/keystone/generated/keystone_capability_registry/keystone_capability_registry.go index 47c65d5276b..61060bcb8ea 100644 --- a/core/gethwrappers/keystone/generated/keystone_capability_registry/keystone_capability_registry.go +++ b/core/gethwrappers/keystone/generated/keystone_capability_registry/keystone_capability_registry.go @@ -31,8 +31,10 @@ var ( ) type CapabilityRegistryCapability struct { - CapabilityType [32]byte - Version [32]byte + CapabilityType [32]byte + Version [32]byte + ResponseType uint8 + ConfigurationContract common.Address } type CapabilityRegistryNodeOperator struct { @@ -41,8 +43,8 @@ type CapabilityRegistryNodeOperator struct { } var CapabilityRegistryMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"name\":\"InvalidNodeOperatorAdmin\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nodeOperatorId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NodeOperatorAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nodeOperatorId\",\"type\":\"uint256\"}],\"name\":\"NodeOperatorRemoved\",\"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\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityType\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"version\",\"type\":\"bytes32\"}],\"internalType\":\"structCapabilityRegistry.Capability\",\"name\":\"capability\",\"type\":\"tuple\"}],\"name\":\"addCapability\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilityRegistry.NodeOperator[]\",\"name\":\"nodeOperators\",\"type\":\"tuple[]\"}],\"name\":\"addNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityID\",\"type\":\"bytes32\"}],\"name\":\"getCapability\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityType\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"version\",\"type\":\"bytes32\"}],\"internalType\":\"structCapabilityRegistry.Capability\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityType\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"version\",\"type\":\"bytes32\"}],\"name\":\"getCapabilityID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"nodeOperatorId\",\"type\":\"uint256\"}],\"name\":\"getNodeOperator\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilityRegistry.NodeOperator\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"nodeOperatorIds\",\"type\":\"uint256[]\"}],\"name\":\"removeNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b610ef1806101576000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c80636e5f2869116100765780638da5cb5b1161005b5780638da5cb5b146101b65780639cb7c5f4146101de578063f2fde38b1461024557600080fd5b80636e5f28691461019b57806379ba5097146101ae57600080fd5b8063229111f5116100a7578063229111f514610120578063398f37731461016857806365c14dc71461017b57600080fd5b8063181f5a77146100c35780631cdf63431461010b575b600080fd5b604080518082018252601881527f4361706162696c697479526567697374727920312e302e300000000000000000602082015290516101029190610957565b60405180910390f35b61011e6101193660046109bd565b610258565b005b61015a61012e3660046109ff565b604080516020808201949094528082019290925280518083038201815260609092019052805191012090565b604051908152602001610102565b61011e6101763660046109bd565b61031b565b61018e610189366004610a21565b6104b4565b6040516101029190610a3a565b61011e6101a9366004610a7d565b61059a565b61011e610617565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610102565b61022a6101ec366004610a21565b604080518082019091526000808252602082015250600090815260026020908152604091829020825180840190935280548352600101549082015290565b60408051825181526020928301519281019290925201610102565b61011e610253366004610abe565b610719565b61026061072d565b60005b8181101561031657600083838381811061027f5761027f610ad9565b60209081029290920135600081815260039093526040832080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681559093509190506102d060018301826108a5565b50506040518181527f1e5877d7b3001d1569bf733b76c7eceda58bd6c031e5b8d0b7042308ba2e9d4f9060200160405180910390a15061030f81610b08565b9050610263565b505050565b61032361072d565b60005b8181101561031657600083838381811061034257610342610ad9565b90506020028101906103549190610b67565b61035d90610c4c565b805190915073ffffffffffffffffffffffffffffffffffffffff166103ae576040517feeacd93900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600454604080518082018252835173ffffffffffffffffffffffffffffffffffffffff908116825260208086015181840190815260008681526003909252939020825181547fffffffffffffffffffffffff0000000000000000000000000000000000000000169216919091178155915190919060018201906104319082610db1565b5090505060046000815461044490610b08565b909155508151602083015160405173ffffffffffffffffffffffffffffffffffffffff909216917fda6697b182650034bd205cdc2dbfabb06bdb3a0a83a2b45bfefa3c4881284e0b9161049991859190610ecb565b60405180910390a25050806104ad90610b08565b9050610326565b6040805180820190915260008152606060208201526000828152600360209081526040918290208251808401909352805473ffffffffffffffffffffffffffffffffffffffff168352600181018054919284019161051190610d16565b80601f016020809104026020016040519081016040528092919081815260200182805461053d90610d16565b801561058a5780601f1061055f5761010080835404028352916020019161058a565b820191906000526020600020905b81548152906001019060200180831161056d57829003601f168201915b5050505050815250509050919050565b6105a261072d565b60408051823560208083018290528085013583850181905284518085038601815260609094018086528451948301949094206000818152600290935294822092835560019092019190915582917f65610e5677eedff94555572640e442f89848a109ef8593fa927ac30b2565ff069190a25050565b60015473ffffffffffffffffffffffffffffffffffffffff16331461069d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b61072161072d565b61072a816107b0565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146107ae576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610694565b565b3373ffffffffffffffffffffffffffffffffffffffff82160361082f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610694565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b5080546108b190610d16565b6000825580601f106108c1575050565b601f01602090049060005260206000209081019061072a91905b808211156108ef57600081556001016108db565b5090565b6000815180845260005b81811015610919576020818501810151868301820152016108fd565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60208152600061096a60208301846108f3565b9392505050565b60008083601f84011261098357600080fd5b50813567ffffffffffffffff81111561099b57600080fd5b6020830191508360208260051b85010111156109b657600080fd5b9250929050565b600080602083850312156109d057600080fd5b823567ffffffffffffffff8111156109e757600080fd5b6109f385828601610971565b90969095509350505050565b60008060408385031215610a1257600080fd5b50508035926020909101359150565b600060208284031215610a3357600080fd5b5035919050565b6020815273ffffffffffffffffffffffffffffffffffffffff825116602082015260006020830151604080840152610a7560608401826108f3565b949350505050565b600060408284031215610a8f57600080fd5b50919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610ab957600080fd5b919050565b600060208284031215610ad057600080fd5b61096a82610a95565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610b60577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610b9b57600080fd5b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715610bf757610bf7610ba5565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610c4457610c44610ba5565b604052919050565b600060408236031215610c5e57600080fd5b610c66610bd4565b610c6f83610a95565b815260208084013567ffffffffffffffff80821115610c8d57600080fd5b9085019036601f830112610ca057600080fd5b813581811115610cb257610cb2610ba5565b610ce2847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601610bfd565b91508082523684828501011115610cf857600080fd5b80848401858401376000908201840152918301919091525092915050565b600181811c90821680610d2a57607f821691505b602082108103610a8f577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b601f82111561031657600081815260208120601f850160051c81016020861015610d8a5750805b601f850160051c820191505b81811015610da957828155600101610d96565b505050505050565b815167ffffffffffffffff811115610dcb57610dcb610ba5565b610ddf81610dd98454610d16565b84610d63565b602080601f831160018114610e325760008415610dfc5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610da9565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015610e7f57888601518255948401946001909101908401610e60565b5085821015610ebb57878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b828152604060208201526000610a7560408301846108f356fea164736f6c6343000813000a", + ABI: "[{\"inputs\":[],\"name\":\"AccessForbidden\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CapabilityAlreadyExists\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"proposedConfigurationContract\",\"type\":\"address\"}],\"name\":\"InvalidCapabilityConfigurationContractInterface\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidNodeOperatorAdmin\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"lengthOne\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lengthTwo\",\"type\":\"uint256\"}],\"name\":\"LengthMismatch\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"capabilityId\",\"type\":\"bytes32\"}],\"name\":\"CapabilityAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nodeOperatorId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NodeOperatorAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nodeOperatorId\",\"type\":\"uint256\"}],\"name\":\"NodeOperatorRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nodeOperatorId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"NodeOperatorUpdated\",\"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\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityType\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"version\",\"type\":\"bytes32\"},{\"internalType\":\"enumCapabilityRegistry.CapabilityResponseType\",\"name\":\"responseType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"configurationContract\",\"type\":\"address\"}],\"internalType\":\"structCapabilityRegistry.Capability\",\"name\":\"capability\",\"type\":\"tuple\"}],\"name\":\"addCapability\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilityRegistry.NodeOperator[]\",\"name\":\"nodeOperators\",\"type\":\"tuple[]\"}],\"name\":\"addNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityID\",\"type\":\"bytes32\"}],\"name\":\"getCapability\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityType\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"version\",\"type\":\"bytes32\"},{\"internalType\":\"enumCapabilityRegistry.CapabilityResponseType\",\"name\":\"responseType\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"configurationContract\",\"type\":\"address\"}],\"internalType\":\"structCapabilityRegistry.Capability\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"capabilityType\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"version\",\"type\":\"bytes32\"}],\"name\":\"getCapabilityID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"nodeOperatorId\",\"type\":\"uint256\"}],\"name\":\"getNodeOperator\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilityRegistry.NodeOperator\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"nodeOperatorIds\",\"type\":\"uint256[]\"}],\"name\":\"removeNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"nodeOperatorIds\",\"type\":\"uint256[]\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"internalType\":\"structCapabilityRegistry.NodeOperator[]\",\"name\":\"nodeOperators\",\"type\":\"tuple[]\"}],\"name\":\"updateNodeOperators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b5033806000816100675760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615610097576100978161009f565b505050610148565b336001600160a01b038216036100f75760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161005e565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b611702806101576000396000f3fe608060405234801561001057600080fd5b50600436106100c95760003560e01c8063398f3773116100815780638da5cb5b1161005b5780638da5cb5b146101ad5780639cb7c5f4146101d5578063f2fde38b146101f557600080fd5b8063398f37731461017257806365c14dc71461018557806379ba5097146101a557600080fd5b8063181f5a77116100b2578063181f5a77146100f65780631cdf63431461013e578063229111f51461015157600080fd5b80630c5801e3146100ce578063117392ce146100e3575b600080fd5b6100e16100dc366004610efe565b610208565b005b6100e16100f1366004610f6a565b610519565b604080518082018252601881527f4361706162696c697479526567697374727920312e302e300000000000000000602082015290516101359190610fe6565b60405180910390f35b6100e161014c366004610ff9565b61074a565b61016461015f36600461103b565b61080d565b604051908152602001610135565b6100e1610180366004610ff9565b61083c565b61019861019336600461105d565b6109d5565b6040516101359190611076565b6100e1610abb565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610135565b6101e86101e336600461105d565b610bb8565b60405161013591906110e8565b6100e1610203366004611186565b610c62565b828114610250576040517fab8b67c600000000000000000000000000000000000000000000000000000000815260048101849052602481018290526044015b60405180910390fd5b6000805473ffffffffffffffffffffffffffffffffffffffff16905b84811015610511576000868683818110610288576102886111a3565b90506020020135905060008585848181106102a5576102a56111a3565b90506020028101906102b791906111d2565b6102c0906112b7565b805190915073ffffffffffffffffffffffffffffffffffffffff16610311576040517feeacd93900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805173ffffffffffffffffffffffffffffffffffffffff16331480159061034e57503373ffffffffffffffffffffffffffffffffffffffff851614155b15610385576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805160008381526005602052604090205473ffffffffffffffffffffffffffffffffffffffff908116911614158061043757506020808201516040516103cb9201610fe6565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828252805160209182012060008681526005835292909220919261041e9260010191016113d0565b6040516020818303038152906040528051906020012014155b156104fe578051600083815260056020908152604090912080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9093169290921782558201516001909101906104a490826114bf565b50806000015173ffffffffffffffffffffffffffffffffffffffff167f14c8f513e8a6d86d2d16b0cb64976de4e72386c4f8068eca3b7354373f8fe97a8383602001516040516104f59291906115d9565b60405180910390a25b50508061050a906115f2565b905061026c565b505050505050565b610521610c76565b60006105328235602084013561080d565b905061053f600382610cf9565b15610576576040517fe288638f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006105886080840160608501611186565b73ffffffffffffffffffffffffffffffffffffffff16146106f3576105b36080830160608401611186565b73ffffffffffffffffffffffffffffffffffffffff163b158061069357506105e16080830160608401611186565b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f884efe6100000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff91909116906301ffc9a790602401602060405180830381865afa15801561066d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106919190611651565b155b156106f3576106a86080830160608401611186565b6040517fabb5e3fd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401610247565b6106fe600382610d14565b50600081815260026020526040902082906107198282611673565b505060405181907f65610e5677eedff94555572640e442f89848a109ef8593fa927ac30b2565ff0690600090a25050565b610752610c76565b60005b81811015610808576000838383818110610771576107716111a3565b60209081029290920135600081815260059093526040832080547fffffffffffffffffffffffff00000000000000000000000000000000000000001681559093509190506107c26001830182610e64565b50506040518181527f1e5877d7b3001d1569bf733b76c7eceda58bd6c031e5b8d0b7042308ba2e9d4f9060200160405180910390a150610801816115f2565b9050610755565b505050565b604080516020808201859052818301849052825180830384018152606090920190925280519101205b92915050565b610844610c76565b60005b81811015610808576000838383818110610863576108636111a3565b905060200281019061087591906111d2565b61087e906112b7565b805190915073ffffffffffffffffffffffffffffffffffffffff166108cf576040517feeacd93900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600654604080518082018252835173ffffffffffffffffffffffffffffffffffffffff908116825260208086015181840190815260008681526005909252939020825181547fffffffffffffffffffffffff00000000000000000000000000000000000000001692169190911781559151909190600182019061095290826114bf565b50905050600660008154610965906115f2565b909155508151602083015160405173ffffffffffffffffffffffffffffffffffffffff909216917fda6697b182650034bd205cdc2dbfabb06bdb3a0a83a2b45bfefa3c4881284e0b916109ba918591906115d9565b60405180910390a25050806109ce906115f2565b9050610847565b6040805180820190915260008152606060208201526000828152600560209081526040918290208251808401909352805473ffffffffffffffffffffffffffffffffffffffff1683526001810180549192840191610a3290611383565b80601f0160208091040260200160405190810160405280929190818152602001828054610a5e90611383565b8015610aab5780601f10610a8057610100808354040283529160200191610aab565b820191906000526020600020905b815481529060010190602001808311610a8e57829003601f168201915b5050505050815250509050919050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610b3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152606401610247565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b604080516080808201835260008083526020808401829052838501829052606084018290528582526002808252918590208551938401865280548452600180820154928501929092529182015493949293919284019160ff1690811115610c2157610c216110b9565b6001811115610c3257610c326110b9565b815260029190910154610100900473ffffffffffffffffffffffffffffffffffffffff1660209091015292915050565b610c6a610c76565b610c7381610d20565b50565b60005473ffffffffffffffffffffffffffffffffffffffff163314610cf7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610247565b565b600081815260018301602052604081205415155b9392505050565b6000610d0d8383610e15565b3373ffffffffffffffffffffffffffffffffffffffff821603610d9f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610247565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000818152600183016020526040812054610e5c57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610836565b506000610836565b508054610e7090611383565b6000825580601f10610e80575050565b601f016020900490600052602060002090810190610c7391905b80821115610eae5760008155600101610e9a565b5090565b60008083601f840112610ec457600080fd5b50813567ffffffffffffffff811115610edc57600080fd5b6020830191508360208260051b8501011115610ef757600080fd5b9250929050565b60008060008060408587031215610f1457600080fd5b843567ffffffffffffffff80821115610f2c57600080fd5b610f3888838901610eb2565b90965094506020870135915080821115610f5157600080fd5b50610f5e87828801610eb2565b95989497509550505050565b600060808284031215610f7c57600080fd5b50919050565b6000815180845260005b81811015610fa857602081850181015186830182015201610f8c565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081526000610d0d6020830184610f82565b6000806020838503121561100c57600080fd5b823567ffffffffffffffff81111561102357600080fd5b61102f85828601610eb2565b90969095509350505050565b6000806040838503121561104e57600080fd5b50508035926020909101359150565b60006020828403121561106f57600080fd5b5035919050565b6020815273ffffffffffffffffffffffffffffffffffffffff8251166020820152600060208301516040808401526110b16060840182610f82565b949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b81518152602080830151908201526040820151608082019060028110611137577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8060408401525073ffffffffffffffffffffffffffffffffffffffff606084015116606083015292915050565b73ffffffffffffffffffffffffffffffffffffffff81168114610c7357600080fd5b60006020828403121561119857600080fd5b8135610d0d81611164565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc183360301811261120657600080fd5b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff8111828210171561126257611262611210565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156112af576112af611210565b604052919050565b6000604082360312156112c957600080fd5b6112d161123f565b82356112dc81611164565b815260208381013567ffffffffffffffff808211156112fa57600080fd5b9085019036601f83011261130d57600080fd5b81358181111561131f5761131f611210565b61134f847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601611268565b9150808252368482850101111561136557600080fd5b80848401858401376000908201840152918301919091525092915050565b600181811c9082168061139757607f821691505b602082108103610f7c577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006020808352600084546113e481611383565b80848701526040600180841660008114611405576001811461143d5761146b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008516838a01528284151560051b8a0101955061146b565b896000528660002060005b858110156114635781548b8201860152908301908801611448565b8a0184019650505b509398975050505050505050565b601f82111561080857600081815260208120601f850160051c810160208610156114a05750805b601f850160051c820191505b81811015610511578281556001016114ac565b815167ffffffffffffffff8111156114d9576114d9611210565b6114ed816114e78454611383565b84611479565b602080601f831160018114611540576000841561150a5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610511565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561158d5788860151825594840194600190910190840161156e565b50858210156115c957878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b8281526040602082015260006110b16040830184610f82565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361164a577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b60006020828403121561166357600080fd5b81518015158114610d0d57600080fd5b81358155602082013560018201556002810160408301356002811061169757600080fd5b815460608501356116a781611164565b74ffffffffffffffffffffffffffffffffffffffff008160081b1660ff84167fffffffffffffffffffffff00000000000000000000000000000000000000000084161717845550505050505056fea164736f6c6343000813000a", } var CapabilityRegistryABI = CapabilityRegistryMetaData.ABI @@ -351,6 +353,18 @@ func (_CapabilityRegistry *CapabilityRegistryTransactorSession) TransferOwnershi return _CapabilityRegistry.Contract.TransferOwnership(&_CapabilityRegistry.TransactOpts, to) } +func (_CapabilityRegistry *CapabilityRegistryTransactor) UpdateNodeOperators(opts *bind.TransactOpts, nodeOperatorIds []*big.Int, nodeOperators []CapabilityRegistryNodeOperator) (*types.Transaction, error) { + return _CapabilityRegistry.contract.Transact(opts, "updateNodeOperators", nodeOperatorIds, nodeOperators) +} + +func (_CapabilityRegistry *CapabilityRegistrySession) UpdateNodeOperators(nodeOperatorIds []*big.Int, nodeOperators []CapabilityRegistryNodeOperator) (*types.Transaction, error) { + return _CapabilityRegistry.Contract.UpdateNodeOperators(&_CapabilityRegistry.TransactOpts, nodeOperatorIds, nodeOperators) +} + +func (_CapabilityRegistry *CapabilityRegistryTransactorSession) UpdateNodeOperators(nodeOperatorIds []*big.Int, nodeOperators []CapabilityRegistryNodeOperator) (*types.Transaction, error) { + return _CapabilityRegistry.Contract.UpdateNodeOperators(&_CapabilityRegistry.TransactOpts, nodeOperatorIds, nodeOperators) +} + type CapabilityRegistryCapabilityAddedIterator struct { Event *CapabilityRegistryCapabilityAdded @@ -724,6 +738,135 @@ func (_CapabilityRegistry *CapabilityRegistryFilterer) ParseNodeOperatorRemoved( return event, nil } +type CapabilityRegistryNodeOperatorUpdatedIterator struct { + Event *CapabilityRegistryNodeOperatorUpdated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *CapabilityRegistryNodeOperatorUpdatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(CapabilityRegistryNodeOperatorUpdated) + 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(CapabilityRegistryNodeOperatorUpdated) + 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 *CapabilityRegistryNodeOperatorUpdatedIterator) Error() error { + return it.fail +} + +func (it *CapabilityRegistryNodeOperatorUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type CapabilityRegistryNodeOperatorUpdated struct { + NodeOperatorId *big.Int + Admin common.Address + Name string + Raw types.Log +} + +func (_CapabilityRegistry *CapabilityRegistryFilterer) FilterNodeOperatorUpdated(opts *bind.FilterOpts, admin []common.Address) (*CapabilityRegistryNodeOperatorUpdatedIterator, error) { + + var adminRule []interface{} + for _, adminItem := range admin { + adminRule = append(adminRule, adminItem) + } + + logs, sub, err := _CapabilityRegistry.contract.FilterLogs(opts, "NodeOperatorUpdated", adminRule) + if err != nil { + return nil, err + } + return &CapabilityRegistryNodeOperatorUpdatedIterator{contract: _CapabilityRegistry.contract, event: "NodeOperatorUpdated", logs: logs, sub: sub}, nil +} + +func (_CapabilityRegistry *CapabilityRegistryFilterer) WatchNodeOperatorUpdated(opts *bind.WatchOpts, sink chan<- *CapabilityRegistryNodeOperatorUpdated, admin []common.Address) (event.Subscription, error) { + + var adminRule []interface{} + for _, adminItem := range admin { + adminRule = append(adminRule, adminItem) + } + + logs, sub, err := _CapabilityRegistry.contract.WatchLogs(opts, "NodeOperatorUpdated", adminRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(CapabilityRegistryNodeOperatorUpdated) + if err := _CapabilityRegistry.contract.UnpackLog(event, "NodeOperatorUpdated", 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 (_CapabilityRegistry *CapabilityRegistryFilterer) ParseNodeOperatorUpdated(log types.Log) (*CapabilityRegistryNodeOperatorUpdated, error) { + event := new(CapabilityRegistryNodeOperatorUpdated) + if err := _CapabilityRegistry.contract.UnpackLog(event, "NodeOperatorUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + type CapabilityRegistryOwnershipTransferRequestedIterator struct { Event *CapabilityRegistryOwnershipTransferRequested @@ -1004,6 +1147,8 @@ func (_CapabilityRegistry *CapabilityRegistry) ParseLog(log types.Log) (generate return _CapabilityRegistry.ParseNodeOperatorAdded(log) case _CapabilityRegistry.abi.Events["NodeOperatorRemoved"].ID: return _CapabilityRegistry.ParseNodeOperatorRemoved(log) + case _CapabilityRegistry.abi.Events["NodeOperatorUpdated"].ID: + return _CapabilityRegistry.ParseNodeOperatorUpdated(log) case _CapabilityRegistry.abi.Events["OwnershipTransferRequested"].ID: return _CapabilityRegistry.ParseOwnershipTransferRequested(log) case _CapabilityRegistry.abi.Events["OwnershipTransferred"].ID: @@ -1026,6 +1171,10 @@ func (CapabilityRegistryNodeOperatorRemoved) Topic() common.Hash { return common.HexToHash("0x1e5877d7b3001d1569bf733b76c7eceda58bd6c031e5b8d0b7042308ba2e9d4f") } +func (CapabilityRegistryNodeOperatorUpdated) Topic() common.Hash { + return common.HexToHash("0x14c8f513e8a6d86d2d16b0cb64976de4e72386c4f8068eca3b7354373f8fe97a") +} + func (CapabilityRegistryOwnershipTransferRequested) Topic() common.Hash { return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") } @@ -1059,6 +1208,8 @@ type CapabilityRegistryInterface interface { TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + UpdateNodeOperators(opts *bind.TransactOpts, nodeOperatorIds []*big.Int, nodeOperators []CapabilityRegistryNodeOperator) (*types.Transaction, error) + FilterCapabilityAdded(opts *bind.FilterOpts, capabilityId [][32]byte) (*CapabilityRegistryCapabilityAddedIterator, error) WatchCapabilityAdded(opts *bind.WatchOpts, sink chan<- *CapabilityRegistryCapabilityAdded, capabilityId [][32]byte) (event.Subscription, error) @@ -1077,6 +1228,12 @@ type CapabilityRegistryInterface interface { ParseNodeOperatorRemoved(log types.Log) (*CapabilityRegistryNodeOperatorRemoved, error) + FilterNodeOperatorUpdated(opts *bind.FilterOpts, admin []common.Address) (*CapabilityRegistryNodeOperatorUpdatedIterator, error) + + WatchNodeOperatorUpdated(opts *bind.WatchOpts, sink chan<- *CapabilityRegistryNodeOperatorUpdated, admin []common.Address) (event.Subscription, error) + + ParseNodeOperatorUpdated(log types.Log) (*CapabilityRegistryNodeOperatorUpdated, error) + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*CapabilityRegistryOwnershipTransferRequestedIterator, error) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *CapabilityRegistryOwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) 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 62a75a8a644..e137d35ea5e 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,4 @@ GETH_VERSION: 1.13.8 forwarder: ../../../contracts/solc/v0.8.19/KeystoneForwarder/KeystoneForwarder.abi ../../../contracts/solc/v0.8.19/KeystoneForwarder/KeystoneForwarder.bin b4c900aae9e022f01abbac7993d41f93912247613ac6270b0c4da4ef6f2016e3 -keystone_capability_registry: ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.abi ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.bin 9eaa36e45c1b33c1fa72b7e25d6d1ca351413225611abb84ae053270b569cca4 +keystone_capability_registry: ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.abi ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.bin 5f801185084a6d6bcc08e0a37574d80f5092bc0dcd40808e9e04804064db0a56 ocr3_capability: ../../../contracts/solc/v0.8.19/OCR3Capability/OCR3Capability.abi ../../../contracts/solc/v0.8.19/OCR3Capability/OCR3Capability.bin 9dcbdf55bd5729ba266148da3f17733eb592c871c2108ccca546618628fd9ad2 diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 08050c32167..3963538b2c3 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -21,7 +21,7 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/chainlink-automation v1.0.3 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 48a66de1ec3..daedbe64eb3 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1185,8 +1185,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs= github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73 h1:54hM3/SrOM166it2K35hGb5K7gQ49/Op0aHp9WkqpqU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb h1:nJ9dkgvX5vdpFWhYufnRUAiNvNHsXkoBL6C0bDerq/k= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee h1:eFuBKyEbL2b+eyfgV/Eu9+8HuCEev+IcBi+K9l1dG7g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee/go.mod h1:uATrrJ8IsuBkOBJ46USuf73gz9gZy5k5bzGE5/ji/rc= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo= diff --git a/core/services/chainlink/config_database_test.go b/core/services/chainlink/config_database_test.go index 9cde07c9a63..b52d17452aa 100644 --- a/core/services/chainlink/config_database_test.go +++ b/core/services/chainlink/config_database_test.go @@ -14,6 +14,8 @@ import ( func TestDatabaseConfig(t *testing.T) { opts := GeneralConfigOpts{ ConfigStrings: []string{fullTOML}, + SecretsStrings: []string{`[Database] +URL = "postgresql://doesnotexist:justtopassvalidationtests@localhost:5432/chainlink_na_test"`}, } cfg, err := opts.New() require.NoError(t, err) diff --git a/core/services/chainlink/mocks/relayer_chain_interoperators.go b/core/services/chainlink/mocks/relayer_chain_interoperators.go index 6e47f6a1f9b..5b0815b6569 100644 --- a/core/services/chainlink/mocks/relayer_chain_interoperators.go +++ b/core/services/chainlink/mocks/relayer_chain_interoperators.go @@ -43,6 +43,10 @@ func (f *FakeRelayerChainInteroperators) Get(id types.RelayID) (loop.Relayer, er panic("unimplemented") } +func (f *FakeRelayerChainInteroperators) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { + panic("unimplemented") +} + func (f *FakeRelayerChainInteroperators) Slice() []loop.Relayer { return f.Relayers } diff --git a/core/services/chainlink/relayer_chain_interoperators.go b/core/services/chainlink/relayer_chain_interoperators.go index e17d4d516e9..3ed3c3242ba 100644 --- a/core/services/chainlink/relayer_chain_interoperators.go +++ b/core/services/chainlink/relayer_chain_interoperators.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/smartcontractkit/chainlink-common/pkg/loop" + relay "github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/relay" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos/adapters" @@ -16,7 +17,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" ) var ErrNoSuchRelayer = errors.New("relayer does not exist") @@ -183,6 +183,17 @@ func (rs *CoreRelayerChainInteroperators) Get(id types.RelayID) (loop.Relayer, e return lr, nil } +func (rs *CoreRelayerChainInteroperators) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { + rs.mu.Lock() + defer rs.mu.Unlock() + result := make(map[types.RelayID]loop.Relayer) + for id, relayer := range rs.loopRelayers { + result[id] = relayer + } + + return result, nil +} + // LegacyEVMChains returns a container with all the evm chains // TODO BCF-2511 func (rs *CoreRelayerChainInteroperators) LegacyEVMChains() legacyevm.LegacyChainContainer { diff --git a/core/services/chainlink/relayer_factory.go b/core/services/chainlink/relayer_factory.go index 31645b7c54d..8bb06538f03 100644 --- a/core/services/chainlink/relayer_factory.go +++ b/core/services/chainlink/relayer_factory.go @@ -8,6 +8,7 @@ import ( "github.com/pelletier/go-toml/v2" "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/relay" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-cosmos/pkg/cosmos" @@ -23,7 +24,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" "github.com/smartcontractkit/chainlink/v2/plugins" diff --git a/core/services/job/spawner_test.go b/core/services/job/spawner_test.go index 7b4ab138e7c..ef55021dabf 100644 --- a/core/services/job/spawner_test.go +++ b/core/services/job/spawner_test.go @@ -73,6 +73,10 @@ func (g *relayGetter) Get(id types.RelayID) (loop.Relayer, error) { return evmrelayer.NewLoopRelayServerAdapter(g.r, g.e), nil } +func (g *relayGetter) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { + return map[types.RelayID]loop.Relayer{}, nil +} + func TestSpawner_CreateJobDeleteJob(t *testing.T) { t.Parallel() ctx := testutils.Context(t) diff --git a/core/services/ocr2/delegate.go b/core/services/ocr2/delegate.go index 4e1eb0cc623..7747cad3360 100644 --- a/core/services/ocr2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -25,26 +25,22 @@ import ( ocr2keepers20runner "github.com/smartcontractkit/chainlink-automation/pkg/v2/runner" ocr2keepers21config "github.com/smartcontractkit/chainlink-automation/pkg/v3/config" ocr2keepers21 "github.com/smartcontractkit/chainlink-automation/pkg/v3/plugin" - "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins/ocr3" - "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" - - "github.com/smartcontractkit/chainlink/v2/core/config/env" - - "github.com/smartcontractkit/chainlink-vrf/altbn_128" - dkgpkg "github.com/smartcontractkit/chainlink-vrf/dkg" - "github.com/smartcontractkit/chainlink-vrf/ocr2vrf" - commonlogger "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins" + "github.com/smartcontractkit/chainlink-common/pkg/loop/reportingplugins/ocr3" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/core" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" - + "github.com/smartcontractkit/chainlink-vrf/altbn_128" + dkgpkg "github.com/smartcontractkit/chainlink-vrf/dkg" + "github.com/smartcontractkit/chainlink-vrf/ocr2vrf" "github.com/smartcontractkit/chainlink/v2/core/bridges" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" coreconfig "github.com/smartcontractkit/chainlink/v2/core/config" + "github.com/smartcontractkit/chainlink/v2/core/config/env" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" @@ -71,7 +67,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" evmrelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" functionsRelay "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/functions" evmmercury "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" @@ -108,6 +103,7 @@ func (e ErrRelayNotEnabled) Error() string { type RelayGetter interface { Get(id types.RelayID) (loop.Relayer, error) + GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) } type Delegate struct { ds sqlutil.DataSource @@ -567,6 +563,11 @@ func (d *Delegate) newServicesGenericPlugin( return nil, ErrJobSpecNoRelayer{PluginName: pCfg.PluginName, Err: err} } + relayerSet, err := generic.NewRelayerSet(d.RelayGetter, jb.ExternalJobID, jb.ID, d.isNewlyCreatedJob) + if err != nil { + return nil, fmt.Errorf("failed to create relayer set: %w", err) + } + relayer, err := d.RelayGetter.Get(rid) if err != nil { return nil, ErrRelayNotEnabled{Err: err, Relay: spec.Relay, PluginName: pCfg.PluginName} @@ -620,7 +621,7 @@ func (d *Delegate) newServicesGenericPlugin( //TODO: remove this workaround when the EVM relayer is running inside of an LOOPP d.lggr.Info("provider is not a LOOPP provider, switching to provider server") - ps, err2 := relay.NewProviderServer(provider, types.OCR2PluginType(pCfg.ProviderType), d.lggr) + ps, err2 := loop.NewProviderServer(provider, types.OCR2PluginType(pCfg.ProviderType), d.lggr) if err2 != nil { return nil, fmt.Errorf("cannot start EVM provider server: %s", err2) } @@ -657,7 +658,7 @@ func (d *Delegate) newServicesGenericPlugin( switch pCfg.OCRVersion { case 2: plugin := reportingplugins.NewLOOPPService(pluginLggr, grpcOpts, cmdFn, pluginConfig, providerClientConn, pr, ta, - errorLog, keyValueStore) + errorLog, keyValueStore, relayerSet) oracleArgs := libocr2.OCR2OracleArgs{ BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, V2Bootstrappers: bootstrapPeers, @@ -683,7 +684,7 @@ func (d *Delegate) newServicesGenericPlugin( case 3: //OCR3 with OCR2 OnchainKeyring and ContractTransmitter plugin := ocr3.NewLOOPPService(pluginLggr, grpcOpts, cmdFn, pluginConfig, providerClientConn, pr, ta, errorLog, - capabilitiesRegistry, keyValueStore) + capabilitiesRegistry, keyValueStore, relayerSet) contractTransmitter := ocrcommon.NewOCR3ContractTransmitterAdapter(provider.ContractTransmitter()) oracleArgs := libocr2.OCR3OracleArgs[[]byte]{ BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, diff --git a/core/services/ocr2/plugins/generic/relayerset.go b/core/services/ocr2/plugins/generic/relayerset.go new file mode 100644 index 00000000000..0586f600c50 --- /dev/null +++ b/core/services/ocr2/plugins/generic/relayerset.go @@ -0,0 +1,87 @@ +package generic + +import ( + "context" + "fmt" + + "github.com/google/uuid" + + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" +) + +type RelayGetter interface { + GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) +} + +type RelayerSet struct { + wrappedRelayers map[types.RelayID]core.Relayer +} + +func NewRelayerSet(relayGetter RelayGetter, externalJobID uuid.UUID, jobID int32, isNew bool) (*RelayerSet, error) { + + wrappedRelayers := map[types.RelayID]core.Relayer{} + + relayers, err := relayGetter.GetIDToRelayerMap() + if err != nil { + return nil, fmt.Errorf("failed to get relayers: %w", err) + } + + for id, relayer := range relayers { + wrappedRelayers[id] = relayerWrapper{Relayer: relayer, ExternalJobID: externalJobID, JobID: jobID, New: isNew} + } + + return &RelayerSet{wrappedRelayers: wrappedRelayers}, nil +} + +func (r *RelayerSet) Get(_ context.Context, id types.RelayID) (core.Relayer, error) { + if relayer, ok := r.wrappedRelayers[id]; ok { + return relayer, nil + } + + return nil, fmt.Errorf("relayer with id %s not found", id) +} + +func (r *RelayerSet) List(_ context.Context, relayIDs ...types.RelayID) (map[types.RelayID]core.Relayer, error) { + + if len(relayIDs) == 0 { + return r.wrappedRelayers, nil + } + + filterer := map[types.RelayID]bool{} + for _, id := range relayIDs { + filterer[id] = true + } + + result := map[types.RelayID]core.Relayer{} + for id, relayer := range r.wrappedRelayers { + if _, ok := filterer[id]; ok { + result[id] = relayer + } + } + + return result, nil +} + +type relayerWrapper struct { + loop.Relayer + ExternalJobID uuid.UUID + JobID int32 + New bool // Whether this is a first time job add. +} + +func (r relayerWrapper) NewPluginProvider(ctx context.Context, rargs core.RelayArgs, pargs core.PluginArgs) (types.PluginProvider, error) { + + relayArgs := types.RelayArgs{ + ExternalJobID: r.ExternalJobID, + JobID: r.JobID, + ContractID: rargs.ContractID, + New: r.New, + RelayConfig: rargs.RelayConfig, + ProviderType: rargs.ProviderType, + MercuryCredentials: rargs.MercuryCredentials, + } + + return r.Relayer.NewPluginProvider(ctx, relayArgs, types.PluginArgs(pargs)) +} diff --git a/core/services/ocr2/plugins/generic/relayerset_test.go b/core/services/ocr2/plugins/generic/relayerset_test.go new file mode 100644 index 00000000000..9aef7e29d78 --- /dev/null +++ b/core/services/ocr2/plugins/generic/relayerset_test.go @@ -0,0 +1,173 @@ +package generic + +import ( + "context" + "fmt" + "math/big" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" +) + +func TestRelayerSet_List(t *testing.T) { + + testRelayersMap := map[types.RelayID]loop.Relayer{} + testRelayersMap[types.RelayID{Network: "N1", ChainID: "C1"}] = &TestRelayer{} + testRelayersMap[types.RelayID{Network: "N2", ChainID: "C2"}] = &TestRelayer{} + testRelayersMap[types.RelayID{Network: "N3", ChainID: "C3"}] = &TestRelayer{} + + testGetter := TestRelayGetter{relayers: testRelayersMap} + + relayerSet, err := NewRelayerSet(testGetter, uuid.New(), 1, true) + assert.NoError(t, err) + relayers, err := relayerSet.List(context.Background()) + assert.NoError(t, err) + + assert.Equal(t, len(relayers), 3) + + relayers, err = relayerSet.List(context.Background(), types.RelayID{Network: "N1", ChainID: "C1"}, types.RelayID{Network: "N3", ChainID: "C3"}) + assert.NoError(t, err) + + assert.Equal(t, len(relayers), 2) + + _, ok := relayers[types.RelayID{Network: "N1", ChainID: "C1"}] + assert.True(t, ok) + + _, ok = relayers[types.RelayID{Network: "N3", ChainID: "C3"}] + assert.True(t, ok) +} + +func TestRelayerSet_Get(t *testing.T) { + + testRelayersMap := map[types.RelayID]loop.Relayer{} + testRelayersMap[types.RelayID{Network: "N1", ChainID: "C1"}] = &TestRelayer{} + testRelayersMap[types.RelayID{Network: "N2", ChainID: "C2"}] = &TestRelayer{} + testRelayersMap[types.RelayID{Network: "N3", ChainID: "C3"}] = &TestRelayer{} + + testGetter := TestRelayGetter{relayers: testRelayersMap} + + relayerSet, err := NewRelayerSet(testGetter, uuid.New(), 1, true) + assert.NoError(t, err) + + _, err = relayerSet.Get(context.Background(), types.RelayID{Network: "N1", ChainID: "C1"}) + assert.NoError(t, err) + + _, err = relayerSet.Get(context.Background(), types.RelayID{Network: "N4", ChainID: "C4"}) + assert.NotNil(t, err) +} + +func TestRelayerSet_NewPluginProvider(t *testing.T) { + testRelayersMap := map[types.RelayID]loop.Relayer{} + testRelayer := &TestRelayer{} + testRelayersMap[types.RelayID{Network: "N1", ChainID: "C1"}] = testRelayer + testRelayersMap[types.RelayID{Network: "N2", ChainID: "C2"}] = &TestRelayer{} + testRelayersMap[types.RelayID{Network: "N3", ChainID: "C3"}] = &TestRelayer{} + + testGetter := TestRelayGetter{relayers: testRelayersMap} + + externalJobID := uuid.New() + relayerSet, err := NewRelayerSet(testGetter, externalJobID, 1, true) + assert.NoError(t, err) + + relayer, err := relayerSet.Get(context.Background(), types.RelayID{Network: "N1", ChainID: "C1"}) + assert.NoError(t, err) + + _, err = relayer.NewPluginProvider(context.Background(), core.RelayArgs{ + ContractID: "c1", + RelayConfig: []byte("relayconfig"), + ProviderType: "p1", + MercuryCredentials: &types.MercuryCredentials{ + LegacyURL: "legacy", + URL: "url", + Username: "user", + Password: "pass", + }, + }, core.PluginArgs{ + TransmitterID: "t1", + PluginConfig: []byte("pluginconfig"), + }) + assert.NoError(t, err) + + assert.Equal(t, types.RelayArgs{ + ExternalJobID: externalJobID, + JobID: 1, + ContractID: "c1", + New: true, + RelayConfig: []byte("relayconfig"), + ProviderType: "p1", + MercuryCredentials: &types.MercuryCredentials{ + LegacyURL: "legacy", + URL: "url", + Username: "user", + Password: "pass", + }, + }, testRelayer.relayArgs) + + assert.Equal(t, types.PluginArgs{ + TransmitterID: "t1", + PluginConfig: []byte("pluginconfig"), + }, testRelayer.pluginArgs) +} + +type TestRelayGetter struct { + relayers map[types.RelayID]loop.Relayer +} + +func (t TestRelayGetter) Get(id types.RelayID) (loop.Relayer, error) { + if relayer, ok := t.relayers[id]; ok { + return relayer, nil + } + + return nil, fmt.Errorf("relayer with id %s not found", id) +} + +func (t TestRelayGetter) GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) { + return t.relayers, nil +} + +type TestRelayer struct { + relayArgs types.RelayArgs + pluginArgs types.PluginArgs +} + +func (t *TestRelayer) NewPluginProvider(ctx context.Context, args types.RelayArgs, args2 types.PluginArgs) (types.PluginProvider, error) { + t.relayArgs = args + t.pluginArgs = args2 + + return nil, nil +} + +func (t *TestRelayer) Name() string { panic("implement me") } + +func (t *TestRelayer) Start(ctx context.Context) error { panic("implement me") } + +func (t *TestRelayer) Close() error { panic("implement me") } + +func (t *TestRelayer) Ready() error { panic("implement me") } + +func (t *TestRelayer) HealthReport() map[string]error { panic("implement me") } + +func (t *TestRelayer) GetChainStatus(ctx context.Context) (types.ChainStatus, error) { + panic("implement me") +} + +func (t *TestRelayer) ListNodeStatuses(ctx context.Context, pageSize int32, pageToken string) (stats []types.NodeStatus, nextPageToken string, total int, err error) { + panic("implement me") +} + +func (t *TestRelayer) Transact(ctx context.Context, from, to string, amount *big.Int, balanceCheck bool) error { + panic("implement me") +} + +func (t *TestRelayer) NewConfigProvider(ctx context.Context, args types.RelayArgs) (types.ConfigProvider, error) { + panic("implement me") +} + +func (t *TestRelayer) NewLLOProvider(ctx context.Context, args types.RelayArgs, args2 types.PluginArgs) (types.LLOProvider, error) { + panic("implement me") +} diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go index b07b08d3354..e2c1a1531e2 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/logprovider/provider.go @@ -42,7 +42,7 @@ var ( readJobQueueSize = 64 readLogsTimeout = 10 * time.Second - readMaxBatchSize = 32 + readMaxBatchSize = 56 // reorgBuffer is the number of blocks to add as a buffer to the block range when reading logs. reorgBuffer = int64(32) readerThreads = 4 diff --git a/core/services/ocrbootstrap/delegate.go b/core/services/ocrbootstrap/delegate.go index 0bb7a0ca2ba..4f927faa009 100644 --- a/core/services/ocrbootstrap/delegate.go +++ b/core/services/ocrbootstrap/delegate.go @@ -22,6 +22,7 @@ import ( type RelayGetter interface { Get(types.RelayID) (loop.Relayer, error) + GetIDToRelayerMap() (map[types.RelayID]loop.Relayer, error) } // Delegate creates Bootstrap jobs diff --git a/core/services/relay/evm/contract_transmitter.go b/core/services/relay/evm/contract_transmitter.go index af0f83f6979..724bbbe4aa0 100644 --- a/core/services/relay/evm/contract_transmitter.go +++ b/core/services/relay/evm/contract_transmitter.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/hex" "math/big" + "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -63,13 +64,29 @@ func NewOCRContractTransmitter( lp logpoller.LogPoller, lggr logger.Logger, reportToEvmTxMeta ReportToEthMetadata, +) (*contractTransmitter, error) { + return NewOCRContractTransmitterWithRetention(ctx, address, caller, contractABI, transmitter, lp, lggr, reportToEvmTxMeta, 0) +} + +func NewOCRContractTransmitterWithRetention( + ctx context.Context, + address gethcommon.Address, + caller contractReader, + contractABI abi.ABI, + transmitter Transmitter, + lp logpoller.LogPoller, + lggr logger.Logger, + reportToEvmTxMeta ReportToEthMetadata, + retention time.Duration, ) (*contractTransmitter, error) { transmitted, ok := contractABI.Events["Transmitted"] if !ok { return nil, errors.New("invalid ABI, missing transmitted") } - err := lp.RegisterFilter(ctx, logpoller.Filter{Name: transmitterFilterName(address), EventSigs: []common.Hash{transmitted.ID}, Addresses: []common.Address{address}}) + // TODO It would be better to keep MaxLogsKept = 1 for the OCR contract transmitter instead of Retention. We are always interested only in the latest log. + // Although MaxLogsKept is present in the Filter struct, it is not supported by LogPoller yet. + err := lp.RegisterFilter(ctx, logpoller.Filter{Name: transmitterFilterName(address), EventSigs: []common.Hash{transmitted.ID}, Addresses: []common.Address{address}, Retention: retention}) if err != nil { return nil, err } diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 737a8e7561e..585d20df3ab 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" "sync" + "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -172,7 +173,7 @@ func (r *Relayer) NewPluginProvider(rargs commontypes.RelayArgs, pargs commontyp return nil, err } - transmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, pargs.TransmitterID, r.ks.Eth(), configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI) + transmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, pargs.TransmitterID, r.ks.Eth(), configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI, 0) if err != nil { return nil, err } @@ -476,7 +477,7 @@ type configTransmitterOpts struct { } // newOnChainContractTransmitter creates a new contract transmitter. -func newOnChainContractTransmitter(ctx context.Context, lggr logger.Logger, rargs commontypes.RelayArgs, transmitterID string, ethKeystore keystore.Eth, configWatcher *configWatcher, opts configTransmitterOpts, transmissionContractABI abi.ABI) (*contractTransmitter, error) { +func newOnChainContractTransmitter(ctx context.Context, lggr logger.Logger, rargs commontypes.RelayArgs, transmitterID string, ethKeystore keystore.Eth, configWatcher *configWatcher, opts configTransmitterOpts, transmissionContractABI abi.ABI, transmissionContractRetention time.Duration) (*contractTransmitter, error) { var relayConfig types.RelayConfig if err := json.Unmarshal(rargs.RelayConfig, &relayConfig); err != nil { return nil, err @@ -540,7 +541,7 @@ func newOnChainContractTransmitter(ctx context.Context, lggr logger.Logger, rarg return nil, pkgerrors.Wrap(err, "failed to create transmitter") } - return NewOCRContractTransmitter( + return NewOCRContractTransmitterWithRetention( ctx, configWatcher.contractAddress, configWatcher.chain.Client(), @@ -549,6 +550,7 @@ func newOnChainContractTransmitter(ctx context.Context, lggr logger.Logger, rarg configWatcher.chain.LogPoller(), lggr, nil, + transmissionContractRetention, ) } @@ -578,7 +580,7 @@ func (r *Relayer) NewMedianProvider(rargs commontypes.RelayArgs, pargs commontyp reportCodec := evmreportcodec.ReportCodec{} - contractTransmitter, err := newOnChainContractTransmitter(ctx, lggr, rargs, pargs.TransmitterID, r.ks.Eth(), configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI) + contractTransmitter, err := newOnChainContractTransmitter(ctx, lggr, rargs, pargs.TransmitterID, r.ks.Eth(), configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI, 0) if err != nil { return nil, err } diff --git a/core/services/relay/evm/loop_impl.go b/core/services/relay/evm/loop_impl.go index 57a09dd49ae..7f9d405847d 100644 --- a/core/services/relay/evm/loop_impl.go +++ b/core/services/relay/evm/loop_impl.go @@ -2,9 +2,9 @@ package evm import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" + relay "github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/relay" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" - "github.com/smartcontractkit/chainlink/v2/core/services/relay" ) //go:generate mockery --quiet --name LoopRelayAdapter --output ./mocks/ --case=underscore diff --git a/core/services/relay/evm/ocr2keeper.go b/core/services/relay/evm/ocr2keeper.go index 78f4b43b43f..a839ce8430e 100644 --- a/core/services/relay/evm/ocr2keeper.go +++ b/core/services/relay/evm/ocr2keeper.go @@ -91,7 +91,7 @@ func (r *ocr2keeperRelayer) NewOCR2KeeperProvider(rargs commontypes.RelayArgs, p } gasLimit := cfgWatcher.chain.Config().EVM().OCR2().Automation().GasLimit() - contractTransmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, pargs.TransmitterID, r.ethKeystore, cfgWatcher, configTransmitterOpts{pluginGasLimit: &gasLimit}, OCR2AggregatorTransmissionContractABI) + contractTransmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, pargs.TransmitterID, r.ethKeystore, cfgWatcher, configTransmitterOpts{pluginGasLimit: &gasLimit}, OCR2AggregatorTransmissionContractABI, 0) if err != nil { return nil, err } diff --git a/core/services/relay/evm/ocr2vrf.go b/core/services/relay/evm/ocr2vrf.go index a108151be47..b83ce0fd81e 100644 --- a/core/services/relay/evm/ocr2vrf.go +++ b/core/services/relay/evm/ocr2vrf.go @@ -64,7 +64,7 @@ func (r *ocr2vrfRelayer) NewDKGProvider(rargs commontypes.RelayArgs, pargs commo if err != nil { return nil, err } - contractTransmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, pargs.TransmitterID, r.ethKeystore, configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI) + contractTransmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, pargs.TransmitterID, r.ethKeystore, configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI, 0) if err != nil { return nil, err } @@ -91,7 +91,7 @@ func (r *ocr2vrfRelayer) NewOCR2VRFProvider(rargs commontypes.RelayArgs, pargs c if err != nil { return nil, err } - contractTransmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, pargs.TransmitterID, r.ethKeystore, configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI) + contractTransmitter, err := newOnChainContractTransmitter(ctx, r.lggr, rargs, pargs.TransmitterID, r.ethKeystore, configWatcher, configTransmitterOpts{}, OCR2AggregatorTransmissionContractABI, 0) if err != nil { return nil, err } diff --git a/core/services/relay/evm/relayer_extender.go b/core/services/relay/evm/relayer_extender.go index 83f03b47f9e..5f49a0b16c9 100644 --- a/core/services/relay/evm/relayer_extender.go +++ b/core/services/relay/evm/relayer_extender.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "go.uber.org/multierr" - "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/loop/adapters/relay" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" @@ -19,7 +19,7 @@ import ( var ErrNoChains = errors.New("no EVM chains loaded") type EVMChainRelayerExtender interface { - loop.RelayerExt + relay.RelayerExt Chain() legacyevm.Chain } diff --git a/core/services/relay/grpc_provider_server.go b/core/services/relay/grpc_provider_server.go deleted file mode 100644 index 67bbb8c6c2a..00000000000 --- a/core/services/relay/grpc_provider_server.go +++ /dev/null @@ -1,68 +0,0 @@ -package relay - -import ( - "context" - "net" - - "go.uber.org/multierr" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/smartcontractkit/chainlink-common/pkg/loop" - "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -type ProviderServer struct { - s *grpc.Server - lis net.Listener - lggr logger.Logger - conns []*grpc.ClientConn -} - -func (p *ProviderServer) Start(ctx context.Context) error { - p.serve() - return nil -} - -func (p *ProviderServer) Close() error { - var err error - for _, c := range p.conns { - err = multierr.Combine(err, c.Close()) - } - p.s.Stop() - return err -} - -func (p *ProviderServer) GetConn() (*grpc.ClientConn, error) { - cc, err := grpc.Dial(p.lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) - p.conns = append(p.conns, cc) - return cc, err -} - -// NewProviderServer creates a GRPC server that will wrap a provider, this is a workaround to test the Node API PoC until the EVM relayer is loopifyed -func NewProviderServer(p types.PluginProvider, pType types.OCR2PluginType, lggr logger.Logger) (*ProviderServer, error) { - lis, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return nil, err - } - ps := ProviderServer{ - s: grpc.NewServer(), - lis: lis, - lggr: lggr.Named("EVM.ProviderServer"), - } - err = loop.RegisterStandAloneProvider(ps.s, p, pType) - if err != nil { - return nil, err - } - - return &ps, nil -} - -func (p *ProviderServer) serve() { - go func() { - if err := p.s.Serve(p.lis); err != nil { - p.lggr.Errorf("Failed to serve EVM provider server: %v", err) - } - }() -} diff --git a/core/services/relay/grpc_provider_server_test.go b/core/services/relay/grpc_provider_server_test.go deleted file mode 100644 index 72bbbca0f44..00000000000 --- a/core/services/relay/grpc_provider_server_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package relay - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -func TestProviderServer(t *testing.T) { - r := &mockRelayer{} - sa := NewServerAdapter(r, mockRelayerExt{}) - mp, _ := sa.NewPluginProvider(testutils.Context(t), types.RelayArgs{ProviderType: string(types.Median)}, types.PluginArgs{}) - - lggr := logger.TestLogger(t) - _, err := NewProviderServer(mp, "unsupported-type", lggr) - require.ErrorContains(t, err, "unsupported-type") - - ps, err := NewProviderServer(staticMedianProvider{}, types.Median, lggr) - require.NoError(t, err) - - _, err = ps.GetConn() - require.NoError(t, err) -} diff --git a/core/services/relay/relay.go b/core/services/relay/relay.go deleted file mode 100644 index b4cc4517390..00000000000 --- a/core/services/relay/relay.go +++ /dev/null @@ -1,38 +0,0 @@ -package relay - -import ( - "context" - "fmt" - - "github.com/smartcontractkit/chainlink-common/pkg/loop" - "github.com/smartcontractkit/chainlink-common/pkg/types" -) - -// ServerAdapter extends [loop.RelayerAdapter] by overriding NewPluginProvider to dispatches calls according to `RelayArgs.ProviderType`. -// This should only be used to adapt relayers not running via GRPC in a LOOPP. -type ServerAdapter struct { - loop.RelayerAdapter -} - -// NewServerAdapter returns a new ServerAdapter. -func NewServerAdapter(r types.Relayer, e loop.RelayerExt) *ServerAdapter { //nolint:staticcheck - return &ServerAdapter{RelayerAdapter: loop.RelayerAdapter{Relayer: r, RelayerExt: e}} -} - -func (r *ServerAdapter) NewPluginProvider(ctx context.Context, rargs types.RelayArgs, pargs types.PluginArgs) (types.PluginProvider, error) { - switch types.OCR2PluginType(rargs.ProviderType) { - case types.Median: - return r.NewMedianProvider(ctx, rargs, pargs) - case types.Functions: - return r.NewFunctionsProvider(ctx, rargs, pargs) - case types.Mercury: - return r.NewMercuryProvider(ctx, rargs, pargs) - case types.OCR2Keeper: - return r.NewAutomationProvider(ctx, rargs, pargs) - case types.DKG, types.OCR2VRF, types.GenericPlugin: - return r.RelayerAdapter.NewPluginProvider(ctx, rargs, pargs) - case types.LLO, types.CCIPCommit, types.CCIPExecution: - return nil, fmt.Errorf("provider type not supported: %s", rargs.ProviderType) - } - return nil, fmt.Errorf("provider type not recognized: %s", rargs.ProviderType) -} diff --git a/core/services/relay/relay_test.go b/core/services/relay/relay_test.go deleted file mode 100644 index c0b2248ed1a..00000000000 --- a/core/services/relay/relay_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package relay - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" - ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - - "github.com/smartcontractkit/chainlink-common/pkg/loop" - "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" -) - -type staticMedianProvider struct { -} - -var _ types.MedianProvider = staticMedianProvider{} - -// ContractConfigTracker implements types.MedianProvider. -func (s staticMedianProvider) ContractConfigTracker() ocr2types.ContractConfigTracker { - return nil -} - -// ContractTransmitter implements types.MedianProvider. -func (s staticMedianProvider) ContractTransmitter() ocr2types.ContractTransmitter { - return nil -} - -// MedianContract implements types.MedianProvider. -func (s staticMedianProvider) MedianContract() median.MedianContract { - return nil -} - -// OffchainConfigDigester implements types.MedianProvider. -func (s staticMedianProvider) OffchainConfigDigester() ocr2types.OffchainConfigDigester { - return nil -} - -// OnchainConfigCodec implements types.MedianProvider. -func (s staticMedianProvider) OnchainConfigCodec() median.OnchainConfigCodec { - return nil -} - -// ReportCodec implements types.MedianProvider. -func (s staticMedianProvider) ReportCodec() median.ReportCodec { - return nil -} - -// ChainReader implements types.MedianProvider. -func (s staticMedianProvider) ChainReader() types.ChainReader { - return nil -} - -// Close implements types.MedianProvider. -func (s staticMedianProvider) Close() error { - return nil -} - -// Codec implements types.MedianProvider. -func (s staticMedianProvider) Codec() types.Codec { - return nil -} - -// HealthReport implements types.MedianProvider. -func (s staticMedianProvider) HealthReport() map[string]error { - return nil -} - -// Name implements types.MedianProvider. -func (s staticMedianProvider) Name() string { - return "" -} - -// Ready implements types.MedianProvider. -func (s staticMedianProvider) Ready() error { - return nil -} - -// Start implements types.MedianProvider. -func (s staticMedianProvider) Start(context.Context) error { - return nil -} - -type staticFunctionsProvider struct { - types.FunctionsProvider -} - -type staticMercuryProvider struct { - types.MercuryProvider -} - -type staticAutomationProvider struct { - types.AutomationProvider -} - -type staticPluginProvider struct { - types.PluginProvider -} - -type mockRelayer struct { - types.Relayer -} - -func (m *mockRelayer) NewMedianProvider(rargs types.RelayArgs, pargs types.PluginArgs) (types.MedianProvider, error) { - return staticMedianProvider{}, nil -} - -func (m *mockRelayer) NewFunctionsProvider(rargs types.RelayArgs, pargs types.PluginArgs) (types.FunctionsProvider, error) { - return staticFunctionsProvider{}, nil -} - -func (m *mockRelayer) NewMercuryProvider(rargs types.RelayArgs, pargs types.PluginArgs) (types.MercuryProvider, error) { - return staticMercuryProvider{}, nil -} - -func (m *mockRelayer) NewAutomationProvider(rargs types.RelayArgs, pargs types.PluginArgs) (types.AutomationProvider, error) { - return staticAutomationProvider{}, nil -} - -func (m *mockRelayer) NewPluginProvider(rargs types.RelayArgs, pargs types.PluginArgs) (types.PluginProvider, error) { - return staticPluginProvider{}, nil -} - -type mockRelayerExt struct { - loop.RelayerExt -} - -func isType[T any](p any) bool { - _, ok := p.(T) - return ok -} - -func TestRelayerServerAdapter(t *testing.T) { - r := &mockRelayer{} - sa := NewServerAdapter(r, mockRelayerExt{}) - - testCases := []struct { - ProviderType string - Test func(p any) bool - Error string - }{ - { - ProviderType: string(types.Median), - Test: isType[types.MedianProvider], - }, - { - ProviderType: string(types.Functions), - Test: isType[types.FunctionsProvider], - }, - { - ProviderType: string(types.Mercury), - Test: isType[types.MercuryProvider], - }, - { - ProviderType: string(types.CCIPCommit), - Error: "provider type not supported", - }, - { - ProviderType: string(types.CCIPExecution), - Error: "provider type not supported", - }, - { - ProviderType: "unknown", - Error: "provider type not recognized", - }, - { - ProviderType: string(types.GenericPlugin), - Test: isType[types.PluginProvider], - }, - } - - ctx := testutils.Context(t) - for _, tc := range testCases { - pp, err := sa.NewPluginProvider( - ctx, - types.RelayArgs{ProviderType: tc.ProviderType}, - types.PluginArgs{}, - ) - - if tc.Error != "" { - assert.ErrorContains(t, err, tc.Error) - } else { - assert.NoError(t, err) - assert.True(t, tc.Test(pp)) - } - } -} diff --git a/core/services/workflows/engine.go b/core/services/workflows/engine.go index 0ecc311acac..040e123567e 100644 --- a/core/services/workflows/engine.go +++ b/core/services/workflows/engine.go @@ -45,6 +45,14 @@ type Engine struct { // Used for testing to wait for an execution to complete xxxExecutionFinished chan string + // testing lifecycle hook to signal initialization status + afterInit func(success bool) + // Used for testing to control the number of retries + // we'll do when initializing the engine. + maxRetries int + // Used for testing to control the retry interval + // when initializing the engine. + retryMs int } func (e *Engine) Start(ctx context.Context) error { @@ -60,94 +68,67 @@ func (e *Engine) Start(ctx context.Context) error { }) } -// init does the following: -// -// 1. Resolves the underlying capability for each trigger -// 2. Registers each step's capability to this workflow -// 3. Registers for trigger events now that all capabilities are resolved +// resolveWorkflowCapabilities does the following: // -// Steps 1 and 2 are retried every 5 seconds until successful. -func (e *Engine) init(ctx context.Context) { - defer e.wg.Done() - - retrySec := 5 - ticker := time.NewTicker(time.Duration(retrySec) * time.Second) - defer ticker.Stop() - -LOOP: - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - initSuccessful := true - // Resolve the underlying capability for each trigger - for _, t := range e.workflow.triggers { - tg, err := e.registry.GetTrigger(ctx, t.Type) - if err != nil { - initSuccessful = false - e.logger.Errorf("failed to get trigger capability: %s, retrying in %d seconds", err, retrySec) - continue - } - t.trigger = tg - } - if !initSuccessful { - continue - } - - // Walk the graph and initialize each step. - // This means: - // - fetching the capability - // - register the capability to this workflow - // - initializing the step's executionStrategy - err := e.workflow.walkDo(keywordTrigger, func(s *step) error { - // The graph contains a dummy step for triggers, but - // we handle triggers separately since there might be more than one. - if s.Ref == keywordTrigger { - return nil - } - - err := e.initializeCapability(ctx, s, retrySec) - if err != nil { - return err - } - - return e.initializeExecutionStrategy(s) - }) - if err != nil { - initSuccessful = false - e.logger.Error(err) - } - - if initSuccessful { - break LOOP - } +// 1. Resolves the underlying capability for each trigger +// 2. Registers each step's capability to this workflow +func (e *Engine) resolveWorkflowCapabilities(ctx context.Context) error { + // + // Step 1. Resolve the underlying capability for each trigger + // + triggersInitialized := true + for _, t := range e.workflow.triggers { + tg, err := e.registry.GetTrigger(ctx, t.Type) + if err != nil { + e.logger.Errorf("failed to get trigger capability: %s", err) + // we don't immediately return here, since we want to retry all triggers + // to notify the user of all errors at once. + triggersInitialized = false + } else { + t.trigger = tg } } + if !triggersInitialized { + return fmt.Errorf("failed to resolve triggers") + } + + // Step 2. Walk the graph and register each step's capability to this workflow + // + // This means: + // - fetching the capability + // - register the capability to this workflow + // - initializing the step's executionStrategy + capabilityRegistrationErr := e.workflow.walkDo(keywordTrigger, func(s *step) error { + // The graph contains a dummy step for triggers, but + // we handle triggers separately since there might be more than one + // trigger registered to a workflow. + if s.Ref == keywordTrigger { + return nil + } - // We have all needed capabilities, now we can register for trigger events - for _, t := range e.workflow.triggers { - err := e.registerTrigger(ctx, t) + err := e.initializeCapability(ctx, s) if err != nil { - e.logger.Errorf("failed to register trigger: %s", err) + return err } - } - e.logger.Info("engine initialized") + return e.initializeExecutionStrategy(s) + }) + + return capabilityRegistrationErr } -func (e *Engine) initializeCapability(ctx context.Context, s *step, retrySec int) error { +func (e *Engine) initializeCapability(ctx context.Context, s *step) error { // If the capability already exists, that means we've already registered it if s.capability != nil { return nil } - cp, innerErr := e.registry.Get(ctx, s.Type) - if innerErr != nil { - return fmt.Errorf("failed to get capability with ref %s: %s, retrying in %d seconds", s.Type, innerErr, retrySec) + cp, err := e.registry.Get(ctx, s.Type) + if err != nil { + return fmt.Errorf("failed to get capability with ref %s: %s", s.Type, err) } - // We only need to configure actions, consensus and targets here, and + // We configure actions, consensus and targets here, and // they all satisfy the `CallbackCapability` interface cc, ok := cp.(capabilities.CallbackCapability) if !ok { @@ -155,29 +136,65 @@ func (e *Engine) initializeCapability(ctx context.Context, s *step, retrySec int } if s.config == nil { - configMap, ierr := values.NewMap(s.Config) - if ierr != nil { - return fmt.Errorf("failed to convert config to values.Map: %s", ierr) + configMap, newMapErr := values.NewMap(s.Config) + if newMapErr != nil { + return fmt.Errorf("failed to convert config to values.Map: %s", newMapErr) } s.config = configMap } - reg := capabilities.RegisterToWorkflowRequest{ + registrationRequest := capabilities.RegisterToWorkflowRequest{ Metadata: capabilities.RegistrationMetadata{ WorkflowID: e.workflow.id, }, Config: s.config, } - innerErr = cc.RegisterToWorkflow(ctx, reg) - if innerErr != nil { - return fmt.Errorf("failed to register to workflow (%+v): %w", reg, innerErr) + err = cc.RegisterToWorkflow(ctx, registrationRequest) + if err != nil { + return fmt.Errorf("failed to register to workflow (%+v): %w", registrationRequest, err) } s.capability = cc return nil } +// init does the following: +// +// 1. Resolves the underlying capability for each trigger +// 2. Registers each step's capability to this workflow +// 3. Registers for trigger events now that all capabilities are resolved +// +// Steps 1 and 2 are retried every 5 seconds until successful. +func (e *Engine) init(ctx context.Context) { + defer e.wg.Done() + + retryErr := retryable(ctx, e.logger, e.retryMs, e.maxRetries, func() error { + err := e.resolveWorkflowCapabilities(ctx) + if err != nil { + return fmt.Errorf("failed to resolve workflow: %s", err) + } + return nil + }) + + if retryErr != nil { + e.logger.Errorf("initialization failed: %s", retryErr) + e.afterInit(false) + return + } + + e.logger.Debug("capabilities resolved, registering triggers") + for _, t := range e.workflow.triggers { + err := e.registerTrigger(ctx, t) + if err != nil { + e.logger.Errorf("failed to register trigger: %s", err) + } + } + + e.logger.Info("engine initialized") + e.afterInit(true) +} + // initializeExecutionStrategy for `step`. // Broadly speaking, we'll use `immediateExecution` for non-target steps // and `scheduledExecution` for targets. If we don't have the necessary @@ -609,6 +626,7 @@ func (e *Engine) deregisterTrigger(ctx context.Context, t *triggerCapability) er func (e *Engine) Close() error { return e.StopOnce("Engine", func() error { + e.logger.Info("shutting down engine") ctx := context.Background() // To shut down the engine, we'll start by deregistering // any triggers to ensure no new executions are triggered, @@ -668,6 +686,11 @@ type Config struct { NewWorkerTimeout time.Duration DONInfo *capabilities.DON PeerID func() *p2ptypes.PeerID + + // For testing purposes only + maxRetries int + retryMs int + afterInit func(success bool) } const ( @@ -689,6 +712,14 @@ func NewEngine(cfg Config) (engine *Engine, err error) { cfg.NewWorkerTimeout = defaultNewWorkerTimeout } + if cfg.retryMs == 0 { + cfg.retryMs = 5000 + } + + if cfg.afterInit == nil { + cfg.afterInit = func(success bool) {} + } + // TODO: validation of the workflow spec // We'll need to check, among other things: // - that there are no step `ref` called `trigger` as this is reserved for any triggers @@ -718,14 +749,18 @@ func NewEngine(cfg Config) (engine *Engine, err error) { DON: cfg.DONInfo, PeerID: cfg.PeerID, }, - executionStates: newInMemoryStore(), - pendingStepRequests: make(chan stepRequest, cfg.QueueSize), - newWorkerCh: newWorkerCh, - stepUpdateCh: make(chan stepState), - triggerEvents: make(chan capabilities.CapabilityResponse), - stopCh: make(chan struct{}), - newWorkerTimeout: cfg.NewWorkerTimeout, + executionStates: newInMemoryStore(), + pendingStepRequests: make(chan stepRequest, cfg.QueueSize), + newWorkerCh: newWorkerCh, + stepUpdateCh: make(chan stepState), + triggerEvents: make(chan capabilities.CapabilityResponse), + stopCh: make(chan struct{}), + newWorkerTimeout: cfg.NewWorkerTimeout, + // For testing purposes only xxxExecutionFinished: make(chan string), + afterInit: cfg.afterInit, + maxRetries: cfg.maxRetries, + retryMs: cfg.retryMs, } return engine, nil } diff --git a/core/services/workflows/engine_test.go b/core/services/workflows/engine_test.go index d82c9d4b7d2..4821b5800c7 100644 --- a/core/services/workflows/engine_test.go +++ b/core/services/workflows/engine_test.go @@ -65,6 +65,44 @@ targets: abi: "receive(report bytes)" ` +// newTestEngine creates a new engine with some test defaults. +func newTestEngine(t *testing.T, reg *coreCap.Registry, spec string) (eng *Engine, initFailed chan struct{}) { + peerID := p2ptypes.PeerID{} + initFailed = make(chan struct{}) + cfg := Config{ + Lggr: logger.TestLogger(t), + Registry: reg, + Spec: spec, + DONInfo: nil, + PeerID: func() *p2ptypes.PeerID { return &peerID }, + maxRetries: 1, + retryMs: 100, + afterInit: func(success bool) { + if !success { + close(initFailed) + } + }, + } + eng, err := NewEngine(cfg) + require.NoError(t, err) + return eng, initFailed +} + +// getExecutionId returns the execution id of the workflow that is +// currently being executed by the engine. +// +// If the engine fails to initialize, the test will fail rather +// than blocking indefinitely. +func getExecutionId(t *testing.T, eng *Engine, initFailed <-chan struct{}) string { + var eid string + select { + case <-initFailed: + t.FailNow() + case eid = <-eng.xxxExecutionFinished: + } + return eid +} + type mockCapability struct { capabilities.CapabilityInfo capabilities.CallbackExecutable @@ -148,23 +186,13 @@ func TestEngineWithHardcodedWorkflow(t *testing.T) { ) require.NoError(t, reg.Add(ctx, target2)) - lggr := logger.TestLogger(t) - peerID := p2ptypes.PeerID{} - cfg := Config{ - Lggr: lggr, - Registry: reg, - Spec: hardcodedWorkflow, - DONInfo: nil, - PeerID: func() *p2ptypes.PeerID { return &peerID }, - } - eng, err := NewEngine(cfg) - require.NoError(t, err) + eng, initFailed := newTestEngine(t, reg, hardcodedWorkflow) - err = eng.Start(ctx) + err := eng.Start(ctx) require.NoError(t, err) defer eng.Close() - eid := <-eng.xxxExecutionFinished + eid := getExecutionId(t, eng, initFailed) assert.Equal(t, cr, <-target1.response) assert.Equal(t, cr, <-target2.response) @@ -312,22 +340,13 @@ func TestEngine_ErrorsTheWorkflowIfAStepErrors(t *testing.T) { require.NoError(t, reg.Add(ctx, mockFailingConsensus())) require.NoError(t, reg.Add(ctx, mockTarget())) - peerID := p2ptypes.PeerID{} - cfg := Config{ - Lggr: logger.TestLogger(t), - Registry: reg, - Spec: simpleWorkflow, - DONInfo: nil, - PeerID: func() *p2ptypes.PeerID { return &peerID }, - } - eng, err := NewEngine(cfg) - require.NoError(t, err) + eng, initFailed := newTestEngine(t, reg, simpleWorkflow) - err = eng.Start(ctx) + err := eng.Start(ctx) require.NoError(t, err) defer eng.Close() - eid := <-eng.xxxExecutionFinished + eid := getExecutionId(t, eng, initFailed) state, err := eng.executionStates.get(ctx, eid) require.NoError(t, err) @@ -420,22 +439,12 @@ func TestEngine_MultiStepDependencies(t *testing.T) { action, out := mockAction() require.NoError(t, reg.Add(ctx, action)) - peerID := p2ptypes.PeerID{} - cfg := Config{ - Lggr: logger.TestLogger(t), - Registry: reg, - Spec: multiStepWorkflow, - DONInfo: nil, - PeerID: func() *p2ptypes.PeerID { return &peerID }, - } - eng, err := NewEngine(cfg) - require.NoError(t, err) - - err = eng.Start(ctx) + eng, initFailed := newTestEngine(t, reg, multiStepWorkflow) + err := eng.Start(ctx) require.NoError(t, err) defer eng.Close() - eid := <-eng.xxxExecutionFinished + eid := getExecutionId(t, eng, initFailed) state, err := eng.executionStates.get(ctx, eid) require.NoError(t, err) diff --git a/core/services/workflows/retry.go b/core/services/workflows/retry.go new file mode 100644 index 00000000000..e3f04353e7f --- /dev/null +++ b/core/services/workflows/retry.go @@ -0,0 +1,53 @@ +package workflows + +import ( + "context" + "fmt" + "time" + + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +// retryable is a helper function that retries a function until it succeeds. +// +// It will retry every `retryMs` milliseconds, up to `maxRetries` times. +// +// If `maxRetries` is 0, it will retry indefinitely. +// +// retryable will return an error in the following conditions: +// - the context is cancelled: the error returned is the context error +// - the retry limit has been hit: the error returned is the last error returned by `fn` +func retryable(ctx context.Context, lggr logger.Logger, retryMs int, maxRetries int, fn func() error) error { + ticker := time.NewTicker(time.Duration(retryMs) * time.Millisecond) + defer ticker.Stop() + + // immediately try once + err := fn() + if err == nil { + return nil + } + retries := 0 + + for { + // if maxRetries is 0, we'll retry indefinitely + if maxRetries > 0 { + if retries >= maxRetries { + lggr.Errorf("%s", err) + return fmt.Errorf("max retries reached, aborting") + } + } + lggr.Errorf("%s, retrying in %.2fs", err, float64(retryMs)/1000) + + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + err = fn() + if err == nil { + return nil + } + } + + retries++ + } +} diff --git a/core/services/workflows/retryable_test.go b/core/services/workflows/retryable_test.go new file mode 100644 index 00000000000..1a17ac55fae --- /dev/null +++ b/core/services/workflows/retryable_test.go @@ -0,0 +1,113 @@ +package workflows + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestRetryableZeroMaxRetries(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer cancel() + + fn := func() error { + return errors.New("test error") + } + + err := retryable(ctx, logger.NullLogger, 100, 0, fn) + assert.ErrorIs(t, err, context.DeadlineExceeded, "Expected context deadline exceeded error") +} + +func TestRetryableSuccessOnFirstAttempt(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + fn := func() error { + return nil + } + + err := retryable(ctx, logger.NullLogger, 100, 3, fn) + require.NoError(t, err, "Expected no error as function succeeds on first attempt") +} + +func TestRetryableSuccessAfterRetries(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + retries := 0 + fn := func() error { + if retries < 2 { + retries++ + return errors.New("test error") + } + return nil + } + + err := retryable(ctx, logger.NullLogger, 100, 5, fn) + assert.NoError(t, err, "Expected no error after successful retry") + assert.Equal(t, 2, retries, "Expected two retries before success") +} + +func TestRetryableErrorOnFirstTryNoRetries(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + fn := func() error { + return errors.New("immediate failure") + } + + err := retryable(ctx, logger.NullLogger, 100, 1, fn) + require.Error(t, err, "Expected an error on the first try with no retries allowed") + assert.Equal(t, "max retries reached, aborting", err.Error(), "Expected function to abort after the first try") +} + +func TestRetryableErrorAfterMultipleRetries(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + attempts := 0 + fn := func() error { + attempts++ + return errors.New("persistent error") + } + + maxRetries := 3 + err := retryable(ctx, logger.NullLogger, 100, maxRetries, fn) + require.Error(t, err, "Expected an error after multiple retries") + assert.Equal(t, "max retries reached, aborting", err.Error(), "Expected the max retries reached error message") + assert.Equal(t, maxRetries+1, attempts, "Expected the function to be executed retry + 1 times") +} + +func TestRetryableCancellationHandling(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + + fn := func() error { + return errors.New("test error") + } + + go func() { + time.Sleep(150 * time.Millisecond) + cancel() + }() + + err := retryable(ctx, logger.NullLogger, 100, 5, fn) + assert.ErrorIs(t, err, context.Canceled, "Expected context cancellation error") +} diff --git a/go.mod b/go.mod index 6e081467bc0..3e691e95405 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/chain-selectors v1.0.10 github.com/smartcontractkit/chainlink-automation v1.0.3 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 github.com/smartcontractkit/chainlink-feeds v0.0.0-20240422130241-13c17a91b2ab @@ -353,5 +353,4 @@ replace ( // until merged upstream: https://github.com/mwitkow/grpc-proxy/pull/69 github.com/mwitkow/grpc-proxy => github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f - ) diff --git a/go.sum b/go.sum index 50cb9f3b44f..90faeacb057 100644 --- a/go.sum +++ b/go.sum @@ -1180,8 +1180,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs= github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73 h1:54hM3/SrOM166it2K35hGb5K7gQ49/Op0aHp9WkqpqU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb h1:nJ9dkgvX5vdpFWhYufnRUAiNvNHsXkoBL6C0bDerq/k= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee h1:eFuBKyEbL2b+eyfgV/Eu9+8HuCEev+IcBi+K9l1dG7g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee/go.mod h1:uATrrJ8IsuBkOBJ46USuf73gz9gZy5k5bzGE5/ji/rc= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo= diff --git a/integration-tests/contracts/contract_deployer.go b/integration-tests/contracts/contract_deployer.go index c85c927b8d4..e2511c7292e 100644 --- a/integration-tests/contracts/contract_deployer.go +++ b/integration-tests/contracts/contract_deployer.go @@ -435,7 +435,7 @@ func (e *EthereumContractDeployer) DeployStakingEventsMock() (StakingEventsMock, if err != nil { return nil, err } - return &EthereumStakingEventsMock{ + return &LegacyEthereumStakingEventsMock{ client: e.client, eventsMock: instance.(*eth_contracts.StakingEventsMock), address: address, @@ -452,7 +452,7 @@ func (e *EthereumContractDeployer) DeployFunctionsV1EventsMock() (FunctionsV1Eve if err != nil { return nil, err } - return &EthereumFunctionsV1EventsMock{ + return &LegacyEthereumFunctionsV1EventsMock{ client: e.client, eventsMock: instance.(*functions_v1_events_mock.FunctionsV1EventsMock), address: address, @@ -469,7 +469,7 @@ func (e *EthereumContractDeployer) DeployKeeperRegistry11Mock() (KeeperRegistry1 if err != nil { return nil, err } - return &EthereumKeeperRegistry11Mock{ + return &LegacyEthereumKeeperRegistry11Mock{ client: e.client, registryMock: instance.(*keeper_registry_wrapper1_1_mock.KeeperRegistryMock), address: address, @@ -486,7 +486,7 @@ func (e *EthereumContractDeployer) DeployKeeperRegistrar12Mock() (KeeperRegistra if err != nil { return nil, err } - return &EthereumKeeperRegistrar12Mock{ + return &LegacyEthereumKeeperRegistrar12Mock{ client: e.client, registrarMock: instance.(*keeper_registrar_wrapper1_2_mock.KeeperRegistrarMock), address: address, @@ -503,7 +503,7 @@ func (e *EthereumContractDeployer) DeployKeeperGasWrapperMock() (KeeperGasWrappe if err != nil { return nil, err } - return &EthereumKeeperGasWrapperMock{ + return &LegacyEthereumKeeperGasWrapperMock{ client: e.client, gasWrapperMock: instance.(*gas_wrapper_mock.KeeperRegistryCheckUpkeepGasUsageWrapperMock), address: address, diff --git a/integration-tests/contracts/etherem_contracts_atlas_seth.go b/integration-tests/contracts/etherem_contracts_atlas_seth.go new file mode 100644 index 00000000000..c28e5198682 --- /dev/null +++ b/integration-tests/contracts/etherem_contracts_atlas_seth.go @@ -0,0 +1,392 @@ +package contracts + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/seth" + + eth_contracts "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" + "github.com/smartcontractkit/chainlink/integration-tests/wrappers" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/functions/generated/functions_v1_events_mock" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/gas_wrapper_mock" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registrar_wrapper1_2_mock" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/keeper_registry_wrapper1_1_mock" +) + +// EthereumFunctionsV1EventsMock represents the basic functions v1 events mock contract +type EthereumFunctionsV1EventsMock struct { + client *seth.Client + eventsMock *functions_v1_events_mock.FunctionsV1EventsMock + address *common.Address +} + +func (f *EthereumFunctionsV1EventsMock) Address() string { + return f.address.Hex() +} + +func (f *EthereumFunctionsV1EventsMock) EmitRequestProcessed(requestId [32]byte, subscriptionId uint64, totalCostJuels *big.Int, transmitter common.Address, resultCode uint8, response []byte, errByte []byte, callbackReturnData []byte) error { + _, err := f.client.Decode(f.eventsMock.EmitRequestProcessed(f.client.NewTXOpts(), requestId, subscriptionId, totalCostJuels, transmitter, resultCode, response, errByte, callbackReturnData)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitRequestStart(requestId [32]byte, donId [32]byte, subscriptionId uint64, subscriptionOwner common.Address, requestingContract common.Address, requestInitiator common.Address, data []byte, dataVersion uint16, callbackGasLimit uint32, estimatedTotalCostJuels *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitRequestStart(f.client.NewTXOpts(), requestId, donId, subscriptionId, subscriptionOwner, requestingContract, requestInitiator, data, dataVersion, callbackGasLimit, estimatedTotalCostJuels)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionCanceled(subscriptionId uint64, fundsRecipient common.Address, fundsAmount *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitSubscriptionCanceled(f.client.NewTXOpts(), subscriptionId, fundsRecipient, fundsAmount)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionConsumerAdded(subscriptionId uint64, consumer common.Address) error { + _, err := f.client.Decode(f.eventsMock.EmitSubscriptionConsumerAdded(f.client.NewTXOpts(), subscriptionId, consumer)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionConsumerRemoved(subscriptionId uint64, consumer common.Address) error { + _, err := f.client.Decode(f.eventsMock.EmitSubscriptionConsumerRemoved(f.client.NewTXOpts(), subscriptionId, consumer)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionCreated(subscriptionId uint64, owner common.Address) error { + _, err := f.client.Decode(f.eventsMock.EmitSubscriptionCreated(f.client.NewTXOpts(), subscriptionId, owner)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionFunded(subscriptionId uint64, oldBalance *big.Int, newBalance *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitSubscriptionFunded(f.client.NewTXOpts(), subscriptionId, oldBalance, newBalance)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionOwnerTransferred(subscriptionId uint64, from common.Address, to common.Address) error { + _, err := f.client.Decode(f.eventsMock.EmitSubscriptionOwnerTransferred(f.client.NewTXOpts(), subscriptionId, from, to)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionOwnerTransferRequested(subscriptionId uint64, from common.Address, to common.Address) error { + _, err := f.client.Decode(f.eventsMock.EmitSubscriptionOwnerTransferRequested(f.client.NewTXOpts(), subscriptionId, from, to)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitRequestNotProcessed(requestId [32]byte, coordinator common.Address, transmitter common.Address, resultCode uint8) error { + _, err := f.client.Decode(f.eventsMock.EmitRequestNotProcessed(f.client.NewTXOpts(), requestId, coordinator, transmitter, resultCode)) + return err +} + +func (f *EthereumFunctionsV1EventsMock) EmitContractUpdated(id [32]byte, from common.Address, to common.Address) error { + _, err := f.client.Decode(f.eventsMock.EmitContractUpdated(f.client.NewTXOpts(), id, from, to)) + return err +} + +// DeployFunctionsV1EventsMock deploys a new instance of the FunctionsV1EventsMock contract +func DeployFunctionsV1EventsMock(client *seth.Client) (FunctionsV1EventsMock, error) { + abi, err := functions_v1_events_mock.FunctionsV1EventsMockMetaData.GetAbi() + if err != nil { + return &EthereumFunctionsV1EventsMock{}, fmt.Errorf("failed to get FunctionsV1EventsMock ABI: %w", err) + } + client.ContractStore.AddABI("FunctionsV1EventsMock", *abi) + client.ContractStore.AddBIN("FunctionsV1EventsMock", common.FromHex(functions_v1_events_mock.FunctionsV1EventsMockMetaData.Bin)) + + data, err := client.DeployContract(client.NewTXOpts(), "FunctionsV1EventsMock", *abi, common.FromHex(functions_v1_events_mock.FunctionsV1EventsMockMetaData.Bin)) + + if err != nil { + return &EthereumFunctionsV1EventsMock{}, fmt.Errorf("FunctionsV1EventsMock instance deployment have failed: %w", err) + } + + instance, err := functions_v1_events_mock.NewFunctionsV1EventsMock(data.Address, wrappers.MustNewWrappedContractBackend(nil, client)) + if err != nil { + return &EthereumFunctionsV1EventsMock{}, fmt.Errorf("failed to instantiate FunctionsV1EventsMock instance: %w", err) + } + + return &EthereumFunctionsV1EventsMock{ + client: client, + eventsMock: instance, + address: &data.Address, + }, nil +} + +// EthereumKeeperRegistry11Mock represents the basic keeper registry 1.1 mock contract +type EthereumKeeperRegistry11Mock struct { + client *seth.Client + registryMock *keeper_registry_wrapper1_1_mock.KeeperRegistryMock + address *common.Address +} + +func (f *EthereumKeeperRegistry11Mock) Address() string { + return f.address.Hex() +} + +func (f *EthereumKeeperRegistry11Mock) EmitUpkeepPerformed(id *big.Int, success bool, from common.Address, payment *big.Int, performData []byte) error { + _, err := f.client.Decode(f.registryMock.EmitUpkeepPerformed(f.client.NewTXOpts(), id, success, from, payment, performData)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) EmitUpkeepCanceled(id *big.Int, atBlockHeight uint64) error { + _, err := f.client.Decode(f.registryMock.EmitUpkeepCanceled(f.client.NewTXOpts(), id, atBlockHeight)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) EmitFundsWithdrawn(id *big.Int, amount *big.Int, to common.Address) error { + _, err := f.client.Decode(f.registryMock.EmitFundsWithdrawn(f.client.NewTXOpts(), id, amount, to)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) EmitKeepersUpdated(keepers []common.Address, payees []common.Address) error { + _, err := f.client.Decode(f.registryMock.EmitKeepersUpdated(f.client.NewTXOpts(), keepers, payees)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) EmitUpkeepRegistered(id *big.Int, executeGas uint32, admin common.Address) error { + _, err := f.client.Decode(f.registryMock.EmitUpkeepRegistered(f.client.NewTXOpts(), id, executeGas, admin)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) EmitFundsAdded(id *big.Int, from common.Address, amount *big.Int) error { + _, err := f.client.Decode(f.registryMock.EmitFundsAdded(f.client.NewTXOpts(), id, from, amount)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) SetUpkeepCount(upkeepCount *big.Int) error { + _, err := f.client.Decode(f.registryMock.SetUpkeepCount(f.client.NewTXOpts(), upkeepCount)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) SetCanceledUpkeepList(canceledUpkeepList []*big.Int) error { + _, err := f.client.Decode(f.registryMock.SetCanceledUpkeepList(f.client.NewTXOpts(), canceledUpkeepList)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) SetKeeperList(keepers []common.Address) error { + _, err := f.client.Decode(f.registryMock.SetKeeperList(f.client.NewTXOpts(), keepers)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) SetConfig(paymentPremiumPPB uint32, flatFeeMicroLink uint32, blockCountPerTurn *big.Int, checkGasLimit uint32, stalenessSeconds *big.Int, gasCeilingMultiplier uint16, fallbackGasPrice *big.Int, fallbackLinkPrice *big.Int) error { + _, err := f.client.Decode(f.registryMock.SetConfig(f.client.NewTXOpts(), paymentPremiumPPB, flatFeeMicroLink, blockCountPerTurn, checkGasLimit, stalenessSeconds, gasCeilingMultiplier, fallbackGasPrice, fallbackLinkPrice)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) SetUpkeep(id *big.Int, target common.Address, executeGas uint32, balance *big.Int, admin common.Address, maxValidBlocknumber uint64, lastKeeper common.Address, checkData []byte) error { + _, err := f.client.Decode(f.registryMock.SetUpkeep(f.client.NewTXOpts(), id, target, executeGas, balance, admin, maxValidBlocknumber, lastKeeper, checkData)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) SetMinBalance(id *big.Int, minBalance *big.Int) error { + _, err := f.client.Decode(f.registryMock.SetMinBalance(f.client.NewTXOpts(), id, minBalance)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) SetCheckUpkeepData(id *big.Int, performData []byte, maxLinkPayment *big.Int, gasLimit *big.Int, adjustedGasWei *big.Int, linkEth *big.Int) error { + _, err := f.client.Decode(f.registryMock.SetCheckUpkeepData(f.client.NewTXOpts(), id, performData, maxLinkPayment, gasLimit, adjustedGasWei, linkEth)) + return err +} + +func (f *EthereumKeeperRegistry11Mock) SetPerformUpkeepSuccess(id *big.Int, success bool) error { + _, err := f.client.Decode(f.registryMock.SetPerformUpkeepSuccess(f.client.NewTXOpts(), id, success)) + return err +} + +func DeployKeeperRegistry11Mock(client *seth.Client) (KeeperRegistry11Mock, error) { + abi, err := keeper_registry_wrapper1_1_mock.KeeperRegistryMockMetaData.GetAbi() + if err != nil { + return &EthereumKeeperRegistry11Mock{}, fmt.Errorf("failed to get KeeperRegistry11Mock ABI: %w", err) + } + client.ContractStore.AddABI("KeeperRegistry11Mock", *abi) + client.ContractStore.AddBIN("KeeperRegistry11Mock", common.FromHex(keeper_registry_wrapper1_1_mock.KeeperRegistryMockMetaData.Bin)) + + data, err := client.DeployContract(client.NewTXOpts(), "KeeperRegistry11Mock", *abi, common.FromHex(keeper_registry_wrapper1_1_mock.KeeperRegistryMockMetaData.Bin)) + + if err != nil { + return &EthereumKeeperRegistry11Mock{}, fmt.Errorf("KeeperRegistry11Mock instance deployment have failed: %w", err) + } + + instance, err := keeper_registry_wrapper1_1_mock.NewKeeperRegistryMock(data.Address, wrappers.MustNewWrappedContractBackend(nil, client)) + if err != nil { + return &EthereumKeeperRegistry11Mock{}, fmt.Errorf("failed to instantiate KeeperRegistry11Mock instance: %w", err) + } + + return &EthereumKeeperRegistry11Mock{ + client: client, + registryMock: instance, + address: &data.Address, + }, nil +} + +// EthereumKeeperRegistrar12Mock represents the basic keeper registrar 1.2 mock contract +type EthereumKeeperRegistrar12Mock struct { + client *seth.Client + registrarMock *keeper_registrar_wrapper1_2_mock.KeeperRegistrarMock + address *common.Address +} + +func (f *EthereumKeeperRegistrar12Mock) Address() string { + return f.address.Hex() +} + +func (f *EthereumKeeperRegistrar12Mock) EmitRegistrationRequested(hash [32]byte, name string, encryptedEmail []byte, upkeepContract common.Address, gasLimit uint32, adminAddress common.Address, checkData []byte, amount *big.Int, source uint8) error { + _, err := f.client.Decode(f.registrarMock.EmitRegistrationRequested(f.client.NewTXOpts(), hash, name, encryptedEmail, upkeepContract, gasLimit, adminAddress, checkData, amount, source)) + return err +} + +func (f *EthereumKeeperRegistrar12Mock) EmitRegistrationApproved(hash [32]byte, displayName string, upkeepId *big.Int) error { + _, err := f.client.Decode(f.registrarMock.EmitRegistrationApproved(f.client.NewTXOpts(), hash, displayName, upkeepId)) + return err +} + +func (f *EthereumKeeperRegistrar12Mock) SetRegistrationConfig(autoApproveConfigType uint8, autoApproveMaxAllowed uint32, approvedCount uint32, keeperRegistry common.Address, minLINKJuels *big.Int) error { + _, err := f.client.Decode(f.registrarMock.SetRegistrationConfig(f.client.NewTXOpts(), autoApproveConfigType, autoApproveMaxAllowed, approvedCount, keeperRegistry, minLINKJuels)) + return err +} + +func DeployKeeperRegistrar12Mock(client *seth.Client) (KeeperRegistrar12Mock, error) { + abi, err := keeper_registrar_wrapper1_2_mock.KeeperRegistrarMockMetaData.GetAbi() + if err != nil { + return &EthereumKeeperRegistrar12Mock{}, fmt.Errorf("failed to get KeeperRegistrar12Mock ABI: %w", err) + } + client.ContractStore.AddABI("KeeperRegistrar12Mock", *abi) + client.ContractStore.AddBIN("KeeperRegistrar12Mock", common.FromHex(keeper_registrar_wrapper1_2_mock.KeeperRegistrarMockMetaData.Bin)) + + data, err := client.DeployContract(client.NewTXOpts(), "KeeperRegistrar12Mock", *abi, common.FromHex(keeper_registrar_wrapper1_2_mock.KeeperRegistrarMockMetaData.Bin)) + + if err != nil { + return &EthereumKeeperRegistrar12Mock{}, fmt.Errorf("KeeperRegistrar12Mock instance deployment have failed: %w", err) + } + + instance, err := keeper_registrar_wrapper1_2_mock.NewKeeperRegistrarMock(data.Address, wrappers.MustNewWrappedContractBackend(nil, client)) + if err != nil { + return &EthereumKeeperRegistrar12Mock{}, fmt.Errorf("failed to instantiate KeeperRegistrar12Mock instance: %w", err) + } + + return &EthereumKeeperRegistrar12Mock{ + client: client, + registrarMock: instance, + address: &data.Address, + }, nil +} + +// EthereumKeeperGasWrapperMock represents the basic keeper gas wrapper mock contract +type EthereumKeeperGasWrapperMock struct { + client *seth.Client + gasWrapperMock *gas_wrapper_mock.KeeperRegistryCheckUpkeepGasUsageWrapperMock + address *common.Address +} + +func (f *EthereumKeeperGasWrapperMock) Address() string { + return f.address.Hex() +} + +func (f *EthereumKeeperGasWrapperMock) SetMeasureCheckGasResult(result bool, payload []byte, gas *big.Int) error { + _, err := f.client.Decode(f.gasWrapperMock.SetMeasureCheckGasResult(f.client.NewTXOpts(), result, payload, gas)) + return err +} + +func DeployKeeperGasWrapperMock(client *seth.Client) (KeeperGasWrapperMock, error) { + abi, err := gas_wrapper_mock.KeeperRegistryCheckUpkeepGasUsageWrapperMockMetaData.GetAbi() + if err != nil { + return &EthereumKeeperGasWrapperMock{}, fmt.Errorf("failed to get KeeperGasWrapperMock ABI: %w", err) + } + client.ContractStore.AddABI("KeeperGasWrapperMock", *abi) + client.ContractStore.AddBIN("KeeperGasWrapperMock", common.FromHex(gas_wrapper_mock.KeeperRegistryCheckUpkeepGasUsageWrapperMockMetaData.Bin)) + + data, err := client.DeployContract(client.NewTXOpts(), "KeeperGasWrapperMock", *abi, common.FromHex(gas_wrapper_mock.KeeperRegistryCheckUpkeepGasUsageWrapperMockMetaData.Bin)) + + if err != nil { + return &EthereumKeeperGasWrapperMock{}, fmt.Errorf("KeeperGasWrapperMock instance deployment have failed: %w", err) + } + + instance, err := gas_wrapper_mock.NewKeeperRegistryCheckUpkeepGasUsageWrapperMock(data.Address, wrappers.MustNewWrappedContractBackend(nil, client)) + if err != nil { + return &EthereumKeeperGasWrapperMock{}, fmt.Errorf("failed to instantiate KeeperGasWrapperMock instance: %w", err) + } + + return &EthereumKeeperGasWrapperMock{ + client: client, + gasWrapperMock: instance, + address: &data.Address, + }, nil +} + +// EthereumStakingEventsMock represents the basic events mock contract +type EthereumStakingEventsMock struct { + client *seth.Client + eventsMock *eth_contracts.StakingEventsMock + address *common.Address +} + +func (f *EthereumStakingEventsMock) Address() string { + return f.address.Hex() +} + +func (f *EthereumStakingEventsMock) MaxCommunityStakeAmountIncreased(maxStakeAmount *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitMaxCommunityStakeAmountIncreased(f.client.NewTXOpts(), maxStakeAmount)) + return err +} + +func (f *EthereumStakingEventsMock) PoolSizeIncreased(maxPoolSize *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitPoolSizeIncreased(f.client.NewTXOpts(), maxPoolSize)) + return err +} + +func (f *EthereumStakingEventsMock) MaxOperatorStakeAmountIncreased(maxStakeAmount *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitMaxOperatorStakeAmountIncreased(f.client.NewTXOpts(), maxStakeAmount)) + return err +} + +func (f *EthereumStakingEventsMock) RewardInitialized(rate *big.Int, available *big.Int, startTimestamp *big.Int, endTimestamp *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitRewardInitialized(f.client.NewTXOpts(), rate, available, startTimestamp, endTimestamp)) + return err +} + +func (f *EthereumStakingEventsMock) AlertRaised(alerter common.Address, roundId *big.Int, rewardAmount *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitAlertRaised(f.client.NewTXOpts(), alerter, roundId, rewardAmount)) + return err +} + +func (f *EthereumStakingEventsMock) Staked(staker common.Address, newStake *big.Int, totalStake *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitStaked(f.client.NewTXOpts(), staker, newStake, totalStake)) + return err +} + +func (f *EthereumStakingEventsMock) OperatorAdded(operator common.Address) error { + _, err := f.client.Decode(f.eventsMock.EmitOperatorAdded(f.client.NewTXOpts(), operator)) + return err +} + +func (f *EthereumStakingEventsMock) OperatorRemoved(operator common.Address, amount *big.Int) error { + _, err := f.client.Decode(f.eventsMock.EmitOperatorRemoved(f.client.NewTXOpts(), operator, amount)) + return err +} + +func (f *EthereumStakingEventsMock) FeedOperatorsSet(feedOperators []common.Address) error { + _, err := f.client.Decode(f.eventsMock.EmitFeedOperatorsSet(f.client.NewTXOpts(), feedOperators)) + return err +} + +func DeployStakingEventsMock(client *seth.Client) (StakingEventsMock, error) { + abi, err := eth_contracts.StakingEventsMockMetaData.GetAbi() + if err != nil { + return &EthereumStakingEventsMock{}, fmt.Errorf("failed to get StakingEventsMock ABI: %w", err) + } + client.ContractStore.AddABI("StakingEventsMock", *abi) + client.ContractStore.AddBIN("StakingEventsMock", common.FromHex(eth_contracts.StakingEventsMockMetaData.Bin)) + + data, err := client.DeployContract(client.NewTXOpts(), "StakingEventsMock", *abi, common.FromHex(eth_contracts.StakingEventsMockMetaData.Bin)) + + if err != nil { + return &EthereumStakingEventsMock{}, fmt.Errorf("StakingEventsMock instance deployment have failed: %w", err) + } + + instance, err := eth_contracts.NewStakingEventsMock(data.Address, wrappers.MustNewWrappedContractBackend(nil, client)) + if err != nil { + return &EthereumStakingEventsMock{}, fmt.Errorf("failed to instantiate StakingEventsMock instance: %w", err) + } + + return &EthereumStakingEventsMock{ + client: client, + eventsMock: instance, + address: &data.Address, + }, nil +} diff --git a/integration-tests/contracts/ethereum_contracts.go b/integration-tests/contracts/ethereum_contracts.go index e8b2f184ce9..adf4dcffe80 100644 --- a/integration-tests/contracts/ethereum_contracts.go +++ b/integration-tests/contracts/ethereum_contracts.go @@ -350,18 +350,18 @@ func (f *EthereumFunctionsBillingRegistryEventsMock) BillingEnd(requestId [32]by return f.client.ProcessTransaction(tx) } -// EthereumStakingEventsMock represents the basic events mock contract -type EthereumStakingEventsMock struct { +// LegacyEthereumStakingEventsMock represents the basic events mock contract +type LegacyEthereumStakingEventsMock struct { client blockchain.EVMClient eventsMock *eth_contracts.StakingEventsMock address *common.Address } -func (f *EthereumStakingEventsMock) Address() string { +func (f *LegacyEthereumStakingEventsMock) Address() string { return f.address.Hex() } -func (f *EthereumStakingEventsMock) MaxCommunityStakeAmountIncreased(maxStakeAmount *big.Int) error { +func (f *LegacyEthereumStakingEventsMock) MaxCommunityStakeAmountIncreased(maxStakeAmount *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -373,7 +373,7 @@ func (f *EthereumStakingEventsMock) MaxCommunityStakeAmountIncreased(maxStakeAmo return f.client.ProcessTransaction(tx) } -func (f *EthereumStakingEventsMock) PoolSizeIncreased(maxPoolSize *big.Int) error { +func (f *LegacyEthereumStakingEventsMock) PoolSizeIncreased(maxPoolSize *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -385,7 +385,7 @@ func (f *EthereumStakingEventsMock) PoolSizeIncreased(maxPoolSize *big.Int) erro return f.client.ProcessTransaction(tx) } -func (f *EthereumStakingEventsMock) MaxOperatorStakeAmountIncreased(maxStakeAmount *big.Int) error { +func (f *LegacyEthereumStakingEventsMock) MaxOperatorStakeAmountIncreased(maxStakeAmount *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -397,7 +397,7 @@ func (f *EthereumStakingEventsMock) MaxOperatorStakeAmountIncreased(maxStakeAmou return f.client.ProcessTransaction(tx) } -func (f *EthereumStakingEventsMock) RewardInitialized(rate *big.Int, available *big.Int, startTimestamp *big.Int, endTimestamp *big.Int) error { +func (f *LegacyEthereumStakingEventsMock) RewardInitialized(rate *big.Int, available *big.Int, startTimestamp *big.Int, endTimestamp *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -409,7 +409,7 @@ func (f *EthereumStakingEventsMock) RewardInitialized(rate *big.Int, available * return f.client.ProcessTransaction(tx) } -func (f *EthereumStakingEventsMock) AlertRaised(alerter common.Address, roundId *big.Int, rewardAmount *big.Int) error { +func (f *LegacyEthereumStakingEventsMock) AlertRaised(alerter common.Address, roundId *big.Int, rewardAmount *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -421,7 +421,7 @@ func (f *EthereumStakingEventsMock) AlertRaised(alerter common.Address, roundId return f.client.ProcessTransaction(tx) } -func (f *EthereumStakingEventsMock) Staked(staker common.Address, newStake *big.Int, totalStake *big.Int) error { +func (f *LegacyEthereumStakingEventsMock) Staked(staker common.Address, newStake *big.Int, totalStake *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -433,7 +433,7 @@ func (f *EthereumStakingEventsMock) Staked(staker common.Address, newStake *big. return f.client.ProcessTransaction(tx) } -func (f *EthereumStakingEventsMock) OperatorAdded(operator common.Address) error { +func (f *LegacyEthereumStakingEventsMock) OperatorAdded(operator common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -445,7 +445,7 @@ func (f *EthereumStakingEventsMock) OperatorAdded(operator common.Address) error return f.client.ProcessTransaction(tx) } -func (f *EthereumStakingEventsMock) OperatorRemoved(operator common.Address, amount *big.Int) error { +func (f *LegacyEthereumStakingEventsMock) OperatorRemoved(operator common.Address, amount *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -457,7 +457,7 @@ func (f *EthereumStakingEventsMock) OperatorRemoved(operator common.Address, amo return f.client.ProcessTransaction(tx) } -func (f *EthereumStakingEventsMock) FeedOperatorsSet(feedOperators []common.Address) error { +func (f *LegacyEthereumStakingEventsMock) FeedOperatorsSet(feedOperators []common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -504,18 +504,18 @@ func (f *EthereumOffchainAggregatorEventsMock) NewTransmission(aggregatorRoundId return f.client.ProcessTransaction(tx) } -// EthereumKeeperRegistry11Mock represents the basic keeper registry 1.1 mock contract -type EthereumKeeperRegistry11Mock struct { +// LegacyEthereumKeeperRegistry11Mock represents the basic keeper registry 1.1 mock contract +type LegacyEthereumKeeperRegistry11Mock struct { client blockchain.EVMClient registryMock *keeper_registry_wrapper1_1_mock.KeeperRegistryMock address *common.Address } -func (f *EthereumKeeperRegistry11Mock) Address() string { +func (f *LegacyEthereumKeeperRegistry11Mock) Address() string { return f.address.Hex() } -func (f *EthereumKeeperRegistry11Mock) EmitUpkeepPerformed(id *big.Int, success bool, from common.Address, payment *big.Int, performData []byte) error { +func (f *LegacyEthereumKeeperRegistry11Mock) EmitUpkeepPerformed(id *big.Int, success bool, from common.Address, payment *big.Int, performData []byte) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -527,7 +527,7 @@ func (f *EthereumKeeperRegistry11Mock) EmitUpkeepPerformed(id *big.Int, success return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) EmitUpkeepCanceled(id *big.Int, atBlockHeight uint64) error { +func (f *LegacyEthereumKeeperRegistry11Mock) EmitUpkeepCanceled(id *big.Int, atBlockHeight uint64) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -539,7 +539,7 @@ func (f *EthereumKeeperRegistry11Mock) EmitUpkeepCanceled(id *big.Int, atBlockHe return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) EmitFundsWithdrawn(id *big.Int, amount *big.Int, to common.Address) error { +func (f *LegacyEthereumKeeperRegistry11Mock) EmitFundsWithdrawn(id *big.Int, amount *big.Int, to common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -551,7 +551,7 @@ func (f *EthereumKeeperRegistry11Mock) EmitFundsWithdrawn(id *big.Int, amount *b return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) EmitKeepersUpdated(keepers []common.Address, payees []common.Address) error { +func (f *LegacyEthereumKeeperRegistry11Mock) EmitKeepersUpdated(keepers []common.Address, payees []common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -563,7 +563,7 @@ func (f *EthereumKeeperRegistry11Mock) EmitKeepersUpdated(keepers []common.Addre return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) EmitUpkeepRegistered(id *big.Int, executeGas uint32, admin common.Address) error { +func (f *LegacyEthereumKeeperRegistry11Mock) EmitUpkeepRegistered(id *big.Int, executeGas uint32, admin common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -575,7 +575,7 @@ func (f *EthereumKeeperRegistry11Mock) EmitUpkeepRegistered(id *big.Int, execute return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) EmitFundsAdded(id *big.Int, from common.Address, amount *big.Int) error { +func (f *LegacyEthereumKeeperRegistry11Mock) EmitFundsAdded(id *big.Int, from common.Address, amount *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -587,7 +587,7 @@ func (f *EthereumKeeperRegistry11Mock) EmitFundsAdded(id *big.Int, from common.A return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) SetUpkeepCount(_upkeepCount *big.Int) error { +func (f *LegacyEthereumKeeperRegistry11Mock) SetUpkeepCount(_upkeepCount *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -599,7 +599,7 @@ func (f *EthereumKeeperRegistry11Mock) SetUpkeepCount(_upkeepCount *big.Int) err return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) SetCanceledUpkeepList(_canceledUpkeepList []*big.Int) error { +func (f *LegacyEthereumKeeperRegistry11Mock) SetCanceledUpkeepList(_canceledUpkeepList []*big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -611,7 +611,7 @@ func (f *EthereumKeeperRegistry11Mock) SetCanceledUpkeepList(_canceledUpkeepList return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) SetKeeperList(_keepers []common.Address) error { +func (f *LegacyEthereumKeeperRegistry11Mock) SetKeeperList(_keepers []common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -623,7 +623,7 @@ func (f *EthereumKeeperRegistry11Mock) SetKeeperList(_keepers []common.Address) return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) SetConfig(_paymentPremiumPPB uint32, _flatFeeMicroLink uint32, _blockCountPerTurn *big.Int, _checkGasLimit uint32, _stalenessSeconds *big.Int, _gasCeilingMultiplier uint16, _fallbackGasPrice *big.Int, _fallbackLinkPrice *big.Int) error { +func (f *LegacyEthereumKeeperRegistry11Mock) SetConfig(_paymentPremiumPPB uint32, _flatFeeMicroLink uint32, _blockCountPerTurn *big.Int, _checkGasLimit uint32, _stalenessSeconds *big.Int, _gasCeilingMultiplier uint16, _fallbackGasPrice *big.Int, _fallbackLinkPrice *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -635,7 +635,7 @@ func (f *EthereumKeeperRegistry11Mock) SetConfig(_paymentPremiumPPB uint32, _fla return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) SetUpkeep(id *big.Int, _target common.Address, _executeGas uint32, _balance *big.Int, _admin common.Address, _maxValidBlocknumber uint64, _lastKeeper common.Address, _checkData []byte) error { +func (f *LegacyEthereumKeeperRegistry11Mock) SetUpkeep(id *big.Int, _target common.Address, _executeGas uint32, _balance *big.Int, _admin common.Address, _maxValidBlocknumber uint64, _lastKeeper common.Address, _checkData []byte) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -647,7 +647,7 @@ func (f *EthereumKeeperRegistry11Mock) SetUpkeep(id *big.Int, _target common.Add return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) SetMinBalance(id *big.Int, minBalance *big.Int) error { +func (f *LegacyEthereumKeeperRegistry11Mock) SetMinBalance(id *big.Int, minBalance *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -659,7 +659,7 @@ func (f *EthereumKeeperRegistry11Mock) SetMinBalance(id *big.Int, minBalance *bi return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) SetCheckUpkeepData(id *big.Int, performData []byte, maxLinkPayment *big.Int, gasLimit *big.Int, adjustedGasWei *big.Int, linkEth *big.Int) error { +func (f *LegacyEthereumKeeperRegistry11Mock) SetCheckUpkeepData(id *big.Int, performData []byte, maxLinkPayment *big.Int, gasLimit *big.Int, adjustedGasWei *big.Int, linkEth *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -671,7 +671,7 @@ func (f *EthereumKeeperRegistry11Mock) SetCheckUpkeepData(id *big.Int, performDa return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistry11Mock) SetPerformUpkeepSuccess(id *big.Int, success bool) error { +func (f *LegacyEthereumKeeperRegistry11Mock) SetPerformUpkeepSuccess(id *big.Int, success bool) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -683,18 +683,18 @@ func (f *EthereumKeeperRegistry11Mock) SetPerformUpkeepSuccess(id *big.Int, succ return f.client.ProcessTransaction(tx) } -// EthereumKeeperRegistrar12Mock represents the basic keeper registrar 1.2 mock contract -type EthereumKeeperRegistrar12Mock struct { +// LegacyEthereumKeeperRegistrar12Mock represents the basic keeper registrar 1.2 mock contract +type LegacyEthereumKeeperRegistrar12Mock struct { client blockchain.EVMClient registrarMock *keeper_registrar_wrapper1_2_mock.KeeperRegistrarMock address *common.Address } -func (f *EthereumKeeperRegistrar12Mock) Address() string { +func (f *LegacyEthereumKeeperRegistrar12Mock) Address() string { return f.address.Hex() } -func (f *EthereumKeeperRegistrar12Mock) EmitRegistrationRequested(hash [32]byte, name string, encryptedEmail []byte, upkeepContract common.Address, gasLimit uint32, adminAddress common.Address, checkData []byte, amount *big.Int, source uint8) error { +func (f *LegacyEthereumKeeperRegistrar12Mock) EmitRegistrationRequested(hash [32]byte, name string, encryptedEmail []byte, upkeepContract common.Address, gasLimit uint32, adminAddress common.Address, checkData []byte, amount *big.Int, source uint8) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -706,7 +706,7 @@ func (f *EthereumKeeperRegistrar12Mock) EmitRegistrationRequested(hash [32]byte, return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistrar12Mock) EmitRegistrationApproved(hash [32]byte, displayName string, upkeepId *big.Int) error { +func (f *LegacyEthereumKeeperRegistrar12Mock) EmitRegistrationApproved(hash [32]byte, displayName string, upkeepId *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -718,7 +718,7 @@ func (f *EthereumKeeperRegistrar12Mock) EmitRegistrationApproved(hash [32]byte, return f.client.ProcessTransaction(tx) } -func (f *EthereumKeeperRegistrar12Mock) SetRegistrationConfig(_autoApproveConfigType uint8, _autoApproveMaxAllowed uint32, _approvedCount uint32, _keeperRegistry common.Address, _minLINKJuels *big.Int) error { +func (f *LegacyEthereumKeeperRegistrar12Mock) SetRegistrationConfig(_autoApproveConfigType uint8, _autoApproveMaxAllowed uint32, _approvedCount uint32, _keeperRegistry common.Address, _minLINKJuels *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -730,18 +730,18 @@ func (f *EthereumKeeperRegistrar12Mock) SetRegistrationConfig(_autoApproveConfig return f.client.ProcessTransaction(tx) } -// EthereumKeeperGasWrapperMock represents the basic keeper gas wrapper mock contract -type EthereumKeeperGasWrapperMock struct { +// LegacyEthereumKeeperGasWrapperMock represents the basic keeper gas wrapper mock contract +type LegacyEthereumKeeperGasWrapperMock struct { client blockchain.EVMClient gasWrapperMock *gas_wrapper_mock.KeeperRegistryCheckUpkeepGasUsageWrapperMock address *common.Address } -func (f *EthereumKeeperGasWrapperMock) Address() string { +func (f *LegacyEthereumKeeperGasWrapperMock) Address() string { return f.address.Hex() } -func (f *EthereumKeeperGasWrapperMock) SetMeasureCheckGasResult(result bool, payload []byte, gas *big.Int) error { +func (f *LegacyEthereumKeeperGasWrapperMock) SetMeasureCheckGasResult(result bool, payload []byte, gas *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -753,18 +753,18 @@ func (f *EthereumKeeperGasWrapperMock) SetMeasureCheckGasResult(result bool, pay return f.client.ProcessTransaction(tx) } -// EthereumFunctionsV1EventsMock represents the basic functions v1 events mock contract -type EthereumFunctionsV1EventsMock struct { +// LegacyEthereumFunctionsV1EventsMock represents the basic functions v1 events mock contract +type LegacyEthereumFunctionsV1EventsMock struct { client blockchain.EVMClient eventsMock *functions_v1_events_mock.FunctionsV1EventsMock address *common.Address } -func (f *EthereumFunctionsV1EventsMock) Address() string { +func (f *LegacyEthereumFunctionsV1EventsMock) Address() string { return f.address.Hex() } -func (f *EthereumFunctionsV1EventsMock) EmitRequestProcessed(requestId [32]byte, subscriptionId uint64, totalCostJuels *big.Int, transmitter common.Address, resultCode uint8, response []byte, errByte []byte, callbackReturnData []byte) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitRequestProcessed(requestId [32]byte, subscriptionId uint64, totalCostJuels *big.Int, transmitter common.Address, resultCode uint8, response []byte, errByte []byte, callbackReturnData []byte) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -776,7 +776,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitRequestProcessed(requestId [32]byte, return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitRequestStart(requestId [32]byte, donId [32]byte, subscriptionId uint64, subscriptionOwner common.Address, requestingContract common.Address, requestInitiator common.Address, data []byte, dataVersion uint16, callbackGasLimit uint32, estimatedTotalCostJuels *big.Int) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitRequestStart(requestId [32]byte, donId [32]byte, subscriptionId uint64, subscriptionOwner common.Address, requestingContract common.Address, requestInitiator common.Address, data []byte, dataVersion uint16, callbackGasLimit uint32, estimatedTotalCostJuels *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -788,7 +788,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitRequestStart(requestId [32]byte, don return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionCanceled(subscriptionId uint64, fundsRecipient common.Address, fundsAmount *big.Int) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitSubscriptionCanceled(subscriptionId uint64, fundsRecipient common.Address, fundsAmount *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -800,7 +800,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionCanceled(subscriptionId return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionConsumerAdded(subscriptionId uint64, consumer common.Address) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitSubscriptionConsumerAdded(subscriptionId uint64, consumer common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -812,7 +812,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionConsumerAdded(subscripti return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionConsumerRemoved(subscriptionId uint64, consumer common.Address) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitSubscriptionConsumerRemoved(subscriptionId uint64, consumer common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -824,7 +824,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionConsumerRemoved(subscrip return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionCreated(subscriptionId uint64, owner common.Address) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitSubscriptionCreated(subscriptionId uint64, owner common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -836,7 +836,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionCreated(subscriptionId u return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionFunded(subscriptionId uint64, oldBalance *big.Int, newBalance *big.Int) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitSubscriptionFunded(subscriptionId uint64, oldBalance *big.Int, newBalance *big.Int) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -848,7 +848,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionFunded(subscriptionId ui return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionOwnerTransferred(subscriptionId uint64, from common.Address, to common.Address) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitSubscriptionOwnerTransferred(subscriptionId uint64, from common.Address, to common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -860,7 +860,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionOwnerTransferred(subscri return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionOwnerTransferRequested(subscriptionId uint64, from common.Address, to common.Address) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitSubscriptionOwnerTransferRequested(subscriptionId uint64, from common.Address, to common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -872,7 +872,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitSubscriptionOwnerTransferRequested(s return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitRequestNotProcessed(requestId [32]byte, coordinator common.Address, transmitter common.Address, resultCode uint8) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitRequestNotProcessed(requestId [32]byte, coordinator common.Address, transmitter common.Address, resultCode uint8) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err @@ -884,7 +884,7 @@ func (f *EthereumFunctionsV1EventsMock) EmitRequestNotProcessed(requestId [32]by return f.client.ProcessTransaction(tx) } -func (f *EthereumFunctionsV1EventsMock) EmitContractUpdated(id [32]byte, from common.Address, to common.Address) error { +func (f *LegacyEthereumFunctionsV1EventsMock) EmitContractUpdated(id [32]byte, from common.Address, to common.Address) error { opts, err := f.client.TransactionOpts(f.client.GetDefaultWallet()) if err != nil { return err diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 69c09244ef8..3d76a656be5 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -25,12 +25,12 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.3 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73 - github.com/smartcontractkit/chainlink-testing-framework v1.28.4 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb + github.com/smartcontractkit/chainlink-testing-framework v1.28.7 github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c - github.com/smartcontractkit/seth v0.1.6 + github.com/smartcontractkit/seth v0.1.6-0.20240429143720-cacb8160ecec github.com/smartcontractkit/wasp v0.4.5 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index e2be6f36438..61fef05bbad 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1517,8 +1517,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs= github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73 h1:54hM3/SrOM166it2K35hGb5K7gQ49/Op0aHp9WkqpqU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb h1:nJ9dkgvX5vdpFWhYufnRUAiNvNHsXkoBL6C0bDerq/k= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee h1:eFuBKyEbL2b+eyfgV/Eu9+8HuCEev+IcBi+K9l1dG7g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee/go.mod h1:uATrrJ8IsuBkOBJ46USuf73gz9gZy5k5bzGE5/ji/rc= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo= @@ -1529,8 +1529,8 @@ github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240422172640-59d47c73ba5 github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240422172640-59d47c73ba58/go.mod h1:oV5gIuSKrPEcjQ6uB6smBsm5kXHxyydVLNyAs4V9CoQ= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240325075535-0f7eb05ee595 h1:y6ks0HsSOhPUueOmTcoxDQ50RCS1XINlRDTemZyHjFw= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240325075535-0f7eb05ee595/go.mod h1:vV6WfnVIbK5Q1JsIru4YcTG0T1uRpLJm6t2BgCnCSsg= -github.com/smartcontractkit/chainlink-testing-framework v1.28.4 h1:/OOPH76VFQlG5HEXrXgBVDv1fjuasQzMV1EyeaaXWzM= -github.com/smartcontractkit/chainlink-testing-framework v1.28.4/go.mod h1:jN+HgXbriq6fKRlIqLw9F3I81aYImV6kBJkIfz0mdIA= +github.com/smartcontractkit/chainlink-testing-framework v1.28.7 h1:Yr93tBl5jVx1cfKywt0C0cbuObDPJ6JIU4FIsZ6bZlM= +github.com/smartcontractkit/chainlink-testing-framework v1.28.7/go.mod h1:x1zDOz8zcLjEvs9fNA9y/DMguLam/2+CJdpxX0+rM8A= github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 h1:TFe+FvzxClblt6qRfqEhUfa4kFQx5UobuoFGO2W4mMo= @@ -1539,8 +1539,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJ github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c h1:lIyMbTaF2H0Q71vkwZHX/Ew4KF2BxiKhqEXwF8rn+KI= github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= -github.com/smartcontractkit/seth v0.1.6 h1:exU96KiKM/gxvp7OR8KkOXnTgbtFNepdhMBvyobFKCw= -github.com/smartcontractkit/seth v0.1.6/go.mod h1:2TMOZQ8WTAw7rR1YBbXpnad6VmT/+xDd/nXLmB7Eero= +github.com/smartcontractkit/seth v0.1.6-0.20240429143720-cacb8160ecec h1:BT1loU6TT2YqMenD7XE+aw7IeeTiC25+r1TLKAySVIg= +github.com/smartcontractkit/seth v0.1.6-0.20240429143720-cacb8160ecec/go.mod h1:2TMOZQ8WTAw7rR1YBbXpnad6VmT/+xDd/nXLmB7Eero= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 128c611c04d..c8fde175cab 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -16,8 +16,8 @@ require ( github.com/rs/zerolog v1.30.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.3 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73 - github.com/smartcontractkit/chainlink-testing-framework v1.28.4 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb + github.com/smartcontractkit/chainlink-testing-framework v1.28.7 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240214231432-4ad5eb95178c github.com/smartcontractkit/chainlink/v2 v2.9.0-beta0.0.20240216210048-da02459ddad8 github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 291f221f4b8..46646e6b289 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1500,8 +1500,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs= github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73 h1:54hM3/SrOM166it2K35hGb5K7gQ49/Op0aHp9WkqpqU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240424132620-add4946c1c73/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb h1:nJ9dkgvX5vdpFWhYufnRUAiNvNHsXkoBL6C0bDerq/k= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240429120925-907b29311feb/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee h1:eFuBKyEbL2b+eyfgV/Eu9+8HuCEev+IcBi+K9l1dG7g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419213354-ea34a948e2ee/go.mod h1:uATrrJ8IsuBkOBJ46USuf73gz9gZy5k5bzGE5/ji/rc= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo= @@ -1512,8 +1512,8 @@ github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240422172640-59d47c73ba5 github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240422172640-59d47c73ba58/go.mod h1:oV5gIuSKrPEcjQ6uB6smBsm5kXHxyydVLNyAs4V9CoQ= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240325075535-0f7eb05ee595 h1:y6ks0HsSOhPUueOmTcoxDQ50RCS1XINlRDTemZyHjFw= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240325075535-0f7eb05ee595/go.mod h1:vV6WfnVIbK5Q1JsIru4YcTG0T1uRpLJm6t2BgCnCSsg= -github.com/smartcontractkit/chainlink-testing-framework v1.28.4 h1:/OOPH76VFQlG5HEXrXgBVDv1fjuasQzMV1EyeaaXWzM= -github.com/smartcontractkit/chainlink-testing-framework v1.28.4/go.mod h1:jN+HgXbriq6fKRlIqLw9F3I81aYImV6kBJkIfz0mdIA= +github.com/smartcontractkit/chainlink-testing-framework v1.28.7 h1:Yr93tBl5jVx1cfKywt0C0cbuObDPJ6JIU4FIsZ6bZlM= +github.com/smartcontractkit/chainlink-testing-framework v1.28.7/go.mod h1:x1zDOz8zcLjEvs9fNA9y/DMguLam/2+CJdpxX0+rM8A= github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240227164431-18a7065e23ea h1:ZdLmNAfKRjH8AYUvjiiDGUgiWQfq/7iNpxyTkvjx/ko= github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240227164431-18a7065e23ea/go.mod h1:gCKC9w6XpNk6jm+XIk2psrkkfxhi421N9NSiFceXW88= github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= diff --git a/plugins/medianpoc/plugin.go b/plugins/medianpoc/plugin.go index b937361b9ec..bbd9d437e3a 100644 --- a/plugins/medianpoc/plugin.go +++ b/plugins/medianpoc/plugin.go @@ -66,6 +66,7 @@ func (p *Plugin) NewReportingPluginFactory( telemetry core.TelemetryClient, errorLog core.ErrorLog, keyValueStore core.KeyValueStore, + relayerSet core.RelayerSet, ) (types.ReportingPluginFactory, error) { f, err := p.newFactory(ctx, config, provider, pipelineRunner, telemetry, errorLog) if err != nil { diff --git a/tools/bin/codecov b/tools/bin/codecov deleted file mode 100755 index 36513ce06df..00000000000 --- a/tools/bin/codecov +++ /dev/null @@ -1,1888 +0,0 @@ -#!/usr/bin/env bash - -# Apache License Version 2.0, January 2004 -# https://github.com/codecov/codecov-bash/blob/master/LICENSE - -set -e +o pipefail - -VERSION="1.0.6" - -codecov_flags=( ) -url="https://codecov.io" -env="$CODECOV_ENV" -service="" -token="" -search_in="" -# shellcheck disable=SC2153 -flags="$CODECOV_FLAGS" -exit_with=0 -curlargs="" -curlawsargs="" -dump="0" -clean="0" -curl_s="-s" -name="$CODECOV_NAME" -include_cov="" -exclude_cov="" -ddp="$HOME/Library/Developer/Xcode/DerivedData" -xp="" -files="" -save_to="" -direct_file_upload="" -cacert="$CODECOV_CA_BUNDLE" -gcov_ignore="-not -path './bower_components/**' -not -path './node_modules/**' -not -path './vendor/**'" -gcov_include="" - -ft_gcov="1" -ft_coveragepy="1" -ft_fix="1" -ft_search="1" -ft_s3="1" -ft_network="1" -ft_xcodellvm="1" -ft_xcodeplist="0" -ft_gcovout="1" -ft_html="0" -ft_yaml="0" - -_git_root=$(git rev-parse --show-toplevel 2>/dev/null || hg root 2>/dev/null || echo "$PWD") -git_root="$_git_root" -remote_addr="" -if [ "$git_root" = "$PWD" ]; -then - git_root="." -fi - -branch_o="" -build_o="" -commit_o="" -pr_o="" -prefix_o="" -network_filter_o="" -search_in_o="" -slug_o="" -tag_o="" -url_o="" -git_ls_files_recurse_submodules_o="" -package="bash" - -commit="$VCS_COMMIT_ID" -branch="$VCS_BRANCH_NAME" -pr="$VCS_PULL_REQUEST" -slug="$VCS_SLUG" -tag="$VCS_TAG" -build_url="$CI_BUILD_URL" -build="$CI_BUILD_ID" -job="$CI_JOB_ID" - -beta_xcode_partials="" - -proj_root="$git_root" -gcov_exe="gcov" -gcov_arg="" - -b="\033[0;36m" -g="\033[0;32m" -r="\033[0;31m" -e="\033[0;90m" -y="\033[0;33m" -x="\033[0m" - -show_help() { -cat << EOF - - Codecov Bash $VERSION - - Global report uploading tool for Codecov - Documentation at https://docs.codecov.io/docs - Contribute at https://github.com/codecov/codecov-bash - - - -h Display this help and exit - -f FILE Target file(s) to upload - - -f "path/to/file" only upload this file - skips searching unless provided patterns below - - -f '!*.bar' ignore all files at pattern *.bar - -f '*.foo' include all files at pattern *.foo - Must use single quotes. - This is non-exclusive, use -s "*.foo" to match specific paths. - - -s DIR Directory to search for coverage reports. - Already searches project root and artifact folders. - -t TOKEN Set the private repository token - (option) set environment variable CODECOV_TOKEN=:uuid - - -t @/path/to/token_file - -t uuid - - -n NAME Custom defined name of the upload. Visible in Codecov UI - - -e ENV Specify environment variables to be included with this build - Also accepting environment variables: CODECOV_ENV=VAR,VAR2 - - -e VAR,VAR2 - - -k prefix Prefix filepaths to help resolve path fixing - - -i prefix Only include files in the network with a certain prefix. Useful for upload-specific path fixing - - -X feature Toggle functionalities - - -X gcov Disable gcov - -X coveragepy Disable python coverage - -X fix Disable report fixing - -X search Disable searching for reports - -X xcode Disable xcode processing - -X network Disable uploading the file network - -X gcovout Disable gcov output - -X html Enable coverage for HTML files - -X recursesubs Enable recurse submodules in git projects when searching for source files - -X yaml Enable coverage for YAML files - - -N The commit SHA of the parent for which you are uploading coverage. If not present, - the parent will be determined using the API of your repository provider. - When using the repository provider's API, the parent is determined via finding - the closest ancestor to the commit. - - -R root dir Used when not in git/hg project to identify project root directory - -F flag Flag the upload to group coverage metrics - - -F unittests This upload is only unittests - -F integration This upload is only integration tests - -F ui,chrome This upload is Chrome - UI tests - - -c Move discovered coverage reports to the trash - -z FILE Upload specified file directly to Codecov and bypass all report generation. - This is inteded to be used only with a pre-formatted Codecov report and is not - expected to work under any other circumstances. - -Z Exit with 1 if not successful. Default will Exit with 0 - - -- xcode -- - -D Custom Derived Data Path for Coverage.profdata and gcov processing - Default '~/Library/Developer/Xcode/DerivedData' - -J Specify packages to build coverage. Uploader will only build these packages. - This can significantly reduces time to build coverage reports. - - -J 'MyAppName' Will match "MyAppName" and "MyAppNameTests" - -J '^ExampleApp$' Will match only "ExampleApp" not "ExampleAppTests" - - -- gcov -- - -g GLOB Paths to ignore during gcov gathering - -G GLOB Paths to include during gcov gathering - -p dir Project root directory - Also used when preparing gcov - -x gcovexe gcov executable to run. Defaults to 'gcov' - -a gcovargs extra arguments to pass to gcov - - -- Override CI Environment Variables -- - These variables are automatically detected by popular CI providers - - -B branch Specify the branch name - -C sha Specify the commit sha - -P pr Specify the pull request number - -b build Specify the build number - -T tag Specify the git tag - - -- Enterprise -- - -u URL Set the target url for Enterprise customers - Not required when retrieving the bash uploader from your CCE - (option) Set environment variable CODECOV_URL=https://my-hosted-codecov.com - -r SLUG owner/repo slug used instead of the private repo token in Enterprise - (option) set environment variable CODECOV_SLUG=:owner/:repo - (option) set in your codecov.yml "codecov.slug" - -S PATH File path to your cacert.pem file used to verify ssl with Codecov Enterprise (optional) - (option) Set environment variable: CODECOV_CA_BUNDLE="/path/to/ca.pem" - -U curlargs Extra curl arguments to communicate with Codecov. e.g., -U "--proxy http://http-proxy" - -A curlargs Extra curl arguments to communicate with AWS. - - -- Debugging -- - -d Don't upload, but dump upload file to stdout - -q PATH Write upload file to path - -K Remove color from the output - -v Verbose mode - -EOF -} - - -say() { - echo -e "$1" -} - - -urlencode() { - echo "$1" | curl -Gso /dev/null -w "%{url_effective}" --data-urlencode @- "" | cut -c 3- | sed -e 's/%0A//' -} - -swiftcov() { - _dir=$(dirname "$1" | sed 's/\(Build\).*/\1/g') - for _type in app framework xctest - do - find "$_dir" -name "*.$_type" | while read -r f - do - _proj=${f##*/} - _proj=${_proj%."$_type"} - if [ "$2" = "" ] || [ "$(echo "$_proj" | grep -i "$2")" != "" ]; - then - say " $g+$x Building reports for $_proj $_type" - dest=$([ -f "$f/$_proj" ] && echo "$f/$_proj" || echo "$f/Contents/MacOS/$_proj") - # shellcheck disable=SC2001 - _proj_name=$(echo "$_proj" | sed -e 's/[[:space:]]//g') - # shellcheck disable=SC2086 - xcrun llvm-cov show $beta_xcode_partials -instr-profile "$1" "$dest" > "$_proj_name.$_type.coverage.txt" \ - || say " ${r}x>${x} llvm-cov failed to produce results for $dest" - fi - done - done -} - - -# Credits to: https://gist.github.com/pkuczynski/8665367 -parse_yaml() { - local prefix=$2 - local s='[[:space:]]*' w='[a-zA-Z0-9_]*' - local fs - fs=$(echo @|tr @ '\034') - sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \ - -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$1" | - awk -F"$fs" '{ - indent = length($1)/2; - vname[indent] = $2; - for (i in vname) {if (i > indent) {delete vname[i]}} - if (length($3) > 0) { - vn=""; if (indent > 0) {vn=(vn)(vname[0])("_")} - printf("%s%s%s=\"%s\"\n", "'"$prefix"'",vn, $2, $3); - } - }' -} - -if [ $# != 0 ]; -then - while getopts "a:A:b:B:cC:dD:e:f:F:g:G:hi:J:k:Kn:p:P:Q:q:r:R:s:S:t:T:u:U:vx:X:Zz:N:-" o - do - codecov_flags+=( "$o" ) - case "$o" in - "-") - echo -e "${r}Long options are not supported${x}" - exit 2 - ;; - "?") - ;; - "N") - parent=$OPTARG - ;; - "a") - gcov_arg=$OPTARG - ;; - "A") - curlawsargs="$OPTARG" - ;; - "b") - build_o="$OPTARG" - ;; - "B") - branch_o="$OPTARG" - ;; - "c") - clean="1" - ;; - "C") - commit_o="$OPTARG" - ;; - "d") - dump="1" - ;; - "D") - ddp="$OPTARG" - ;; - "e") - env="$env,$OPTARG" - ;; - "f") - if [ "${OPTARG::1}" = "!" ]; - then - exclude_cov="$exclude_cov -not -path '${OPTARG:1}'" - - elif [[ "$OPTARG" = *"*"* ]]; - then - include_cov="$include_cov -or -path '$OPTARG'" - - else - ft_search=0 - if [ "$files" = "" ]; - then - files="$OPTARG" - else - files="$files -$OPTARG" - fi - fi - ;; - "F") - if [ "$flags" = "" ]; - then - flags="$OPTARG" - else - flags="$flags,$OPTARG" - fi - ;; - "g") - gcov_ignore="$gcov_ignore -not -path '$OPTARG'" - ;; - "G") - gcov_include="$gcov_include -path '$OPTARG'" - ;; - "h") - show_help - exit 0; - ;; - "i") - network_filter_o="$OPTARG" - ;; - "J") - ft_xcodellvm="1" - ft_xcodeplist="0" - if [ "$xp" = "" ]; - then - xp="$OPTARG" - else - xp="$xp\|$OPTARG" - fi - ;; - "k") - prefix_o=$(echo "$OPTARG" | sed -e 's:^/*::' -e 's:/*$::') - ;; - "K") - b="" - g="" - r="" - e="" - x="" - ;; - "n") - name="$OPTARG" - ;; - "p") - proj_root="$OPTARG" - ;; - "P") - pr_o="$OPTARG" - ;; - "Q") - # this is only meant for Codecov packages to overwrite - package="$OPTARG" - ;; - "q") - save_to="$OPTARG" - ;; - "r") - slug_o="$OPTARG" - ;; - "R") - git_root="$OPTARG" - ;; - "s") - if [ "$search_in_o" = "" ]; - then - search_in_o="$OPTARG" - else - search_in_o="$search_in_o $OPTARG" - fi - ;; - "S") - # shellcheck disable=SC2089 - cacert="--cacert \"$OPTARG\"" - ;; - "t") - if [ "${OPTARG::1}" = "@" ]; - then - token=$(< "${OPTARG:1}" tr -d ' \n') - else - token="$OPTARG" - fi - ;; - "T") - tag_o="$OPTARG" - ;; - "u") - url_o=$(echo "$OPTARG" | sed -e 's/\/$//') - ;; - "U") - curlargs="$OPTARG" - ;; - "v") - set -x - curl_s="" - ;; - "x") - gcov_exe=$OPTARG - ;; - "X") - if [ "$OPTARG" = "gcov" ]; - then - ft_gcov="0" - elif [ "$OPTARG" = "coveragepy" ] || [ "$OPTARG" = "py" ]; - then - ft_coveragepy="0" - elif [ "$OPTARG" = "gcovout" ]; - then - ft_gcovout="0" - elif [ "$OPTARG" = "xcodellvm" ]; - then - ft_xcodellvm="1" - ft_xcodeplist="0" - elif [ "$OPTARG" = "fix" ] || [ "$OPTARG" = "fixes" ]; - then - ft_fix="0" - elif [ "$OPTARG" = "xcode" ]; - then - ft_xcodellvm="0" - ft_xcodeplist="0" - elif [ "$OPTARG" = "search" ]; - then - ft_search="0" - elif [ "$OPTARG" = "xcodepartials" ]; - then - beta_xcode_partials="-use-color" - elif [ "$OPTARG" = "network" ]; - then - ft_network="0" - elif [ "$OPTARG" = "s3" ]; - then - ft_s3="0" - elif [ "$OPTARG" = "html" ]; - then - ft_html="1" - elif [ "$OPTARG" = "recursesubs" ]; - then - git_ls_files_recurse_submodules_o="--recurse-submodules" - elif [ "$OPTARG" = "yaml" ]; - then - ft_yaml="1" - fi - ;; - "Z") - exit_with=1 - ;; - "z") - direct_file_upload="$OPTARG" - ft_gcov="0" - ft_coveragepy="0" - ft_fix="0" - ft_search="0" - ft_network="0" - ft_xcodellvm="0" - ft_gcovout="0" - include_cov="" - ;; - *) - echo -e "${r}Unexpected flag not supported${x}" - ;; - esac - done -fi - -say " - _____ _ - / ____| | | -| | ___ __| | ___ ___ _____ __ -| | / _ \\ / _\` |/ _ \\/ __/ _ \\ \\ / / -| |___| (_) | (_| | __/ (_| (_) \\ V / - \\_____\\___/ \\__,_|\\___|\\___\\___/ \\_/ - Bash-$VERSION - -" - -# check for installed tools -# git/hg -if [ "$direct_file_upload" = "" ]; -then - if [ -x "$(command -v git)" ]; - then - say "$b==>$x $(git --version) found" - else - say "$y==>$x git not installed, testing for mercurial" - if [ -x "$(command -v hg)" ]; - then - say "$b==>$x $(hg --version) found" - else - say "$r==>$x git nor mercurial are installed. Uploader may fail or have unintended consequences" - fi - fi -fi -# curl -if [ -x "$(command -v curl)" ]; -then - say "$b==>$x $(curl --version)" -else - say "$r==>$x curl not installed. Exiting." - exit ${exit_with}; -fi - -search_in="$proj_root" - -#shellcheck disable=SC2154 -if [ "$JENKINS_URL" != "" ]; -then - say "$e==>$x Jenkins CI detected." - # https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project - # https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin#GitHubpullrequestbuilderplugin-EnvironmentVariables - service="jenkins" - - # shellcheck disable=SC2154 - if [ "$ghprbSourceBranch" != "" ]; - then - branch="$ghprbSourceBranch" - elif [ "$GIT_BRANCH" != "" ]; - then - branch="$GIT_BRANCH" - elif [ "$BRANCH_NAME" != "" ]; - then - branch="$BRANCH_NAME" - fi - - # shellcheck disable=SC2154 - if [ "$ghprbActualCommit" != "" ]; - then - commit="$ghprbActualCommit" - elif [ "$GIT_COMMIT" != "" ]; - then - commit="$GIT_COMMIT" - fi - - # shellcheck disable=SC2154 - if [ "$ghprbPullId" != "" ]; - then - pr="$ghprbPullId" - elif [ "$CHANGE_ID" != "" ]; - then - pr="$CHANGE_ID" - fi - - build="$BUILD_NUMBER" - # shellcheck disable=SC2153 - build_url=$(urlencode "$BUILD_URL") - -elif [ "$CI" = "true" ] && [ "$TRAVIS" = "true" ] && [ "$SHIPPABLE" != "true" ]; -then - say "$e==>$x Travis CI detected." - # https://docs.travis-ci.com/user/environment-variables/ - service="travis" - commit="${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT}" - build="$TRAVIS_JOB_NUMBER" - pr="$TRAVIS_PULL_REQUEST" - job="$TRAVIS_JOB_ID" - slug="$TRAVIS_REPO_SLUG" - env="$env,TRAVIS_OS_NAME" - tag="$TRAVIS_TAG" - if [ "$TRAVIS_BRANCH" != "$TRAVIS_TAG" ]; - then - branch="${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}" - fi - - language=$(compgen -A variable | grep "^TRAVIS_.*_VERSION$" | head -1) - if [ "$language" != "" ]; - then - env="$env,${!language}" - fi - -elif [ "$CODEBUILD_CI" = "true" ]; -then - say "$e==>$x AWS Codebuild detected." - # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - service="codebuild" - commit="$CODEBUILD_RESOLVED_SOURCE_VERSION" - build="$CODEBUILD_BUILD_ID" - branch="$(echo "$CODEBUILD_WEBHOOK_HEAD_REF" | sed 's/^refs\/heads\///')" - if [ "${CODEBUILD_SOURCE_VERSION/pr}" = "$CODEBUILD_SOURCE_VERSION" ] ; then - pr="false" - else - pr="$(echo "$CODEBUILD_SOURCE_VERSION" | sed 's/^pr\///')" - fi - job="$CODEBUILD_BUILD_ID" - slug="$(echo "$CODEBUILD_SOURCE_REPO_URL" | sed 's/^.*:\/\/[^\/]*\///' | sed 's/\.git$//')" - -elif [ "$CI" = "true" ] && [ "$CI_NAME" = "codeship" ]; -then - say "$e==>$x Codeship CI detected." - # https://www.codeship.io/documentation/continuous-integration/set-environment-variables/ - service="codeship" - branch="$CI_BRANCH" - build="$CI_BUILD_NUMBER" - build_url=$(urlencode "$CI_BUILD_URL") - commit="$CI_COMMIT_ID" - -elif [ -n "$CF_BUILD_URL" ] && [ -n "$CF_BUILD_ID" ]; -then - say "$e==>$x Codefresh CI detected." - # https://docs.codefresh.io/v1.0/docs/variables - service="codefresh" - branch="$CF_BRANCH" - build="$CF_BUILD_ID" - build_url=$(urlencode "$CF_BUILD_URL") - commit="$CF_REVISION" - -elif [ "$TEAMCITY_VERSION" != "" ]; -then - say "$e==>$x TeamCity CI detected." - # https://confluence.jetbrains.com/display/TCD8/Predefined+Build+Parameters - # https://confluence.jetbrains.com/plugins/servlet/mobile#content/view/74847298 - if [ "$TEAMCITY_BUILD_BRANCH" = '' ]; - then - echo " Teamcity does not automatically make build parameters available as environment variables." - echo " Add the following environment parameters to the build configuration" - echo " env.TEAMCITY_BUILD_BRANCH = %teamcity.build.branch%" - echo " env.TEAMCITY_BUILD_ID = %teamcity.build.id%" - echo " env.TEAMCITY_BUILD_URL = %teamcity.serverUrl%/viewLog.html?buildId=%teamcity.build.id%" - echo " env.TEAMCITY_BUILD_COMMIT = %system.build.vcs.number%" - echo " env.TEAMCITY_BUILD_REPOSITORY = %vcsroot..url%" - fi - service="teamcity" - branch="$TEAMCITY_BUILD_BRANCH" - build="$TEAMCITY_BUILD_ID" - build_url=$(urlencode "$TEAMCITY_BUILD_URL") - if [ "$TEAMCITY_BUILD_COMMIT" != "" ]; - then - commit="$TEAMCITY_BUILD_COMMIT" - else - commit="$BUILD_VCS_NUMBER" - fi - remote_addr="$TEAMCITY_BUILD_REPOSITORY" - -elif [ "$CI" = "true" ] && [ "$CIRCLECI" = "true" ]; -then - say "$e==>$x Circle CI detected." - # https://circleci.com/docs/environment-variables - service="circleci" - branch="$CIRCLE_BRANCH" - build="$CIRCLE_BUILD_NUM" - job="$CIRCLE_NODE_INDEX" - if [ "$CIRCLE_PROJECT_REPONAME" != "" ]; - then - slug="$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME" - else - # git@github.com:owner/repo.git - slug="${CIRCLE_REPOSITORY_URL##*:}" - # owner/repo.git - slug="${slug%%.git}" - fi - pr="${CIRCLE_PULL_REQUEST##*/}" - commit="$CIRCLE_SHA1" - search_in="$search_in $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS" - -elif [ "$BUDDYBUILD_BRANCH" != "" ]; -then - say "$e==>$x buddybuild detected" - # http://docs.buddybuild.com/v6/docs/custom-prebuild-and-postbuild-steps - service="buddybuild" - branch="$BUDDYBUILD_BRANCH" - build="$BUDDYBUILD_BUILD_NUMBER" - build_url="https://dashboard.buddybuild.com/public/apps/$BUDDYBUILD_APP_ID/build/$BUDDYBUILD_BUILD_ID" - # BUDDYBUILD_TRIGGERED_BY - if [ "$ddp" = "$HOME/Library/Developer/Xcode/DerivedData" ]; - then - ddp="/private/tmp/sandbox/${BUDDYBUILD_APP_ID}/bbtest" - fi - -elif [ "${bamboo_planRepository_revision}" != "" ]; -then - say "$e==>$x Bamboo detected" - # https://confluence.atlassian.com/bamboo/bamboo-variables-289277087.html#Bamboovariables-Build-specificvariables - service="bamboo" - commit="${bamboo_planRepository_revision}" - # shellcheck disable=SC2154 - branch="${bamboo_planRepository_branch}" - # shellcheck disable=SC2154 - build="${bamboo_buildNumber}" - # shellcheck disable=SC2154 - build_url="${bamboo_buildResultsUrl}" - # shellcheck disable=SC2154 - remote_addr="${bamboo_planRepository_repositoryUrl}" - -elif [ "$CI" = "true" ] && [ "$BITRISE_IO" = "true" ]; -then - # http://devcenter.bitrise.io/faq/available-environment-variables/ - say "$e==>$x Bitrise CI detected." - service="bitrise" - branch="$BITRISE_GIT_BRANCH" - build="$BITRISE_BUILD_NUMBER" - build_url=$(urlencode "$BITRISE_BUILD_URL") - pr="$BITRISE_PULL_REQUEST" - if [ "$GIT_CLONE_COMMIT_HASH" != "" ]; - then - commit="$GIT_CLONE_COMMIT_HASH" - fi - -elif [ "$CI" = "true" ] && [ "$SEMAPHORE" = "true" ]; -then - say "$e==>$x Semaphore CI detected." -# https://docs.semaphoreci.com/ci-cd-environment/environment-variables/#semaphore-related - service="semaphore" - branch="$SEMAPHORE_GIT_BRANCH" - build="$SEMAPHORE_WORKFLOW_NUMBER" - job="$SEMAPHORE_JOB_ID" - pr="$PULL_REQUEST_NUMBER" - slug="$SEMAPHORE_REPO_SLUG" - commit="$REVISION" - env="$env,SEMAPHORE_TRIGGER_SOURCE" - -elif [ "$CI" = "true" ] && [ "$BUILDKITE" = "true" ]; -then - say "$e==>$x Buildkite CI detected." - # https://buildkite.com/docs/guides/environment-variables - service="buildkite" - branch="$BUILDKITE_BRANCH" - build="$BUILDKITE_BUILD_NUMBER" - job="$BUILDKITE_JOB_ID" - build_url=$(urlencode "$BUILDKITE_BUILD_URL") - slug="$BUILDKITE_PROJECT_SLUG" - commit="$BUILDKITE_COMMIT" - if [[ "$BUILDKITE_PULL_REQUEST" != "false" ]]; then - pr="$BUILDKITE_PULL_REQUEST" - fi - tag="$BUILDKITE_TAG" - -elif [ "$CI" = "drone" ] || [ "$DRONE" = "true" ]; -then - say "$e==>$x Drone CI detected." - # http://docs.drone.io/env.html - # drone commits are not full shas - service="drone.io" - branch="$DRONE_BRANCH" - build="$DRONE_BUILD_NUMBER" - build_url=$(urlencode "${DRONE_BUILD_LINK}") - pr="$DRONE_PULL_REQUEST" - job="$DRONE_JOB_NUMBER" - tag="$DRONE_TAG" - -elif [ "$CI" = "true" ] && [ "$HEROKU_TEST_RUN_BRANCH" != "" ]; -then - say "$e==>$x Heroku CI detected." - # https://devcenter.heroku.com/articles/heroku-ci#environment-variables - service="heroku" - branch="$HEROKU_TEST_RUN_BRANCH" - build="$HEROKU_TEST_RUN_ID" - commit="$HEROKU_TEST_RUN_COMMIT_VERSION" - -elif [[ "$CI" = "true" || "$CI" = "True" ]] && [[ "$APPVEYOR" = "true" || "$APPVEYOR" = "True" ]]; -then - say "$e==>$x Appveyor CI detected." - # http://www.appveyor.com/docs/environment-variables - service="appveyor" - branch="$APPVEYOR_REPO_BRANCH" - build=$(urlencode "$APPVEYOR_JOB_ID") - pr="$APPVEYOR_PULL_REQUEST_NUMBER" - job="$APPVEYOR_ACCOUNT_NAME%2F$APPVEYOR_PROJECT_SLUG%2F$APPVEYOR_BUILD_VERSION" - slug="$APPVEYOR_REPO_NAME" - commit="$APPVEYOR_REPO_COMMIT" - build_url=$(urlencode "${APPVEYOR_URL}/project/${APPVEYOR_REPO_NAME}/builds/$APPVEYOR_BUILD_ID/job/${APPVEYOR_JOB_ID}") - -elif [ "$CI" = "true" ] && [ "$WERCKER_GIT_BRANCH" != "" ]; -then - say "$e==>$x Wercker CI detected." - # http://devcenter.wercker.com/articles/steps/variables.html - service="wercker" - branch="$WERCKER_GIT_BRANCH" - build="$WERCKER_MAIN_PIPELINE_STARTED" - slug="$WERCKER_GIT_OWNER/$WERCKER_GIT_REPOSITORY" - commit="$WERCKER_GIT_COMMIT" - -elif [ "$CI" = "true" ] && [ "$MAGNUM" = "true" ]; -then - say "$e==>$x Magnum CI detected." - # https://magnum-ci.com/docs/environment - service="magnum" - branch="$CI_BRANCH" - build="$CI_BUILD_NUMBER" - commit="$CI_COMMIT" - -elif [ "$SHIPPABLE" = "true" ]; -then - say "$e==>$x Shippable CI detected." - # http://docs.shippable.com/ci_configure/ - service="shippable" - # shellcheck disable=SC2153 - branch=$([ "$HEAD_BRANCH" != "" ] && echo "$HEAD_BRANCH" || echo "$BRANCH") - build="$BUILD_NUMBER" - build_url=$(urlencode "$BUILD_URL") - pr="$PULL_REQUEST" - slug="$REPO_FULL_NAME" - # shellcheck disable=SC2153 - commit="$COMMIT" - -elif [ "$TDDIUM" = "true" ]; -then - say "Solano CI detected." - # http://docs.solanolabs.com/Setup/tddium-set-environment-variables/ - service="solano" - commit="$TDDIUM_CURRENT_COMMIT" - branch="$TDDIUM_CURRENT_BRANCH" - build="$TDDIUM_TID" - pr="$TDDIUM_PR_ID" - -elif [ "$GREENHOUSE" = "true" ]; -then - say "$e==>$x Greenhouse CI detected." - # http://docs.greenhouseci.com/docs/environment-variables-files - service="greenhouse" - branch="$GREENHOUSE_BRANCH" - build="$GREENHOUSE_BUILD_NUMBER" - build_url=$(urlencode "$GREENHOUSE_BUILD_URL") - pr="$GREENHOUSE_PULL_REQUEST" - commit="$GREENHOUSE_COMMIT" - search_in="$search_in $GREENHOUSE_EXPORT_DIR" - -elif [ "$GITLAB_CI" != "" ]; -then - say "$e==>$x GitLab CI detected." - # http://doc.gitlab.com/ce/ci/variables/README.html - service="gitlab" - branch="${CI_BUILD_REF_NAME:-$CI_COMMIT_REF_NAME}" - build="${CI_BUILD_ID:-$CI_JOB_ID}" - remote_addr="${CI_BUILD_REPO:-$CI_REPOSITORY_URL}" - commit="${CI_BUILD_REF:-$CI_COMMIT_SHA}" - slug="${CI_PROJECT_PATH}" - -elif [ "$GITHUB_ACTIONS" != "" ]; -then - say "$e==>$x GitHub Actions detected." - say " Env vars used:" - say " -> GITHUB_ACTIONS: ${GITHUB_ACTIONS}" - say " -> GITHUB_HEAD_REF: ${GITHUB_HEAD_REF}" - say " -> GITHUB_REF: ${GITHUB_REF}" - say " -> GITHUB_REPOSITORY: ${GITHUB_REPOSITORY}" - say " -> GITHUB_RUN_ID: ${GITHUB_RUN_ID}" - say " -> GITHUB_SHA: ${GITHUB_SHA}" - say " -> GITHUB_WORKFLOW: ${GITHUB_WORKFLOW}" - - # https://github.com/features/actions - service="github-actions" - - # https://help.github.com/en/articles/virtual-environments-for-github-actions#environment-variables - branch="${GITHUB_REF#refs/heads/}" - if [ "$GITHUB_HEAD_REF" != "" ]; - then - # PR refs are in the format: refs/pull/7/merge - if [[ "$GITHUB_REF" =~ ^refs\/pull\/[0-9]+\/merge$ ]]; - then - pr="${GITHUB_REF#refs/pull/}" - pr="${pr%/merge}" - fi - branch="${GITHUB_HEAD_REF}" - fi - commit="${GITHUB_SHA}" - slug="${GITHUB_REPOSITORY}" - build="${GITHUB_RUN_ID}" - build_url=$(urlencode "${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}") - job="$(urlencode "${GITHUB_WORKFLOW}")" - - # actions/checkout runs in detached HEAD - mc= - if [ -n "$pr" ] && [ "$pr" != false ] && [ "$commit_o" == "" ]; - then - mc=$(git show --no-patch --format="%P" 2>/dev/null || echo "") - - if [[ "$mc" =~ ^[a-z0-9]{40}[[:space:]][a-z0-9]{40}$ ]]; - then - mc=$(echo "$mc" | cut -d' ' -f2) - say " Fixing merge commit SHA $commit -> $mc" - commit=$mc - elif [[ "$mc" = "" ]]; - then - say "$r-> Issue detecting commit SHA. Please run actions/checkout with fetch-depth > 1 or set to 0$x" - fi - fi - -elif [ "$SYSTEM_TEAMFOUNDATIONSERVERURI" != "" ]; -then - say "$e==>$x Azure Pipelines detected." - # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=vsts - # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&viewFallbackFrom=vsts&tabs=yaml - service="azure_pipelines" - commit="$BUILD_SOURCEVERSION" - build="$BUILD_BUILDNUMBER" - if [ -z "$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER" ]; - then - pr="$SYSTEM_PULLREQUEST_PULLREQUESTID" - else - pr="$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER" - fi - project="${SYSTEM_TEAMPROJECT}" - server_uri="${SYSTEM_TEAMFOUNDATIONSERVERURI}" - job="${BUILD_BUILDID}" - branch="${BUILD_SOURCEBRANCH#"refs/heads/"}" - build_url=$(urlencode "${SYSTEM_TEAMFOUNDATIONSERVERURI}${SYSTEM_TEAMPROJECT}/_build/results?buildId=${BUILD_BUILDID}") - - # azure/pipelines runs in detached HEAD - mc= - if [ -n "$pr" ] && [ "$pr" != false ]; - then - mc=$(git show --no-patch --format="%P" 2>/dev/null || echo "") - - if [[ "$mc" =~ ^[a-z0-9]{40}[[:space:]][a-z0-9]{40}$ ]]; - then - mc=$(echo "$mc" | cut -d' ' -f2) - say " Fixing merge commit SHA $commit -> $mc" - commit=$mc - fi - fi - -elif [ "$CI" = "true" ] && [ "$BITBUCKET_BUILD_NUMBER" != "" ]; -then - say "$e==>$x Bitbucket detected." - # https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html - service="bitbucket" - branch="$BITBUCKET_BRANCH" - build="$BITBUCKET_BUILD_NUMBER" - slug="$BITBUCKET_REPO_OWNER/$BITBUCKET_REPO_SLUG" - job="$BITBUCKET_BUILD_NUMBER" - pr="$BITBUCKET_PR_ID" - commit="$BITBUCKET_COMMIT" - # See https://jira.atlassian.com/browse/BCLOUD-19393 - if [ "${#commit}" = 12 ]; - then - commit=$(git rev-parse "$BITBUCKET_COMMIT") - fi - -elif [ "$CI" = "true" ] && [ "$BUDDY" = "true" ]; -then - say "$e==>$x Buddy CI detected." - # https://buddy.works/docs/pipelines/environment-variables - service="buddy" - branch="$BUDDY_EXECUTION_BRANCH" - build="$BUDDY_EXECUTION_ID" - build_url=$(urlencode "$BUDDY_EXECUTION_URL") - commit="$BUDDY_EXECUTION_REVISION" - pr="$BUDDY_EXECUTION_PULL_REQUEST_NO" - tag="$BUDDY_EXECUTION_TAG" - slug="$BUDDY_REPO_SLUG" - -elif [ "$CIRRUS_CI" != "" ]; -then - say "$e==>$x Cirrus CI detected." - # https://cirrus-ci.org/guide/writing-tasks/#environment-variables - service="cirrus-ci" - slug="$CIRRUS_REPO_FULL_NAME" - branch="$CIRRUS_BRANCH" - pr="$CIRRUS_PR" - commit="$CIRRUS_CHANGE_IN_REPO" - build="$CIRRUS_BUILD_ID" - build_url=$(urlencode "https://cirrus-ci.com/task/$CIRRUS_TASK_ID") - job="$CIRRUS_TASK_NAME" - -elif [ "$DOCKER_REPO" != "" ]; -then - say "$e==>$x Docker detected." - # https://docs.docker.com/docker-cloud/builds/advanced/ - service="docker" - branch="$SOURCE_BRANCH" - commit="$SOURCE_COMMIT" - slug="$DOCKER_REPO" - tag="$CACHE_TAG" - env="$env,IMAGE_NAME" - -else - say "${r}x>${x} No CI provider detected." - say " Testing inside Docker? ${b}http://docs.codecov.io/docs/testing-with-docker${x}" - say " Testing with Tox? ${b}https://docs.codecov.io/docs/python#section-testing-with-tox${x}" - -fi - -say " ${e}current dir: ${x} $PWD" -say " ${e}project root:${x} $git_root" - -# find branch, commit, repo from git command -if [ "$GIT_BRANCH" != "" ]; -then - branch="$GIT_BRANCH" - -elif [ "$branch" = "" ]; -then - branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || hg branch 2>/dev/null || echo "") - if [ "$branch" = "HEAD" ]; - then - branch="" - fi -fi - -if [ "$commit_o" = "" ]; -then - if [ "$GIT_COMMIT" != "" ]; - then - commit="$GIT_COMMIT" - elif [ "$commit" = "" ]; - then - commit=$(git log -1 --format="%H" 2>/dev/null || hg id -i --debug 2>/dev/null | tr -d '+' || echo "") - fi -else - commit="$commit_o" -fi - -if [ "$CODECOV_TOKEN" != "" ] && [ "$token" = "" ]; -then - say "${e}-->${x} token set from env" - token="$CODECOV_TOKEN" -fi - -if [ "$CODECOV_URL" != "" ] && [ "$url_o" = "" ]; -then - say "${e}-->${x} url set from env" - url_o=$(echo "$CODECOV_URL" | sed -e 's/\/$//') -fi - -if [ "$CODECOV_SLUG" != "" ]; -then - say "${e}-->${x} slug set from env" - slug_o="$CODECOV_SLUG" - -elif [ "$slug" = "" ]; -then - if [ "$remote_addr" = "" ]; - then - remote_addr=$(git config --get remote.origin.url || hg paths default || echo '') - fi - if [ "$remote_addr" != "" ]; - then - if echo "$remote_addr" | grep -q "//"; then - # https - slug=$(echo "$remote_addr" | cut -d / -f 4,5 | sed -e 's/\.git$//') - else - # ssh - slug=$(echo "$remote_addr" | cut -d : -f 2 | sed -e 's/\.git$//') - fi - fi - if [ "$slug" = "/" ]; - then - slug="" - fi -fi - -yaml=$(cd "$git_root" && \ - git ls-files "*codecov.yml" "*codecov.yaml" 2>/dev/null \ - || hg locate "*codecov.yml" "*codecov.yaml" 2>/dev/null \ - || cd "$proj_root" && find . -maxdepth 1 -type f -name '*codecov.y*ml' 2>/dev/null \ - || echo '') -yaml=$(echo "$yaml" | head -1) - -if [ "$yaml" != "" ]; -then - say " ${e}Yaml found at:${x} $yaml" - if [[ "$yaml" != /* ]]; then - # relative path for yaml file given, assume relative to the repo root - yaml="$git_root/$yaml" - fi - config=$(parse_yaml "$yaml" || echo '') - - # TODO validate the yaml here - - if [ "$(echo "$config" | grep 'codecov_token="')" != "" ] && [ "$token" = "" ]; - then - say "${e}-->${x} token set from yaml" - token="$(echo "$config" | grep 'codecov_token="' | sed -e 's/codecov_token="//' | sed -e 's/"\.*//')" - fi - - if [ "$(echo "$config" | grep 'codecov_url="')" != "" ] && [ "$url_o" = "" ]; - then - say "${e}-->${x} url set from yaml" - url_o="$(echo "$config" | grep 'codecov_url="' | sed -e 's/codecov_url="//' | sed -e 's/"\.*//')" - fi - - if [ "$(echo "$config" | grep 'codecov_slug="')" != "" ] && [ "$slug_o" = "" ]; - then - say "${e}-->${x} slug set from yaml" - slug_o="$(echo "$config" | grep 'codecov_slug="' | sed -e 's/codecov_slug="//' | sed -e 's/"\.*//')" - fi -else - say " ${g}Yaml not found, that's ok! Learn more at${x} ${b}http://docs.codecov.io/docs/codecov-yaml${x}" -fi - -if [ "$branch_o" != "" ]; -then - branch=$(urlencode "$branch_o") -else - branch=$(urlencode "$branch") -fi - -if [ "$slug_o" = "" ]; -then - urlencoded_slug=$(urlencode "$slug") -else - urlencoded_slug=$(urlencode "$slug_o") -fi - -query="branch=$branch\ - &commit=$commit\ - &build=$([ "$build_o" = "" ] && echo "$build" || echo "$build_o")\ - &build_url=$build_url\ - &name=$(urlencode "$name")\ - &tag=$([ "$tag_o" = "" ] && echo "$tag" || echo "$tag_o")\ - &slug=$urlencoded_slug\ - &service=$service\ - &flags=$flags\ - &pr=$([ "$pr_o" = "" ] && echo "${pr##\#}" || echo "${pr_o##\#}")\ - &job=$job\ - &cmd_args=$(IFS=,; echo "${codecov_flags[*]}")" - -if [ -n "$project" ] && [ -n "$server_uri" ]; -then - query=$(echo "$query&project=$project&server_uri=$server_uri" | tr -d ' ') -fi - -if [ "$parent" != "" ]; -then - query=$(echo "parent=$parent&$query" | tr -d ' ') -fi - -if [ "$ft_search" = "1" ]; -then - # detect bower comoponents location - bower_components="bower_components" - bower_rc=$(cd "$git_root" && cat .bowerrc 2>/dev/null || echo "") - if [ "$bower_rc" != "" ]; - then - bower_components=$(echo "$bower_rc" | tr -d '\n' | grep '"directory"' | cut -d'"' -f4 | sed -e 's/\/$//') - if [ "$bower_components" = "" ]; - then - bower_components="bower_components" - fi - fi - - # Swift Coverage - if [ "$ft_xcodellvm" = "1" ] && [ -d "$ddp" ]; - then - say "${e}==>${x} Processing Xcode reports via llvm-cov" - say " DerivedData folder: $ddp" - profdata_files=$(find "$ddp" -name '*.profdata' 2>/dev/null || echo '') - if [ "$profdata_files" != "" ]; - then - # xcode via profdata - if [ "$xp" = "" ]; - then - # xp=$(xcodebuild -showBuildSettings 2>/dev/null | grep -i "^\s*PRODUCT_NAME" | sed -e 's/.*= \(.*\)/\1/') - # say " ${e}->${x} Speed up Xcode processing by adding ${e}-J '$xp'${x}" - say " ${g}hint${x} Speed up Swift processing by using use ${g}-J 'AppName'${x} (regexp accepted)" - say " ${g}hint${x} This will remove Pods/ from your report. Also ${b}https://docs.codecov.io/docs/ignoring-paths${x}" - fi - while read -r profdata; - do - if [ "$profdata" != "" ]; - then - swiftcov "$profdata" "$xp" - fi - done <<< "$profdata_files" - else - say " ${e}->${x} No Swift coverage found" - fi - - # Obj-C Gcov Coverage - if [ "$ft_gcov" = "1" ]; - then - say " ${e}->${x} Running $gcov_exe for Obj-C" - if [ "$ft_gcovout" = "0" ]; - then - # suppress gcov output - bash -c "find $ddp -type f -name '*.gcda' $gcov_include $gcov_ignore -exec $gcov_exe -p $gcov_arg {} +" >/dev/null 2>&1 || true - else - bash -c "find $ddp -type f -name '*.gcda' $gcov_include $gcov_ignore -exec $gcov_exe -p $gcov_arg {} +" || true - fi - fi - fi - - if [ "$ft_xcodeplist" = "1" ] && [ -d "$ddp" ]; - then - say "${e}==>${x} Processing Xcode plists" - plists_files=$(find "$ddp" -name '*.xccoverage' 2>/dev/null || echo '') - if [ "$plists_files" != "" ]; - then - while read -r plist; - do - if [ "$plist" != "" ]; - then - say " ${g}Found${x} plist file at $plist" - plutil -convert xml1 -o "$(basename "$plist").plist" -- "$plist" - fi - done <<< "$plists_files" - fi - fi - - # Gcov Coverage - if [ "$ft_gcov" = "1" ]; - then - say "${e}==>${x} Running $gcov_exe in $proj_root ${e}(disable via -X gcov)${x}" - if [ "$ft_gcovout" = "0" ]; - then - # suppress gcov output - bash -c "find $proj_root -type f -name '*.gcno' $gcov_include $gcov_ignore -exec $gcov_exe -pb $gcov_arg {} +" >/dev/null 2>&1 || true - else - bash -c "find $proj_root -type f -name '*.gcno' $gcov_include $gcov_ignore -exec $gcov_exe -pb $gcov_arg {} +" || true - fi - else - say "${e}==>${x} gcov disabled" - fi - - # Python Coverage - if [ "$ft_coveragepy" = "1" ]; - then - if [ ! -f coverage.xml ]; - then - if command -v coverage >/dev/null 2>&1; - then - say "${e}==>${x} Python coveragepy exists ${e}disable via -X coveragepy${x}" - - dotcoverage=$(find "$git_root" -name '.coverage' -or -name '.coverage.*' | head -1 || echo '') - if [ "$dotcoverage" != "" ]; - then - cd "$(dirname "$dotcoverage")" - if [ ! -f .coverage ]; - then - say " ${e}->${x} Running coverage combine" - coverage combine -a - fi - say " ${e}->${x} Running coverage xml" - if [ "$(coverage xml -i)" != "No data to report." ]; - then - files="$files -$PWD/coverage.xml" - else - say " ${r}No data to report.${x}" - fi - cd "$proj_root" - else - say " ${r}No .coverage file found.${x}" - fi - else - say "${e}==>${x} Python coveragepy not found" - fi - fi - else - say "${e}==>${x} Python coveragepy disabled" - fi - - if [ "$search_in_o" != "" ]; - then - # location override - search_in="$search_in_o" - fi - - say "$e==>$x Searching for coverage reports in:" - for _path in $search_in - do - say " ${g}+${x} $_path" - done - - patterns="find $search_in \( \ - -name vendor \ - -or -name '$bower_components' \ - -or -name '.egg-info*' \ - -or -name 'conftest_*.c.gcov' \ - -or -name .env \ - -or -name .envs \ - -or -name .git \ - -or -name .hg \ - -or -name .tox \ - -or -name .venv \ - -or -name .venvs \ - -or -name .virtualenv \ - -or -name .virtualenvs \ - -or -name .yarn-cache \ - -or -name __pycache__ \ - -or -name env \ - -or -name envs \ - -or -name htmlcov \ - -or -name js/generated/coverage \ - -or -name node_modules \ - -or -name venv \ - -or -name venvs \ - -or -name virtualenv \ - -or -name virtualenvs \ - \) -prune -or \ - -type f \( -name '*coverage*.*' \ - -or -name '*.clover' \ - -or -name '*.codecov.*' \ - -or -name '*.gcov' \ - -or -name '*.lcov' \ - -or -name '*.lst' \ - -or -name 'clover.xml' \ - -or -name 'cobertura.xml' \ - -or -name 'codecov.*' \ - -or -name 'cover.out' \ - -or -name 'codecov-result.json' \ - -or -name 'coverage-final.json' \ - -or -name 'excoveralls.json' \ - -or -name 'gcov.info' \ - -or -name 'jacoco*.xml' \ - -or -name '*Jacoco*.xml' \ - -or -name 'lcov.dat' \ - -or -name 'lcov.info' \ - -or -name 'luacov.report.out' \ - -or -name 'naxsi.info' \ - -or -name 'nosetests.xml' \ - -or -name 'report.xml' \ - $include_cov \) \ - $exclude_cov \ - -not -name '*.am' \ - -not -name '*.bash' \ - -not -name '*.bat' \ - -not -name '*.bw' \ - -not -name '*.cfg' \ - -not -name '*.class' \ - -not -name '*.cmake' \ - -not -name '*.cmake' \ - -not -name '*.conf' \ - -not -name '*.coverage' \ - -not -name '*.cp' \ - -not -name '*.cpp' \ - -not -name '*.crt' \ - -not -name '*.css' \ - -not -name '*.csv' \ - -not -name '*.csv' \ - -not -name '*.data' \ - -not -name '*.db' \ - -not -name '*.dox' \ - -not -name '*.ec' \ - -not -name '*.ec' \ - -not -name '*.egg' \ - -not -name '*.el' \ - -not -name '*.env' \ - -not -name '*.erb' \ - -not -name '*.exe' \ - -not -name '*.ftl' \ - -not -name '*.gif' \ - -not -name '*.gradle' \ - -not -name '*.gz' \ - -not -name '*.h' \ - -not -name '*.html' \ - -not -name '*.in' \ - -not -name '*.jade' \ - -not -name '*.jar*' \ - -not -name '*.jpeg' \ - -not -name '*.jpg' \ - -not -name '*.js' \ - -not -name '*.less' \ - -not -name '*.log' \ - -not -name '*.m4' \ - -not -name '*.mak*' \ - -not -name '*.md' \ - -not -name '*.o' \ - -not -name '*.p12' \ - -not -name '*.pem' \ - -not -name '*.png' \ - -not -name '*.pom*' \ - -not -name '*.profdata' \ - -not -name '*.proto' \ - -not -name '*.ps1' \ - -not -name '*.pth' \ - -not -name '*.py' \ - -not -name '*.pyc' \ - -not -name '*.pyo' \ - -not -name '*.rb' \ - -not -name '*.rsp' \ - -not -name '*.rst' \ - -not -name '*.ru' \ - -not -name '*.sbt' \ - -not -name '*.scss' \ - -not -name '*.scss' \ - -not -name '*.serialized' \ - -not -name '*.sh' \ - -not -name '*.snapshot' \ - -not -name '*.sql' \ - -not -name '*.svg' \ - -not -name '*.tar.tz' \ - -not -name '*.template' \ - -not -name '*.whl' \ - -not -name '*.xcconfig' \ - -not -name '*.xcoverage.*' \ - -not -name '*/classycle/report.xml' \ - -not -name '*codecov.yml' \ - -not -name '*~' \ - -not -name '.*coveragerc' \ - -not -name '.coverage*' \ - -not -name 'coverage-summary.json' \ - -not -name 'createdFiles.lst' \ - -not -name 'fullLocaleNames.lst' \ - -not -name 'include.lst' \ - -not -name 'inputFiles.lst' \ - -not -name 'phpunit-code-coverage.xml' \ - -not -name 'phpunit-coverage.xml' \ - -not -name 'remapInstanbul.coverage*.json' \ - -not -name 'scoverage.measurements.*' \ - -not -name 'test_*_coverage.txt' \ - -not -name 'testrunner-coverage*' \ - -print 2>/dev/null" - files=$(eval "$patterns" || echo '') - -elif [ "$include_cov" != "" ]; -then - files=$(eval "find $search_in -type f \( ${include_cov:5} \)$exclude_cov 2>/dev/null" || echo '') -elif [ "$direct_file_upload" != "" ]; -then - files=$direct_file_upload -fi - -num_of_files=$(echo "$files" | wc -l | tr -d ' ') -if [ "$num_of_files" != '' ] && [ "$files" != '' ]; -then - say " ${e}->${x} Found $num_of_files reports" -fi - -# no files found -if [ "$files" = "" ]; -then - say "${r}-->${x} No coverage report found." - say " Please visit ${b}http://docs.codecov.io/docs/supported-languages${x}" - exit ${exit_with}; -fi - -if [ "$ft_network" == "1" ]; -then - say "${e}==>${x} Detecting git/mercurial file structure" - network=$(cd "$git_root" && git ls-files $git_ls_files_recurse_submodules_o 2>/dev/null || hg locate 2>/dev/null || echo "") - if [ "$network" = "" ]; - then - network=$(find "$git_root" \( \ - -name virtualenv \ - -name .virtualenv \ - -name virtualenvs \ - -name .virtualenvs \ - -name '*.png' \ - -name '*.gif' \ - -name '*.jpg' \ - -name '*.jpeg' \ - -name '*.md' \ - -name .env \ - -name .envs \ - -name env \ - -name envs \ - -name .venv \ - -name .venvs \ - -name venv \ - -name venvs \ - -name .git \ - -name .egg-info \ - -name shunit2-2.1.6 \ - -name vendor \ - -name __pycache__ \ - -name node_modules \ - -path "*/$bower_components/*" \ - -path '*/target/delombok/*' \ - -path '*/build/lib/*' \ - -path '*/js/generated/coverage/*' \ - \) -prune -or \ - -type f -print 2>/dev/null || echo '') - fi - - if [ "$network_filter_o" != "" ]; - then - network=$(echo "$network" | grep -e "$network_filter_o/*") - fi - if [ "$prefix_o" != "" ]; - then - network=$(echo "$network" | awk "{print \"$prefix_o/\"\$0}") - fi -fi - -upload_file=$(mktemp /tmp/codecov.XXXXXX) -adjustments_file=$(mktemp /tmp/codecov.adjustments.XXXXXX) - -cleanup() { - rm -f "$upload_file" "$adjustments_file" "$upload_file.gz" -} - -trap cleanup INT ABRT TERM - - -if [ "$env" != "" ]; -then - inc_env="" - say "${e}==>${x} Appending build variables" - for varname in $(echo "$env" | tr ',' ' ') - do - if [ "$varname" != "" ]; - then - say " ${g}+${x} $varname" - inc_env="${inc_env}${varname}=$(eval echo "\$${varname}") -" - fi - done - echo "$inc_env<<<<<< ENV" >> "$upload_file" -fi - -# Append git file list -# write discovered yaml location -if [ "$direct_file_upload" = "" ]; -then - echo "$yaml" >> "$upload_file" -fi - -if [ "$ft_network" == "1" ]; -then - i="woff|eot|otf" # fonts - i="$i|gif|png|jpg|jpeg|psd" # images - i="$i|ptt|pptx|numbers|pages|md|txt|xlsx|docx|doc|pdf|csv" # docs - i="$i|.gitignore" # supporting docs - - if [ "$ft_html" != "1" ]; - then - i="$i|html" - fi - - if [ "$ft_yaml" != "1" ]; - then - i="$i|yml|yaml" - fi - - echo "$network" | grep -vwE "($i)$" >> "$upload_file" -fi -echo "<<<<<< network" >> "$upload_file" - -if [ "$direct_file_upload" = "" ]; -then - fr=0 - say "${e}==>${x} Reading reports" - while IFS='' read -r file; - do - # read the coverage file - if [ "$(echo "$file" | tr -d ' ')" != '' ]; - then - if [ -f "$file" ]; - then - report_len=$(wc -c < "$file") - if [ "$report_len" -ne 0 ]; - then - say " ${g}+${x} $file ${e}bytes=$(echo "$report_len" | tr -d ' ')${x}" - # append to to upload - _filename=$(basename "$file") - if [ "${_filename##*.}" = 'gcov' ]; - then - { - echo "# path=$(echo "$file.reduced" | sed "s|^$git_root/||")"; - # get file name - head -1 "$file"; - } >> "$upload_file" - # 1. remove source code - # 2. remove ending bracket lines - # 3. remove whitespace - # 4. remove contextual lines - # 5. remove function names - awk -F': *' '{print $1":"$2":"}' "$file" \ - | sed '\/: *} *$/d' \ - | sed 's/^ *//' \ - | sed '/^-/d' \ - | sed 's/^function.*/func/' >> "$upload_file" - else - { - echo "# path=${file//^$git_root/||}"; - cat "$file"; - } >> "$upload_file" - fi - echo "<<<<<< EOF" >> "$upload_file" - fr=1 - if [ "$clean" = "1" ]; - then - rm "$file" - fi - else - say " ${r}-${x} Skipping empty file $file" - fi - else - say " ${r}-${x} file not found at $file" - fi - fi - done <<< "$(echo -e "$files")" - - if [ "$fr" = "0" ]; - then - say "${r}-->${x} No coverage data found." - say " Please visit ${b}http://docs.codecov.io/docs/supported-languages${x}" - say " search for your projects language to learn how to collect reports." - exit ${exit_with}; - fi -else - cp "$direct_file_upload" "$upload_file" - if [ "$clean" = "1" ]; - then - rm "$direct_file_upload" - fi -fi - -if [ "$ft_fix" = "1" ]; -then - say "${e}==>${x} Appending adjustments" - say " ${b}https://docs.codecov.io/docs/fixing-reports${x}" - - empty_line='^[[:space:]]*$' - # // - syntax_comment='^[[:space:]]*//.*' - # /* or */ - syntax_comment_block='^[[:space:]]*(\/\*|\*\/)[[:space:]]*$' - # { or } - syntax_bracket='^[[:space:]]*[\{\}][[:space:]]*(//.*)?$' - # [ or ] - syntax_list='^[[:space:]]*[][][[:space:]]*(//.*)?$' - # func ... { - syntax_go_func='^[[:space:]]*func[[:space:]]*[\{][[:space:]]*$' - - # shellcheck disable=SC2089 - skip_dirs="-not -path '*/$bower_components/*' \ - -not -path '*/node_modules/*'" - - cut_and_join() { - awk 'BEGIN { FS=":" } - $3 ~ /\/\*/ || $3 ~ /\*\// { print $0 ; next } - $1!=key { if (key!="") print out ; key=$1 ; out=$1":"$2 ; next } - { out=out","$2 } - END { print out }' 2>/dev/null - } - - if echo "$network" | grep -m1 '.kt$' 1>/dev/null; - then - # skip brackets and comments - cd "$git_root" && \ - find . -type f \ - -name '*.kt' \ - -exec \ - grep -nIHE -e "$syntax_bracket" \ - -e "$syntax_comment_block" {} \; \ - | cut_and_join \ - >> "$adjustments_file" \ - || echo '' - - # last line in file - cd "$git_root" && \ - find . -type f \ - -name '*.kt' -exec \ - wc -l {} \; \ - | while read -r l; do echo "EOF: $l"; done \ - 2>/dev/null \ - >> "$adjustments_file" \ - || echo '' - fi - - if echo "$network" | grep -m1 '.go$' 1>/dev/null; - then - # skip empty lines, comments, and brackets - cd "$git_root" && \ - find . -type f \ - -not -path '*/vendor/*' \ - -not -path '*/caches/*' \ - -name '*.go' \ - -exec \ - grep -nIHE \ - -e "$empty_line" \ - -e "$syntax_comment" \ - -e "$syntax_comment_block" \ - -e "$syntax_bracket" \ - -e "$syntax_go_func" \ - {} \; \ - | cut_and_join \ - >> "$adjustments_file" \ - || echo '' - fi - - if echo "$network" | grep -m1 '.dart$' 1>/dev/null; - then - # skip brackets - cd "$git_root" && \ - find . -type f \ - -name '*.dart' \ - -exec \ - grep -nIHE \ - -e "$syntax_bracket" \ - {} \; \ - | cut_and_join \ - >> "$adjustments_file" \ - || echo '' - fi - - if echo "$network" | grep -m1 '.php$' 1>/dev/null; - then - # skip empty lines, comments, and brackets - cd "$git_root" && \ - find . -type f \ - -not -path "*/vendor/*" \ - -name '*.php' \ - -exec \ - grep -nIHE \ - -e "$syntax_list" \ - -e "$syntax_bracket" \ - -e '^[[:space:]]*\);[[:space:]]*(//.*)?$' \ - {} \; \ - | cut_and_join \ - >> "$adjustments_file" \ - || echo '' - fi - - if echo "$network" | grep -m1 '\(.c\.cpp\|.cxx\|.h\|.hpp\|.m\|.swift\|.vala\)$' 1>/dev/null; - then - # skip brackets - # shellcheck disable=SC2086,SC2090 - cd "$git_root" && \ - find . -type f \ - $skip_dirs \ - \( \ - -name '*.c' \ - -or -name '*.cpp' \ - -or -name '*.cxx' \ - -or -name '*.h' \ - -or -name '*.hpp' \ - -or -name '*.m' \ - -or -name '*.swift' \ - -or -name '*.vala' \ - \) -exec \ - grep -nIHE \ - -e "$empty_line" \ - -e "$syntax_bracket" \ - -e '// LCOV_EXCL' \ - {} \; \ - | cut_and_join \ - >> "$adjustments_file" \ - || echo '' - - # skip brackets - # shellcheck disable=SC2086,SC2090 - cd "$git_root" && \ - find . -type f \ - $skip_dirs \ - \( \ - -name '*.c' \ - -or -name '*.cpp' \ - -or -name '*.cxx' \ - -or -name '*.h' \ - -or -name '*.hpp' \ - -or -name '*.m' \ - -or -name '*.swift' \ - -or -name '*.vala' \ - \) -exec \ - grep -nIH '// LCOV_EXCL' \ - {} \; \ - >> "$adjustments_file" \ - || echo '' - - fi - - found=$(< "$adjustments_file" tr -d ' ') - - if [ "$found" != "" ]; - then - say " ${g}+${x} Found adjustments" - { - echo "# path=fixes"; - cat "$adjustments_file"; - echo "<<<<<< EOF"; - } >> "$upload_file" - rm -rf "$adjustments_file" - else - say " ${e}->${x} No adjustments found" - fi -fi - -if [ "$url_o" != "" ]; -then - url="$url_o" -fi - -if [ "$dump" != "0" ]; -then - # trim whitespace from query - say " ${e}->${x} Dumping upload file (no upload)" - echo "$url/upload/v4?$(echo "package=$package-$VERSION&$query" | tr -d ' ')" - cat "$upload_file" -else - if [ "$save_to" != "" ]; - then - say "${e}==>${x} Copying upload file to ${save_to}" - mkdir -p "$(dirname "$save_to")" - cp "$upload_file" "$save_to" - fi - - say "${e}==>${x} Gzipping contents" - gzip -nf9 "$upload_file" - say " $(du -h "$upload_file.gz")" - - query=$(echo "${query}" | tr -d ' ') - say "${e}==>${x} Uploading reports" - say " ${e}url:${x} $url" - say " ${e}query:${x} $query" - - # Full query (to display on terminal output) - query=$(echo "package=$package-$VERSION&token=$token&$query" | tr -d ' ') - queryNoToken=$(echo "package=$package-$VERSION&token=&$query" | tr -d ' ') - - if [ "$ft_s3" = "1" ]; - then - say "${e}->${x} Pinging Codecov" - say "$url/upload/v4?$queryNoToken" - # shellcheck disable=SC2086,2090 - res=$(curl $curl_s -X POST $cacert \ - --retry 5 --retry-delay 2 --connect-timeout 2 \ - -H 'X-Reduced-Redundancy: false' \ - -H 'X-Content-Type: application/x-gzip' \ - -H 'Content-Length: 0' \ - -H "X-Upload-Token: ${token}" \ - --write-out "\n%{response_code}\n" \ - $curlargs \ - "$url/upload/v4?$query" || true) - # a good reply is "https://codecov.io" + "\n" + "https://storage.googleapis.com/codecov/..." - s3target=$(echo "$res" | sed -n 2p) - status=$(tail -n1 <<< "$res") - - if [ "$status" = "200" ] && [ "$s3target" != "" ]; - then - say "${e}->${x} Uploading to" - say "${s3target}" - - # shellcheck disable=SC2086 - s3=$(curl -fiX PUT \ - --data-binary @"$upload_file.gz" \ - -H 'Content-Type: application/x-gzip' \ - -H 'Content-Encoding: gzip' \ - $curlawsargs \ - "$s3target" || true) - - if [ "$s3" != "" ]; - then - say " ${g}->${x} Reports have been successfully queued for processing at ${b}$(echo "$res" | sed -n 1p)${x}" - exit 0 - else - say " ${r}X>${x} Failed to upload" - fi - elif [ "$status" = "400" ]; - then - # 400 Error - say "${r}${res}${x}" - exit ${exit_with} - else - say "${r}${res}${x}" - fi - fi - - say "${e}==>${x} Uploading to Codecov" - - # shellcheck disable=SC2086,2090 - res=$(curl -X POST $cacert \ - --data-binary @"$upload_file.gz" \ - --retry 5 --retry-delay 2 --connect-timeout 2 \ - -H 'Content-Type: text/plain' \ - -H 'Content-Encoding: gzip' \ - -H 'X-Content-Encoding: gzip' \ - -H "X-Upload-Token: ${token}" \ - -H 'Accept: text/plain' \ - $curlargs \ - "$url/upload/v2?$query&attempt=$i" || echo 'HTTP 500') - # {"message": "Coverage reports upload successfully", "uploaded": true, "queued": true, "id": "...", "url": "https://codecov.io/..."\} - uploaded=$(grep -o '\"uploaded\": [a-z]*' <<< "$res" | head -1 | cut -d' ' -f2) - if [ "$uploaded" = "true" ] - then - say " Reports have been successfully queued for processing at ${b}$(echo "$res" | head -2 | tail -1)${x}" - exit 0 - else - say " ${g}${res}${x}" - exit ${exit_with} - fi - - say " ${r}X> Failed to upload coverage reports${x}" -fi - -exit ${exit_with} diff --git a/tools/bin/go_core_tests b/tools/bin/go_core_tests index 694a51d1f82..074527698b3 100755 --- a/tools/bin/go_core_tests +++ b/tools/bin/go_core_tests @@ -32,7 +32,6 @@ if [[ $EXITCODE != 0 ]]; then echo "Encountered test failures." else echo "All tests passed!" - # uploading coverage.txt to CodeCov - $(dirname "$0")/codecov -f coverage.txt fi +echo "go_core_tests exiting with code $EXITCODE" exit $EXITCODE diff --git a/tools/flakeytests/runner.go b/tools/flakeytests/runner.go index 88ab647e7c1..a37b123d5cf 100644 --- a/tools/flakeytests/runner.go +++ b/tools/flakeytests/runner.go @@ -219,8 +219,8 @@ func (r *Runner) runTests(rep *Report) (*Report, error) { ts = append(ts, test) } - log.Printf("[FLAKEY_TEST] Executing test command with parameters: pkg=%s, tests=%+v, numReruns=%d\n", pkg, ts, r.numReruns) for i := 0; i < r.numReruns; i++ { + log.Printf("[FLAKEY_TEST] Executing test command with parameters: pkg=%s, tests=%+v, numReruns=%d currentRun=%d\n", pkg, ts, r.numReruns, i) pr, err := r.runTest(pkg, ts) if err != nil { return report, err @@ -237,8 +237,8 @@ func (r *Runner) runTests(rep *Report) (*Report, error) { } for pkg := range rep.packagePanics { - log.Printf("[PACKAGE_PANIC]: Executing test command with parameters: pkg=%s, numReruns=%d\n", pkg, r.numReruns) for i := 0; i < r.numReruns; i++ { + log.Printf("[PACKAGE_PANIC]: Executing test command with parameters: pkg=%s, numReruns=%d currentRun=%d\n", pkg, r.numReruns, i) pr, err := r.runTest(pkg, []string{}) if err != nil { return report, err diff --git a/tools/flakeytests/runner_test.go b/tools/flakeytests/runner_test.go index ee069a1655d..8f9433a427b 100644 --- a/tools/flakeytests/runner_test.go +++ b/tools/flakeytests/runner_test.go @@ -14,6 +14,17 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" ) +// This test suite has two sets of tests that are run in two different modes: +// 1. Normal mode: The tests are run as usual. All tests that start with TestSkippedForTests will be skipped. +// There are a set of integration tests that will run this same test suite in integration mode. +// 2. Integration mode: The tests are run with an environment variable set to run the tests that were skipped in normal mode. +// Their output is captured and parsed to check if the flake runner is behaving as expected. +// +// In summary, the first set of tests are executed by the user, and these tests will trigger the second set of tests to run. +var runInIntegrationTestModeEnvKey = "FLAKEY_TEST_RUNNER_RUN_FIXTURE_TEST" +var skipIntegrationTestMode = os.Getenv(runInIntegrationTestModeEnvKey) != "1" +var runInIntegrationTestModeEnv = runInIntegrationTestModeEnvKey + "=1" + type mockReporter struct { report *Report } @@ -307,7 +318,7 @@ func TestRunner_RerunWithNonZeroExitCodeDoesntStopCommand(t *testing.T) { // Used for integration tests func TestSkippedForTests_Subtests(t *testing.T) { - if os.Getenv("FLAKEY_TEST_RUNNER_RUN_FIXTURE_TEST") != "1" { + if skipIntegrationTestMode { t.Skip() } @@ -322,7 +333,7 @@ func TestSkippedForTests_Subtests(t *testing.T) { // Used for integration tests func TestSkippedForTests(t *testing.T) { - if os.Getenv("FLAKEY_TEST_RUNNER_RUN_FIXTURE_TEST") != "1" { + if skipIntegrationTestMode { t.Skip() } @@ -333,7 +344,7 @@ func TestSkippedForTests(t *testing.T) { // Used for integration tests func TestSkippedForTests_Success(t *testing.T) { - if os.Getenv("FLAKEY_TEST_RUNNER_RUN_FIXTURE_TEST") != "1" { + if skipIntegrationTestMode { t.Skip() } @@ -356,7 +367,7 @@ func TestIntegration_DealsWithSubtests(t *testing.T) { repo: "github.com/smartcontractkit/chainlink/v2/tools/flakeytests", command: "../bin/go_core_tests", overrides: func(cmd *exec.Cmd) { - cmd.Env = append(cmd.Env, "FLAKEY_TESTRUNNER_RUN_FIXTURE_TEST=1") + cmd.Env = append(cmd.Env, runInIntegrationTestModeEnv) cmd.Stdout = io.Discard cmd.Stderr = io.Discard }, @@ -392,7 +403,7 @@ func TestIntegration_ParsesPanics(t *testing.T) { repo: "github.com/smartcontractkit/chainlink/v2/tools/flakeytests", command: "../bin/go_core_tests", overrides: func(cmd *exec.Cmd) { - cmd.Env = append(cmd.Env, "FLAKEY_TESTRUNNER_RUN_FIXTURE_TEST=1") + cmd.Env = append(cmd.Env, runInIntegrationTestModeEnv) cmd.Stdout = io.Discard cmd.Stderr = io.Discard }, @@ -423,7 +434,7 @@ func TestIntegration(t *testing.T) { repo: "github.com/smartcontractkit/chainlink/v2/tools/flakeytests", command: "../bin/go_core_tests", overrides: func(cmd *exec.Cmd) { - cmd.Env = append(cmd.Env, "FLAKEY_TESTRUNNER_RUN_FIXTURE_TEST=1") + cmd.Env = append(cmd.Env, runInIntegrationTestModeEnv) cmd.Stdout = io.Discard cmd.Stderr = io.Discard },