From de268e98b8d68a284e1260297925b91c5d2411bc Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 30 Sep 2024 10:19:35 -0500 Subject: [PATCH 01/28] fixing multinode state transition logic by register polling subscription in rpc client (#14534) * polling subscription to be registered * adding changeset * fix Subscribe new head * add test * update changeset * fix lint * fix deadlock and add unit test for http polling sub * update changeset * adding sub closed check and remove import * add unit test coverage for http polling subscribeToHeads * update test * address comments part 1 * clean * part 2 * fix lint * fix lint --- .changeset/moody-rules-agree.md | 8 ++ core/chains/evm/client/rpc_client.go | 47 +++++++- core/chains/evm/client/rpc_client_test.go | 127 ++++++++++++++++++++++ 3 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 .changeset/moody-rules-agree.md diff --git a/.changeset/moody-rules-agree.md b/.changeset/moody-rules-agree.md new file mode 100644 index 00000000000..ef1f3bcaf62 --- /dev/null +++ b/.changeset/moody-rules-agree.md @@ -0,0 +1,8 @@ +--- +"chainlink": patch +--- + +- register polling subscription to avoid subscription leaking when rpc client gets closed. +- add a temporary special treatment for SubscribeNewHead before we replace it with SubscribeToHeads. Add a goroutine that forwards new head from poller to caller channel. +- fix a deadlock in poller, by using a new lock for subs slice in rpc client. +#bugfix diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index 763348173aa..a29ed5e118c 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -129,7 +129,8 @@ type rpcClient struct { ws rawclient http *rawclient - stateMu sync.RWMutex // protects state* fields + stateMu sync.RWMutex // protects state* fields + subsSliceMu sync.RWMutex // protects subscription slice // Need to track subscriptions because closing the RPC does not (always?) // close the underlying subscription @@ -317,8 +318,8 @@ func (r *rpcClient) getRPCDomain() string { // registerSub adds the sub to the rpcClient list func (r *rpcClient) registerSub(sub ethereum.Subscription, stopInFLightCh chan struct{}) error { - r.stateMu.Lock() - defer r.stateMu.Unlock() + r.subsSliceMu.Lock() + defer r.subsSliceMu.Unlock() // ensure that the `sub` belongs to current life cycle of the `rpcClient` and it should not be killed due to // previous `DisconnectAll` call. select { @@ -335,12 +336,16 @@ func (r *rpcClient) registerSub(sub ethereum.Subscription, stopInFLightCh chan s // DisconnectAll disconnects all clients connected to the rpcClient func (r *rpcClient) DisconnectAll() { r.stateMu.Lock() - defer r.stateMu.Unlock() if r.ws.rpc != nil { r.ws.rpc.Close() } r.cancelInflightRequests() + r.stateMu.Unlock() + + r.subsSliceMu.Lock() r.unsubscribeAll() + r.subsSliceMu.Unlock() + r.chainInfoLock.Lock() r.latestChainInfo = commonclient.ChainInfo{} r.chainInfoLock.Unlock() @@ -496,11 +501,30 @@ func (r *rpcClient) SubscribeNewHead(ctx context.Context, channel chan<- *evmtyp if r.newHeadsPollInterval > 0 { interval := r.newHeadsPollInterval timeout := interval - poller, _ := commonclient.NewPoller[*evmtypes.Head](interval, r.latestBlock, timeout, r.rpcLog) + poller, pollerCh := commonclient.NewPoller[*evmtypes.Head](interval, r.latestBlock, timeout, r.rpcLog) if err = poller.Start(ctx); err != nil { return nil, err } + // NOTE this is a temporary special treatment for SubscribeNewHead before we refactor head tracker to use SubscribeToHeads + // as we need to forward new head from the poller channel to the channel passed from caller. + go func() { + for head := range pollerCh { + select { + case channel <- head: + // forwarding new head to the channel passed from caller + case <-poller.Err(): + // return as poller returns error + return + } + } + }() + + err = r.registerSub(&poller, chStopInFlight) + if err != nil { + return nil, err + } + lggr.Debugf("Polling new heads over http") return &poller, nil } @@ -547,6 +571,11 @@ func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H return nil, nil, err } + err = r.registerSub(&poller, chStopInFlight) + if err != nil { + return nil, nil, err + } + lggr.Debugf("Polling new heads over http") return channel, &poller, nil } @@ -579,6 +608,8 @@ func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H } func (r *rpcClient) SubscribeToFinalizedHeads(ctx context.Context) (<-chan *evmtypes.Head, commontypes.Subscription, error) { + ctx, cancel, chStopInFlight, _, _ := r.acquireQueryCtx(ctx, r.rpcTimeout) + defer cancel() interval := r.finalizedBlockPollInterval if interval == 0 { return nil, nil, errors.New("FinalizedBlockPollInterval is 0") @@ -588,6 +619,12 @@ func (r *rpcClient) SubscribeToFinalizedHeads(ctx context.Context) (<-chan *evmt if err := poller.Start(ctx); err != nil { return nil, nil, err } + + err := r.registerSub(&poller, chStopInFlight) + if err != nil { + return nil, nil, err + } + return channel, &poller, nil } diff --git a/core/chains/evm/client/rpc_client_test.go b/core/chains/evm/client/rpc_client_test.go index b594a0ca166..d959f8d1115 100644 --- a/core/chains/evm/client/rpc_client_test.go +++ b/core/chains/evm/client/rpc_client_test.go @@ -19,6 +19,8 @@ import ( "github.com/tidwall/gjson" "go.uber.org/zap" + commontypes "github.com/smartcontractkit/chainlink/v2/common/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -57,6 +59,25 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { } return } + + checkClosedRPCClientShouldRemoveExistingSub := func(t tests.TestingT, ctx context.Context, sub commontypes.Subscription, rpcClient client.RPCClient) { + errCh := sub.Err() + + // ensure sub exists + require.Equal(t, int32(1), rpcClient.SubscribersCount()) + rpcClient.DisconnectAll() + + // ensure sub is closed + select { + case <-errCh: // ok + default: + assert.Fail(t, "channel should be closed") + } + + require.NoError(t, rpcClient.Dial(ctx)) + require.Equal(t, int32(0), rpcClient.SubscribersCount()) + } + t.Run("Updates chain info on new blocks", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() @@ -131,6 +152,50 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { assert.Equal(t, int64(0), highestUserObservations.FinalizedBlockNumber) assert.Equal(t, (*big.Int)(nil), highestUserObservations.TotalDifficulty) }) + t.Run("SubscribeToHeads with http polling enabled will update new heads", func(t *testing.T) { + type rpcServer struct { + Head *evmtypes.Head + URL *url.URL + } + createRPCServer := func() *rpcServer { + server := &rpcServer{} + server.Head = &evmtypes.Head{Number: 127} + server.URL = testutils.NewWSServer(t, chainId, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + assert.Equal(t, "eth_getBlockByNumber", method) + if assert.True(t, params.IsArray()) && assert.Equal(t, "latest", params.Array()[0].String()) { + head := server.Head + jsonHead, err := json.Marshal(head) + if err != nil { + panic(fmt.Errorf("failed to marshal head: %w", err)) + } + resp.Result = string(jsonHead) + } + + return + }).WSURL() + return server + } + + server := createRPCServer() + rpc := client.NewRPCClient(lggr, *server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, tests.TestInterval, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + latest, highestUserObservations := rpc.GetInterceptedChainInfo() + // latest chain info hasn't been initialized + assert.Equal(t, int64(0), latest.BlockNumber) + assert.Equal(t, int64(0), highestUserObservations.BlockNumber) + + headCh, sub, err := rpc.SubscribeToHeads(commonclient.CtxAddHealthCheckFlag(tests.Context(t))) + require.NoError(t, err) + defer sub.Unsubscribe() + + head := <-headCh + assert.Equal(t, server.Head.Number, head.BlockNumber()) + // the http polling subscription should update the head block + latest, highestUserObservations = rpc.GetInterceptedChainInfo() + assert.Equal(t, server.Head.Number, latest.BlockNumber) + assert.Equal(t, server.Head.Number, highestUserObservations.BlockNumber) + }) t.Run("Concurrent Unsubscribe and onNewHead calls do not lead to a deadlock", func(t *testing.T) { const numberOfAttempts = 1000 // need a large number to increase the odds of reproducing the issue server := testutils.NewWSServer(t, chainId, serverCallBack) @@ -184,6 +249,68 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { require.ErrorContains(t, err, "RPCClient returned error (rpc)") tests.AssertLogEventually(t, observed, "evmclient.Client#EthSubscribe RPC call failure") }) + t.Run("Closed rpc client should remove existing SubscribeNewHead subscription with WS", func(t *testing.T) { + server := testutils.NewWSServer(t, chainId, serverCallBack) + wsURL := server.WSURL() + + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + + ch := make(chan *evmtypes.Head) + sub, err := rpc.SubscribeNewHead(tests.Context(t), ch) + require.NoError(t, err) + checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc) + }) + t.Run("Closed rpc client should remove existing SubscribeNewHead subscription with HTTP polling", func(t *testing.T) { + server := testutils.NewWSServer(t, chainId, serverCallBack) + wsURL := server.WSURL() + + rpc := client.NewRPCClient(lggr, *wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 1, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + + ch := make(chan *evmtypes.Head) + sub, err := rpc.SubscribeNewHead(tests.Context(t), ch) + require.NoError(t, err) + checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc) + }) + t.Run("Closed rpc client should remove existing SubscribeToHeads subscription with WS", func(t *testing.T) { + server := testutils.NewWSServer(t, chainId, serverCallBack) + wsURL := server.WSURL() + + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + + _, sub, err := rpc.SubscribeToHeads(tests.Context(t)) + require.NoError(t, err) + checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc) + }) + t.Run("Closed rpc client should remove existing SubscribeToHeads subscription with HTTP polling", func(t *testing.T) { + server := testutils.NewWSServer(t, chainId, serverCallBack) + wsURL := server.WSURL() + + rpc := client.NewRPCClient(lggr, *wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 1, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + + _, sub, err := rpc.SubscribeToHeads(tests.Context(t)) + require.NoError(t, err) + checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc) + }) + t.Run("Closed rpc client should remove existing SubscribeToFinalizedHeads subscription", func(t *testing.T) { + server := testutils.NewWSServer(t, chainId, serverCallBack) + wsURL := server.WSURL() + + rpc := client.NewRPCClient(lggr, *wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 1, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + + _, sub, err := rpc.SubscribeToFinalizedHeads(tests.Context(t)) + require.NoError(t, err) + checkClosedRPCClientShouldRemoveExistingSub(t, ctx, sub, rpc) + }) t.Run("Subscription error is properly wrapper", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() From 19690b0c4499cf01379f98396917be170ba2b333 Mon Sep 17 00:00:00 2001 From: Mateusz Sekara Date: Mon, 30 Sep 2024 17:57:43 +0200 Subject: [PATCH 02/28] CCIP-3555 Providing evm specific implementation of tokenDataEncoder (#14603) * Providing evm specific implementation of tokenDataEncoder * Providing evm specific implementation of tokenDataEncoder * go mod tidy * Post review fixes * Bump * Bump * Bump --- .changeset/honest-cameras-cross.md | 5 ++ core/capabilities/ccip/ccipevm/tokendata.go | 38 ++++++++++ .../ccip/ccipevm/tokendata_test.go | 73 +++++++++++++++++++ .../capabilities/ccip/oraclecreator/plugin.go | 4 +- core/scripts/go.mod | 4 +- core/scripts/go.sum | 8 +- go.mod | 4 +- go.sum | 8 +- integration-tests/go.mod | 4 +- integration-tests/go.sum | 8 +- integration-tests/load/go.mod | 4 +- integration-tests/load/go.sum | 8 +- 12 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 .changeset/honest-cameras-cross.md create mode 100644 core/capabilities/ccip/ccipevm/tokendata.go create mode 100644 core/capabilities/ccip/ccipevm/tokendata_test.go diff --git a/.changeset/honest-cameras-cross.md b/.changeset/honest-cameras-cross.md new file mode 100644 index 00000000000..1da50c97bd5 --- /dev/null +++ b/.changeset/honest-cameras-cross.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Implementing evm specific token data encoder for CCIP #internal diff --git a/core/capabilities/ccip/ccipevm/tokendata.go b/core/capabilities/ccip/ccipevm/tokendata.go new file mode 100644 index 00000000000..5c205a8739c --- /dev/null +++ b/core/capabilities/ccip/ccipevm/tokendata.go @@ -0,0 +1,38 @@ +package ccipevm + +import ( + "context" + + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" +) + +type usdcAttestationPayload struct { + Message []byte + Attestation []byte +} + +func (m usdcAttestationPayload) AbiString() string { + return ` + [{ + "components": [ + {"name": "message", "type": "bytes"}, + {"name": "attestation", "type": "bytes"} + ], + "type": "tuple" + }]` +} + +type EVMTokenDataEncoder struct{} + +func NewEVMTokenDataEncoder() EVMTokenDataEncoder { + return EVMTokenDataEncoder{} +} + +func (e EVMTokenDataEncoder) EncodeUSDC(_ context.Context, message cciptypes.Bytes, attestation cciptypes.Bytes) (cciptypes.Bytes, error) { + return abihelpers.EncodeAbiStruct(usdcAttestationPayload{ + Message: message, + Attestation: attestation, + }) +} diff --git a/core/capabilities/ccip/ccipevm/tokendata_test.go b/core/capabilities/ccip/ccipevm/tokendata_test.go new file mode 100644 index 00000000000..7479d764071 --- /dev/null +++ b/core/capabilities/ccip/ccipevm/tokendata_test.go @@ -0,0 +1,73 @@ +package ccipevm + +import ( + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/stretchr/testify/require" + + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" +) + +func Test_EVMTokenDataEncoder(t *testing.T) { + var empty usdcAttestationPayload + encoder := NewEVMTokenDataEncoder() + + //https://testnet.snowtrace.io/tx/0xeeb0ad6b26bacd1570a9361724a36e338f4aacf1170dec64399220b7483b7eed/eventlog?chainid=43113 + //https://iris-api-sandbox.circle.com/v1/attestations/0x69fb1b419d648cf6c9512acad303746dc85af3b864af81985c76764aba60bf6b + realMessage, err := cciptypes.NewBytesFromString("0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f8000000000000000100000006000000000004ac0d000000000000000000000000eb08f243e5d3fcff26a9e38ae5520a669f4019d00000000000000000000000009f3b8679c73c2fef8b59b4f3444d4e156fb70aa5000000000000000000000000c08835adf4884e51ff076066706e407506826d9d000000000000000000000000000000005425890298aed601595a70ab815c96711a31bc650000000000000000000000004f32ae7f112c26b109357785e5c66dc5d747fbce00000000000000000000000000000000000000000000000000000000000000640000000000000000000000007a4d8f8c18762d362e64b411d7490fba112811cd0000000000000000") + require.NoError(t, err) + realAttestation, err := cciptypes.NewBytesFromString("0xee466fbd340596aa56e3e40d249869573e4008d84d795b4f2c3cba8649083d08653d38190d0df7e0ee12ae685df2f806d100a03b3716ab1ff2013c7201f1c2d01c9af959b55a4b52dbd0319eed69ce9ace25259830e0b1bff79faf0c9c5d1b5e6d6304e824d657db38f802bcff3e97d0bd30f2ffc62b62381f52c1668ceaa5a73a1b") + require.NoError(t, err) + + tt := []struct { + name string + message []byte + attestation []byte + }{ + { + name: "empty both fields", + message: nil, + attestation: []byte{}, + }, + { + name: "empty attestation", + message: []byte("message"), + attestation: nil, + }, + { + name: "both attestation and message are set", + message: realMessage, + attestation: realAttestation, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + got, err := encoder.EncodeUSDC(tests.Context(t), tc.message, tc.attestation) + require.NoError(t, err) + + decoded, err := abihelpers.ABIDecode(empty.AbiString(), got) + require.NoError(t, err) + + converted := abi.ConvertType(decoded[0], &empty) + casted, ok := converted.(*usdcAttestationPayload) + require.True(t, ok) + + if tc.message == nil { + require.Empty(t, casted.Message) + } else { + require.Equal(t, tc.message, casted.Message) + } + + if tc.attestation == nil { + require.Empty(t, casted.Attestation) + } else { + require.Equal(t, tc.attestation, casted.Attestation) + } + }) + } +} diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index 3515ae39994..07682ba60e9 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -11,7 +11,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" - "github.com/smartcontractkit/chainlink-ccip/execute/tokendata" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" evmconfig "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ocrimpls" @@ -33,6 +32,7 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -242,7 +242,7 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter( ccipevm.NewExecutePluginCodecV1(), ccipevm.NewMessageHasherV1(), i.homeChainReader, - &tokendata.NoopTokenDataObserver{}, + ccipevm.NewEVMTokenDataEncoder(), ccipevm.NewGasEstimateProvider(), contractReaders, chainWriters, diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 82baeff7bdc..b32ae445022 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -22,7 +22,7 @@ require ( github.com/prometheus/client_golang v1.20.0 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/spf13/cobra v1.8.1 @@ -271,7 +271,7 @@ require ( github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.23 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index c94eb80a17c..6433035ca02 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1081,10 +1081,10 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca h1:cvifSiZr9VifpKjgVtGaEqknz2MwpY4shQhoOLpgQUE= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca/go.mod h1:hxKOwuo3HBC9a5hoNOvstl2wVF6Z9kZDssHU5VCdqk8= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57 h1:pRiTiFOkPEyvgG0hchcCSZzwUbwYydnZBu0QbVaRnVk= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 h1:z+XnayyX7pyvVv9OuMQ7oik7RkguQeWHhxcOoVM4oKI= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= diff --git a/go.mod b/go.mod index f49dabbad39..73c38b8c2db 100644 --- a/go.mod +++ b/go.mod @@ -74,8 +74,8 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca - github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd + github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f diff --git a/go.sum b/go.sum index e3b97a90da9..ea1df80842d 100644 --- a/go.sum +++ b/go.sum @@ -1042,10 +1042,10 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca h1:cvifSiZr9VifpKjgVtGaEqknz2MwpY4shQhoOLpgQUE= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca/go.mod h1:hxKOwuo3HBC9a5hoNOvstl2wVF6Z9kZDssHU5VCdqk8= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57 h1:pRiTiFOkPEyvgG0hchcCSZzwUbwYydnZBu0QbVaRnVk= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 h1:z+XnayyX7pyvVv9OuMQ7oik7RkguQeWHhxcOoVM4oKI= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 2d46e7208d8..b6dab3a01f9 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -39,8 +39,8 @@ require ( github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca - github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd + github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 4ff2f7169d1..7ab1481ca95 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1423,10 +1423,10 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca h1:cvifSiZr9VifpKjgVtGaEqknz2MwpY4shQhoOLpgQUE= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca/go.mod h1:hxKOwuo3HBC9a5hoNOvstl2wVF6Z9kZDssHU5VCdqk8= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57 h1:pRiTiFOkPEyvgG0hchcCSZzwUbwYydnZBu0QbVaRnVk= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 h1:z+XnayyX7pyvVv9OuMQ7oik7RkguQeWHhxcOoVM4oKI= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 4efb4622274..975b1a13d35 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -15,7 +15,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/slack-go/slack v0.12.2 - github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.1 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 @@ -30,7 +30,7 @@ require ( require ( github.com/AlekSi/pointer v1.1.0 // indirect github.com/smartcontractkit/chainlink-automation v1.0.4 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd // indirect github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 // indirect ) diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 2d606ba6f98..3d40c225116 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1397,10 +1397,10 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca h1:cvifSiZr9VifpKjgVtGaEqknz2MwpY4shQhoOLpgQUE= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930060710-158d2a9ac9ca/go.mod h1:hxKOwuo3HBC9a5hoNOvstl2wVF6Z9kZDssHU5VCdqk8= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57 h1:pRiTiFOkPEyvgG0hchcCSZzwUbwYydnZBu0QbVaRnVk= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240927162447-20630b333f57/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 h1:z+XnayyX7pyvVv9OuMQ7oik7RkguQeWHhxcOoVM4oKI= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= From 02472a67a47d6d0e78f7d65d99285b989ed5b008 Mon Sep 17 00:00:00 2001 From: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:14:03 -0700 Subject: [PATCH 03/28] Ccip-3535 env abstraction updated (#14598) * env changes * changes * lint * jd changes * fix typo --------- Co-authored-by: Connor Stein --- .../deployment/ccip/add_chain_test.go | 8 +- .../deployment/ccip/add_lane_test.go | 2 +- .../ccip/changeset/2_initial_deploy_test.go | 6 +- .../deployment/ccip/test_helpers.go | 130 ++-- .../deployment/devenv/build_env.go | 32 +- integration-tests/deployment/devenv/don.go | 73 ++- .../deployment/devenv/environment.go | 16 +- integration-tests/deployment/devenv/jd.go | 44 +- .../deployment/memory/environment.go | 17 - .../deployment/memory/job_client.go | 9 + integration-tests/smoke/ccip_test.go | 59 +- integration-tests/web/sdk/client/client.go | 64 +- .../web/sdk/internal/generated/generated.go | 555 +++++++++++++++++- .../web/sdk/internal/genqlient.graphql | 40 +- 14 files changed, 885 insertions(+), 170 deletions(-) diff --git a/integration-tests/deployment/ccip/add_chain_test.go b/integration-tests/deployment/ccip/add_chain_test.go index dbe86b85368..fc39bddcd93 100644 --- a/integration-tests/deployment/ccip/add_chain_test.go +++ b/integration-tests/deployment/ccip/add_chain_test.go @@ -24,8 +24,7 @@ import ( func TestAddChainInbound(t *testing.T) { // 4 chains where the 4th is added after initial deployment. - e := NewEnvironmentWithCRAndJobs(t, logger.TestLogger(t), 4) - require.Equal(t, len(e.Nodes), 5) + e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 4) state, err := LoadOnchainState(e.Env, e.Ab) require.NoError(t, err) // Take first non-home chain as the new chain. @@ -127,6 +126,9 @@ func TestAddChainInbound(t *testing.T) { ExecuteProposal(t, e.Env, chainInboundExec, state, sel) } + replayBlocks, err := LatestBlocksByChain(testcontext.Get(t), e.Env.Chains) + require.NoError(t, err) + // Now configure the new chain using deployer key (not transferred to timelock yet). var offRampEnables []offramp.OffRampSourceChainConfigArgs for _, source := range initialDeploy { @@ -167,7 +169,7 @@ func TestAddChainInbound(t *testing.T) { } // Ensure job related logs are up to date. time.Sleep(30 * time.Second) - require.NoError(t, ReplayAllLogs(e.Nodes, e.Env.Chains)) + ReplayLogs(t, e.Env.Offchain, replayBlocks) // TODO: Send via all inbound lanes and use parallel helper // Now that the proposal has been executed we expect to be able to send traffic to this new 4th chain. diff --git a/integration-tests/deployment/ccip/add_lane_test.go b/integration-tests/deployment/ccip/add_lane_test.go index 3da74ec11a8..540b5dded54 100644 --- a/integration-tests/deployment/ccip/add_lane_test.go +++ b/integration-tests/deployment/ccip/add_lane_test.go @@ -16,7 +16,7 @@ func TestAddLane(t *testing.T) { // TODO: The offchain code doesn't yet support partial lane // enablement, need to address then re-enable this test. t.Skip() - e := NewEnvironmentWithCRAndJobs(t, logger.TestLogger(t), 3) + e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 3) // Here we have CR + nodes set up, but no CCIP contracts deployed. state, err := LoadOnchainState(e.Env, e.Ab) require.NoError(t, err) diff --git a/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go b/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go index 8dc363b0cbb..5744dfbea95 100644 --- a/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go +++ b/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go @@ -20,10 +20,8 @@ import ( func Test0002_InitialDeploy(t *testing.T) { lggr := logger.TestLogger(t) ctx := ccdeploy.Context(t) - tenv := ccdeploy.NewEnvironmentWithCRAndFeeds(t, lggr, 3) + tenv := ccdeploy.NewMemoryEnvironment(t, lggr, 3) e := tenv.Env - nodes := tenv.Nodes - chains := e.Chains state, err := ccdeploy.LoadOnchainState(tenv.Env, tenv.Ab) require.NoError(t, err) @@ -52,7 +50,7 @@ func Test0002_InitialDeploy(t *testing.T) { require.NoError(t, err) // Ensure capreg logs are up to date. - require.NoError(t, ccdeploy.ReplayAllLogs(nodes, chains)) + ccdeploy.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) // Apply the jobs. for nodeID, jobs := range output.JobSpecs { diff --git a/integration-tests/deployment/ccip/test_helpers.go b/integration-tests/deployment/ccip/test_helpers.go index c306ba4fd6a..59eecae4f29 100644 --- a/integration-tests/deployment/ccip/test_helpers.go +++ b/integration-tests/deployment/ccip/test_helpers.go @@ -9,6 +9,7 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/pkg/errors" "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" @@ -64,11 +65,39 @@ type DeployedEnv struct { Env deployment.Environment HomeChainSel uint64 FeedChainSel uint64 + ReplayBlocks map[uint64]uint64 } -type DeployedTestEnvironment struct { - DeployedEnv - Nodes map[string]memory.Node +func (e *DeployedEnv) SetupJobs(t *testing.T) { + ctx := testcontext.Get(t) + jbs, err := NewCCIPJobSpecs(e.Env.NodeIDs, e.Env.Offchain) + require.NoError(t, err) + for nodeID, jobs := range jbs { + for _, job := range jobs { + // Note these auto-accept + _, err := e.Env.Offchain.ProposeJob(ctx, + &jobv1.ProposeJobRequest{ + NodeId: nodeID, + Spec: job, + }) + require.NoError(t, err) + } + } + // Wait for plugins to register filters? + // TODO: Investigate how to avoid. + time.Sleep(30 * time.Second) + ReplayLogs(t, e.Env.Offchain, e.ReplayBlocks) +} + +func ReplayLogs(t *testing.T, oc deployment.OffchainClient, replayBlocks map[uint64]uint64) { + switch oc := oc.(type) { + case *memory.JobClient: + require.NoError(t, oc.ReplayLogs(replayBlocks)) + case *devenv.JobDistributor: + require.NoError(t, oc.ReplayLogs(replayBlocks)) + default: + t.Fatalf("unsupported offchain client type %T", oc) + } } func SetUpHomeAndFeedChains(t *testing.T, lggr logger.Logger, homeChainSel, feedChainSel uint64, chains map[uint64]deployment.Chain) (deployment.AddressBook, deployment.CapabilityRegistryConfig) { @@ -85,11 +114,24 @@ func SetUpHomeAndFeedChains(t *testing.T, lggr logger.Logger, homeChainSel, feed } } -// NewEnvironmentWithCRAndFeeds creates a new CCIP environment +func LatestBlocksByChain(ctx context.Context, chains map[uint64]deployment.Chain) (map[uint64]uint64, error) { + latestBlocks := make(map[uint64]uint64) + for _, chain := range chains { + latesthdr, err := chain.Client.HeaderByNumber(ctx, nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to get latest header for chain %d", chain.Selector) + } + block := latesthdr.Number.Uint64() + latestBlocks[chain.Selector] = block + } + return latestBlocks, nil +} + +// NewMemoryEnvironment creates a new CCIP environment // with capreg, feeds and nodes set up. -func NewEnvironmentWithCRAndFeeds(t *testing.T, lggr logger.Logger, numChains int) DeployedTestEnvironment { +func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int) DeployedEnv { require.GreaterOrEqual(t, numChains, 2, "numChains must be at least 2 for home and feed chains") - ctx := Context(t) + ctx := testcontext.Get(t) chains := memory.NewMemoryChains(t, numChains) // Lower chainSel is home chain. var chainSels []uint64 @@ -103,6 +145,8 @@ func NewEnvironmentWithCRAndFeeds(t *testing.T, lggr logger.Logger, numChains in // Take lowest for determinism. homeChainSel := chainSels[HomeChainIndex] feedSel := chainSels[FeedChainIndex] + replayBlocks, err := LatestBlocksByChain(ctx, chains) + require.NoError(t, err) ab, capReg := SetUpHomeAndFeedChains(t, lggr, homeChainSel, feedSel, chains) nodes := memory.NewNodes(t, zapcore.InfoLevel, chains, 4, 1, capReg) @@ -114,55 +158,21 @@ func NewEnvironmentWithCRAndFeeds(t *testing.T, lggr logger.Logger, numChains in } e := memory.NewMemoryEnvironmentFromChainsNodes(t, lggr, chains, nodes) - return DeployedTestEnvironment{ - DeployedEnv: DeployedEnv{ - Ab: ab, - Env: e, - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - }, - Nodes: nodes, + return DeployedEnv{ + Ab: ab, + Env: e, + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ReplayBlocks: replayBlocks, } } -func NewEnvironmentWithCRAndJobs(t *testing.T, lggr logger.Logger, numChains int) DeployedTestEnvironment { - ctx := Context(t) - e := NewEnvironmentWithCRAndFeeds(t, lggr, numChains) - jbs, err := NewCCIPJobSpecs(e.Env.NodeIDs, e.Env.Offchain) - require.NoError(t, err) - for nodeID, jobs := range jbs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Env.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - // Wait for plugins to register filters? - // TODO: Investigate how to avoid. - time.Sleep(30 * time.Second) - - // Ensure job related logs are up to date. - require.NoError(t, ReplayAllLogs(e.Nodes, e.Env.Chains)) +func NewMemoryEnvironmentWithJobs(t *testing.T, lggr logger.Logger, numChains int) DeployedEnv { + e := NewMemoryEnvironment(t, lggr, numChains) + e.SetupJobs(t) return e } -func ReplayAllLogs(nodes map[string]memory.Node, chains map[uint64]deployment.Chain) error { - blockBySel := make(map[uint64]uint64) - for sel := range chains { - blockBySel[sel] = 1 - } - for _, node := range nodes { - if err := node.ReplayLogs(blockBySel); err != nil { - return err - } - } - return nil -} - func SendRequest(t *testing.T, e deployment.Environment, state CCIPOnChainState, src, dest uint64, testRouter bool) uint64 { msg := router.ClientEVM2AnyMessage{ Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), @@ -228,8 +238,8 @@ func (d DeployedLocalDevEnvironment) RestartChainlinkNodes(t *testing.T) error { return errGrp.Wait() } -func NewDeployedLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedLocalDevEnvironment { - ctx := Context(t) +func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedEnv { + ctx := testcontext.Get(t) // create a local docker environment with simulated chains and job-distributor // we cannot create the chainlink nodes yet as we need to deploy the capability registry first envConfig, testEnv, cfg := devenv.CreateDockerEnv(t) @@ -243,6 +253,8 @@ func NewDeployedLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedLo require.NotEmpty(t, homeChainSel, "homeChainSel should not be empty") feedSel := envConfig.FeedChainSelector require.NotEmpty(t, feedSel, "feedSel should not be empty") + replayBlocks, err := LatestBlocksByChain(ctx, chains) + require.NoError(t, err) ab, capReg := SetUpHomeAndFeedChains(t, lggr, homeChainSel, feedSel, chains) // start the chainlink nodes with the CR address @@ -252,20 +264,16 @@ func NewDeployedLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedLo e, don, err := devenv.NewEnvironment(ctx, lggr, *envConfig) require.NoError(t, err) require.NotNil(t, e) - require.NotNil(t, don) zeroLogLggr := logging.GetTestLogger(t) // fund the nodes devenv.FundNodes(t, zeroLogLggr, testEnv, cfg, don.PluginNodes()) - return DeployedLocalDevEnvironment{ - DeployedEnv: DeployedEnv{ - Ab: ab, - Env: *e, - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - }, - DON: don, - testEnv: testEnv, + return DeployedEnv{ + Ab: ab, + Env: *e, + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ReplayBlocks: replayBlocks, } } diff --git a/integration-tests/deployment/devenv/build_env.go b/integration-tests/deployment/devenv/build_env.go index a36a6f51a69..3e5af0866fa 100644 --- a/integration-tests/deployment/devenv/build_env.go +++ b/integration-tests/deployment/devenv/build_env.go @@ -11,10 +11,14 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" "github.com/rs/zerolog" chainselectors "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" "github.com/subosito/gotenv" + "golang.org/x/sync/errgroup" + + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" "github.com/smartcontractkit/chainlink-testing-framework/seth" @@ -207,7 +211,10 @@ func StartChainlinkNodes( InternalIP: n.API.InternalIP(), } } - envConfig.nodeInfo = nodeInfo + if envConfig == nil { + envConfig = &EnvironmentConfig{} + } + envConfig.JDConfig.nodeInfo = nodeInfo return nil } @@ -345,3 +352,26 @@ func CreateChainConfigFromNetworks( } return chains } + +// RestartChainlinkNodes restarts the chainlink nodes in the test environment +func RestartChainlinkNodes(t *testing.T, env *test_env.CLClusterTestEnv) error { + errGrp := errgroup.Group{} + if env == nil || env.ClCluster == nil { + return errors.Wrap(errors.New("no testenv or clcluster found "), "error restarting node") + } + for _, n := range env.ClCluster.Nodes { + n := n + errGrp.Go(func() error { + if err := n.Container.Terminate(testcontext.Get(t)); err != nil { + return err + } + err := n.RestartContainer() + if err != nil { + return err + } + return nil + }) + + } + return errGrp.Wait() +} diff --git a/integration-tests/deployment/devenv/don.go b/integration-tests/deployment/devenv/don.go index 9478b729f29..0b10bd091a8 100644 --- a/integration-tests/deployment/devenv/don.go +++ b/integration-tests/deployment/devenv/don.go @@ -5,10 +5,12 @@ import ( "fmt" "strconv" "strings" + "time" "github.com/AlekSi/pointer" "github.com/hashicorp/go-multierror" "github.com/rs/zerolog" + "github.com/sethvargo/go-retry" chainsel "github.com/smartcontractkit/chain-selectors" clclient "github.com/smartcontractkit/chainlink/integration-tests/client" @@ -160,7 +162,7 @@ type Node struct { // It expects bootstrap nodes to have label with key "type" and value as "bootstrap". // It fetches the account address, peer id, and OCR2 key bundle id and creates the JobDistributorChainConfig. func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []ChainConfig, jd JobDistributor) error { - for _, chain := range chains { + for i, chain := range chains { chainId := strconv.FormatUint(chain.ChainID, 10) accountAddr, err := n.gqlClient.FetchAccountAddress(ctx, chainId) if err != nil { @@ -198,10 +200,11 @@ func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []ChainC } // JD silently fails to update nodeChainConfig. Therefore, we fetch the node config and // if it's not updated , we retry creating the chain config. - // as a workaround, we keep trying creating the chain config for 3 times until it's created + // as a workaround, we keep trying creating the chain config for 5 times until it's created retryCount := 1 - for retryCount < 3 { - err = n.gqlClient.CreateJobDistributorChainConfig(ctx, client.JobDistributorChainConfigInput{ + created := false + for retryCount < 5 { + chainConfigId, err := n.gqlClient.CreateJobDistributorChainConfig(ctx, client.JobDistributorChainConfigInput{ JobDistributorID: n.JDId, ChainID: chainId, ChainType: chain.ChainType, @@ -217,30 +220,66 @@ func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []ChainC if err != nil { return fmt.Errorf("failed to create CCIPOCR2SupportedChains for node %s: %w", n.Name, err) } - - nodeChainConfigs, err := jd.ListNodeChainConfigs(context.Background(), &nodev1.ListNodeChainConfigsRequest{ - Filter: &nodev1.ListNodeChainConfigsRequest_Filter{ - NodeIds: []string{n.NodeId}, - }}) - if err != nil { - return fmt.Errorf("failed to list node chain configs for node %s: %w", n.Name, err) - } - if nodeChainConfigs != nil && len(nodeChainConfigs.ChainConfigs) > 0 { + // JD doesn't update the node chain config immediately, so we need to wait for it to be updated + err = retry.Do(ctx, retry.WithMaxRetries(3, retry.NewFibonacci(1*time.Second)), func(ctx context.Context) error { + nodeChainConfigs, err := jd.ListNodeChainConfigs(context.Background(), &nodev1.ListNodeChainConfigsRequest{ + Filter: &nodev1.ListNodeChainConfigsRequest_Filter{ + NodeIds: []string{n.NodeId}, + }}) + if err != nil { + return fmt.Errorf("failed to list node chain configs for node %s: %w", n.Name, err) + } + if nodeChainConfigs != nil && len(nodeChainConfigs.ChainConfigs) == i+1 { + return nil + } + return fmt.Errorf("node chain config not updated properly") + }) + if err == nil { + created = true break } + // delete the node chain config if it's not updated properly and retry + err = n.gqlClient.DeleteJobDistributorChainConfig(ctx, chainConfigId) + if err != nil { + return fmt.Errorf("failed to delete job distributor chain config for node %s: %w", n.Name, err) + } + retryCount++ } + if !created { + return fmt.Errorf("failed to create CCIPOCR2SupportedChains for node %s", n.Name) + } } return nil } -func (n *Node) AcceptJob(ctx context.Context, id string) error { - spec, err := n.gqlClient.ApproveJobProposalSpec(ctx, id, false) +// AcceptJob accepts the job proposal for the given job proposal spec +func (n *Node) AcceptJob(ctx context.Context, spec string) error { + // fetch JD to get the job proposals + jd, err := n.gqlClient.GetJobDistributor(ctx, n.JDId) + if err != nil { + return err + } + if jd.GetJobProposals() == nil { + return fmt.Errorf("no job proposals found for node %s", n.Name) + } + // locate the job proposal id for the given job spec + var idToAccept string + for _, jp := range jd.JobProposals { + if jp.LatestSpec.Definition == spec { + idToAccept = jp.Id + break + } + } + if idToAccept == "" { + return fmt.Errorf("no job proposal found for job spec %s", spec) + } + approvedSpec, err := n.gqlClient.ApproveJobProposalSpec(ctx, idToAccept, false) if err != nil { return err } - if spec == nil { - return fmt.Errorf("no job proposal spec found for job id %s", id) + if approvedSpec == nil { + return fmt.Errorf("no job proposal spec found for job id %s", idToAccept) } return nil } diff --git a/integration-tests/deployment/devenv/environment.go b/integration-tests/deployment/devenv/environment.go index 74cde258cc3..e06b69769f8 100644 --- a/integration-tests/deployment/devenv/environment.go +++ b/integration-tests/deployment/devenv/environment.go @@ -17,7 +17,6 @@ type EnvironmentConfig struct { Chains []ChainConfig HomeChainSelector uint64 FeedChainSelector uint64 - nodeInfo []NodeInfo JDConfig JDConfig } @@ -26,25 +25,24 @@ func NewEnvironment(ctx context.Context, lggr logger.Logger, config EnvironmentC if err != nil { return nil, nil, fmt.Errorf("failed to create chains: %w", err) } - offChain, err := NewJDClient(config.JDConfig) + offChain, err := NewJDClient(ctx, config.JDConfig) if err != nil { return nil, nil, fmt.Errorf("failed to create JD client: %w", err) } - jd, ok := offChain.(JobDistributor) + jd, ok := offChain.(*JobDistributor) if !ok { return nil, nil, fmt.Errorf("offchain client does not implement JobDistributor") } - don, err := NewRegisteredDON(ctx, config.nodeInfo, jd) - if err != nil { - return nil, nil, fmt.Errorf("failed to create registered DON: %w", err) + if jd == nil || jd.don == nil { + return nil, nil, fmt.Errorf("offchain client does not have a DON") } - nodeIDs := don.NodeIds() - err = don.CreateSupportedChains(ctx, config.Chains, jd) + err = jd.don.CreateSupportedChains(ctx, config.Chains, *jd) if err != nil { return nil, nil, err } + nodeIDs := jd.don.NodeIds() return &deployment.Environment{ Name: DevEnv, @@ -52,5 +50,5 @@ func NewEnvironment(ctx context.Context, lggr logger.Logger, config EnvironmentC NodeIDs: nodeIDs, Chains: chains, Logger: lggr, - }, don, nil + }, jd.don, nil } diff --git a/integration-tests/deployment/devenv/jd.go b/integration-tests/deployment/devenv/jd.go index 671e6e4cea3..7a8183f04aa 100644 --- a/integration-tests/deployment/devenv/jd.go +++ b/integration-tests/deployment/devenv/jd.go @@ -15,9 +15,10 @@ import ( ) type JDConfig struct { - GRPC string - WSRPC string - creds credentials.TransportCredentials + GRPC string + WSRPC string + creds credentials.TransportCredentials + nodeInfo []NodeInfo } func NewJDConnection(cfg JDConfig) (*grpc.ClientConn, error) { @@ -43,19 +44,25 @@ type JobDistributor struct { nodev1.NodeServiceClient jobv1.JobServiceClient csav1.CSAServiceClient + don *DON } -func NewJDClient(cfg JDConfig) (deployment.OffchainClient, error) { +func NewJDClient(ctx context.Context, cfg JDConfig) (deployment.OffchainClient, error) { conn, err := NewJDConnection(cfg) if err != nil { return nil, fmt.Errorf("failed to connect Job Distributor service. Err: %w", err) } - return JobDistributor{ + jd := &JobDistributor{ WSRPC: cfg.WSRPC, NodeServiceClient: nodev1.NewNodeServiceClient(conn), JobServiceClient: jobv1.NewJobServiceClient(conn), CSAServiceClient: csav1.NewCSAServiceClient(conn), - }, err + } + jd.don, err = NewRegisteredDON(ctx, cfg.nodeInfo, *jd) + if err != nil { + return nil, fmt.Errorf("failed to create registered DON: %w", err) + } + return jd, err } func (jd JobDistributor) GetCSAPublicKey(ctx context.Context) (string, error) { @@ -69,3 +76,28 @@ func (jd JobDistributor) GetCSAPublicKey(ctx context.Context) (string, error) { csakey := keypairs.Keypairs[0].PublicKey return csakey, nil } + +func (jd JobDistributor) ReplayLogs(selectorToBlock map[uint64]uint64) error { + return jd.don.ReplayAllLogs(selectorToBlock) +} + +// ProposeJob proposes jobs through the jobService and accepts the proposed job on selected node based on ProposeJobRequest.NodeId +func (jd JobDistributor) ProposeJob(ctx context.Context, in *jobv1.ProposeJobRequest, opts ...grpc.CallOption) (*jobv1.ProposeJobResponse, error) { + res, err := jd.JobServiceClient.ProposeJob(ctx, in, opts...) + if err != nil { + return nil, fmt.Errorf("failed to propose job. err: %w", err) + } + if res.Proposal == nil { + return nil, fmt.Errorf("failed to propose job. err: proposal is nil") + } + for _, node := range jd.don.Nodes { + if node.NodeId != in.NodeId { + continue + } + // TODO : is there a way to accept the job with proposal id? + if err := node.AcceptJob(ctx, res.Proposal.Spec); err != nil { + return nil, fmt.Errorf("failed to accept job. err: %w", err) + } + } + return res, nil +} diff --git a/integration-tests/deployment/memory/environment.go b/integration-tests/deployment/memory/environment.go index 409e8d3a816..2b68d5666fa 100644 --- a/integration-tests/deployment/memory/environment.go +++ b/integration-tests/deployment/memory/environment.go @@ -112,23 +112,6 @@ func NewMemoryEnvironmentFromChainsNodes(t *testing.T, } } -//func NewMemoryEnvironmentExistingChains(t *testing.T, lggr logger.Logger, -// chains map[uint64]deployment.Chain, config MemoryEnvironmentConfig) deployment.Environment { -// nodes := NewNodes(t, chains, config.Nodes, config.Bootstraps, config.CapabilityRegistryConfig) -// var nodeIDs []string -// for id := range nodes { -// nodeIDs = append(nodeIDs, id) -// } -// return deployment.Environment{ -// Name: Memory, -// Offchain: NewMemoryJobClient(nodes), -// // Note these have the p2p_ prefix. -// NodeIDs: nodeIDs, -// Chains: chains, -// Logger: lggr, -// } -//} - // To be used by tests and any kind of deployment logic. func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, logLevel zapcore.Level, config MemoryEnvironmentConfig) deployment.Environment { chains := NewMemoryChains(t, config.Chains) diff --git a/integration-tests/deployment/memory/job_client.go b/integration-tests/deployment/memory/job_client.go index 25fec711d81..16dbfc2b828 100644 --- a/integration-tests/deployment/memory/job_client.go +++ b/integration-tests/deployment/memory/job_client.go @@ -156,6 +156,15 @@ func (j JobClient) DeleteJob(ctx context.Context, in *jobv1.DeleteJobRequest, op panic("implement me") } +func (j JobClient) ReplayLogs(selectorToBlock map[uint64]uint64) error { + for _, node := range j.Nodes { + if err := node.ReplayLogs(selectorToBlock); err != nil { + return err + } + } + return nil +} + func NewMemoryJobClient(nodesByPeerID map[string]Node) *JobClient { return &JobClient{nodesByPeerID} } diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index 02c8435c222..b4961113131 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -1,7 +1,6 @@ package smoke import ( - "strconv" "testing" "github.com/stretchr/testify/require" @@ -11,7 +10,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - ccipdeployment "github.com/smartcontractkit/chainlink/integration-tests/deployment/ccip" + ccdeploy "github.com/smartcontractkit/chainlink/integration-tests/deployment/ccip" "github.com/smartcontractkit/chainlink/integration-tests/deployment/ccip/changeset" jobv1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/job/v1" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -19,65 +18,54 @@ import ( func Test0002_InitialDeployOnLocal(t *testing.T) { lggr := logger.TestLogger(t) - ctx := ccipdeployment.Context(t) - tenv := ccipdeployment.NewDeployedLocalDevEnvironment(t, lggr) + ctx := ccdeploy.Context(t) + tenv := ccdeploy.NewLocalDevEnvironment(t, lggr) e := tenv.Env - don := tenv.DON - state, err := ccipdeployment.LoadOnchainState(tenv.Env, tenv.Ab) + state, err := ccdeploy.LoadOnchainState(tenv.Env, tenv.Ab) require.NoError(t, err) feeds := state.Chains[tenv.FeedChainSel].USDFeeds - tokenConfig := ccipdeployment.NewTokenConfig() - tokenConfig.UpsertTokenInfo(ccipdeployment.LinkSymbol, + tokenConfig := ccdeploy.NewTokenConfig() + tokenConfig.UpsertTokenInfo(ccdeploy.LinkSymbol, pluginconfig.TokenInfo{ - AggregatorAddress: feeds[ccipdeployment.LinkSymbol].Address().String(), - Decimals: ccipdeployment.LinkDecimals, + AggregatorAddress: feeds[ccdeploy.LinkSymbol].Address().String(), + Decimals: ccdeploy.LinkDecimals, DeviationPPB: cciptypes.NewBigIntFromInt64(1e9), }, ) // Apply migration - output, err := changeset.Apply0002(tenv.Env, ccipdeployment.DeployCCIPContractConfig{ + output, err := changeset.Apply0002(tenv.Env, ccdeploy.DeployCCIPContractConfig{ HomeChainSel: tenv.HomeChainSel, FeedChainSel: tenv.FeedChainSel, - TokenConfig: tokenConfig, ChainsToDeploy: tenv.Env.AllChainSelectors(), - // Capreg/config already exist. + TokenConfig: tokenConfig, + // Capreg/config and feeds already exist. CCIPOnChainState: state, }) require.NoError(t, err) // Get new state after migration. - state, err = ccipdeployment.LoadOnchainState(e, output.AddressBook) + state, err = ccdeploy.LoadOnchainState(e, output.AddressBook) require.NoError(t, err) + // Ensure capreg logs are up to date. + ccdeploy.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) + // Apply the jobs. - nodeIdToJobIds := make(map[string][]string) for nodeID, jobs := range output.JobSpecs { - nodeIdToJobIds[nodeID] = make([]string, 0, len(jobs)) for _, job := range jobs { - res, err := e.Offchain.ProposeJob(ctx, + // Note these auto-accept + _, err := e.Offchain.ProposeJob(ctx, &jobv1.ProposeJobRequest{ NodeId: nodeID, Spec: job, }) require.NoError(t, err) - require.NotNil(t, res.Proposal) - nodeIdToJobIds[nodeID] = append(nodeIdToJobIds[nodeID], res.Proposal.JobId) - } - } - - // Accept all the jobs for this node. - for _, n := range don.Nodes { - jobsToAccept, exists := nodeIdToJobIds[n.NodeId] - require.True(t, exists, "node %s has no jobs to accept", n.NodeId) - for i, jobID := range jobsToAccept { - require.NoError(t, n.AcceptJob(ctx, strconv.Itoa(i+1)), "node -%s failed to accept job %s", n.Name, jobID) } } - t.Log("Jobs accepted") // Add all lanes - require.NoError(t, ccipdeployment.AddLanesForAll(e, state)) + require.NoError(t, ccdeploy.AddLanesForAll(e, state)) // Need to keep track of the block number for each chain so that event subscription can be done from that block. startBlocks := make(map[uint64]*uint64) // Send a message from each chain to every other chain. @@ -91,13 +79,13 @@ func Test0002_InitialDeployOnLocal(t *testing.T) { require.NoError(t, err) block := latesthdr.Number.Uint64() startBlocks[dest] = &block - seqNum := ccipdeployment.SendRequest(t, e, state, src, dest, false) + seqNum := ccdeploy.SendRequest(t, e, state, src, dest, false) expectedSeqNum[dest] = seqNum } } // Wait for all commit reports to land. - ccipdeployment.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks) + ccdeploy.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks) // After commit is reported on all chains, token prices should be updated in FeeQuoter. for dest := range e.Chains { @@ -105,8 +93,11 @@ func Test0002_InitialDeployOnLocal(t *testing.T) { feeQuoter := state.Chains[dest].FeeQuoter timestampedPrice, err := feeQuoter.GetTokenPrice(nil, linkAddress) require.NoError(t, err) - require.Equal(t, ccipdeployment.MockLinkPrice, timestampedPrice.Value) + require.Equal(t, ccdeploy.MockLinkPrice, timestampedPrice.Value) } + // Wait for all exec reports to land - ccipdeployment.ConfirmExecWithSeqNrForAll(t, e, state, expectedSeqNum, startBlocks) + ccdeploy.ConfirmExecWithSeqNrForAll(t, e, state, expectedSeqNum, startBlocks) + + // TODO: Apply the proposal. } diff --git a/integration-tests/web/sdk/client/client.go b/integration-tests/web/sdk/client/client.go index 783a8a88565..e633bf04c15 100644 --- a/integration-tests/web/sdk/client/client.go +++ b/integration-tests/web/sdk/client/client.go @@ -21,12 +21,13 @@ type Client interface { FetchOCR2KeyBundleID(ctx context.Context, chainType string) (string, error) GetJob(ctx context.Context, id string) (*generated.GetJobResponse, error) ListJobs(ctx context.Context, offset, limit int) (*generated.ListJobsResponse, error) - GetJobDistributor(ctx context.Context, id string) (*generated.GetFeedsManagerResponse, error) + GetJobDistributor(ctx context.Context, id string) (generated.FeedsManagerParts, error) ListJobDistributors(ctx context.Context) (*generated.ListFeedsManagersResponse, error) CreateJobDistributor(ctx context.Context, cmd JobDistributorInput) (string, error) UpdateJobDistributor(ctx context.Context, id string, cmd JobDistributorInput) error - CreateJobDistributorChainConfig(ctx context.Context, in JobDistributorChainConfigInput) error - GetJobProposal(ctx context.Context, id string) (*generated.GetJobProposalResponse, error) + CreateJobDistributorChainConfig(ctx context.Context, in JobDistributorChainConfigInput) (string, error) + DeleteJobDistributorChainConfig(ctx context.Context, id string) error + GetJobProposal(ctx context.Context, id string) (*generated.GetJobProposalJobProposal, error) ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*JobProposalApprovalSuccessSpec, error) CancelJobProposalSpec(ctx context.Context, id string) (*generated.CancelJobProposalSpecResponse, error) RejectJobProposalSpec(ctx context.Context, id string) (*generated.RejectJobProposalSpecResponse, error) @@ -142,8 +143,18 @@ func (c *client) ListBridges(ctx context.Context, offset, limit int) (*generated return generated.ListBridges(ctx, c.gqlClient, offset, limit) } -func (c *client) GetJobDistributor(ctx context.Context, id string) (*generated.GetFeedsManagerResponse, error) { - return generated.GetFeedsManager(ctx, c.gqlClient, id) +func (c *client) GetJobDistributor(ctx context.Context, id string) (generated.FeedsManagerParts, error) { + res, err := generated.GetFeedsManager(ctx, c.gqlClient, id) + if err != nil { + return generated.FeedsManagerParts{}, err + } + if res == nil { + return generated.FeedsManagerParts{}, fmt.Errorf("no feeds manager found") + } + if success, ok := res.GetFeedsManager().(*generated.GetFeedsManagerFeedsManager); ok { + return success.FeedsManagerParts, nil + } + return generated.FeedsManagerParts{}, fmt.Errorf("failed to get feeds manager") } func (c *client) ListJobDistributors(ctx context.Context) (*generated.ListFeedsManagersResponse, error) { @@ -178,18 +189,51 @@ func (c *client) UpdateJobDistributor(ctx context.Context, id string, in JobDist return err } -func (c *client) CreateJobDistributorChainConfig(ctx context.Context, in JobDistributorChainConfigInput) error { +func (c *client) CreateJobDistributorChainConfig(ctx context.Context, in JobDistributorChainConfigInput) (string, error) { var cmd generated.CreateFeedsManagerChainConfigInput err := DecodeInput(in, &cmd) + if err != nil { + return "", err + } + res, err := generated.CreateFeedsManagerChainConfig(ctx, c.gqlClient, cmd) + if err != nil { + return "", err + } + if res == nil { + return "", fmt.Errorf("failed to create feeds manager chain config") + } + if success, ok := res.GetCreateFeedsManagerChainConfig().(*generated.CreateFeedsManagerChainConfigCreateFeedsManagerChainConfigCreateFeedsManagerChainConfigSuccess); ok { + return success.ChainConfig.Id, nil + } + return "", fmt.Errorf("failed to create feeds manager chain config") +} + +func (c *client) DeleteJobDistributorChainConfig(ctx context.Context, id string) error { + res, err := generated.DeleteFeedsManagerChainConfig(ctx, c.gqlClient, id) if err != nil { return err } - _, err = generated.CreateFeedsManagerChainConfig(ctx, c.gqlClient, cmd) - return err + if res == nil { + return fmt.Errorf("failed to delete feeds manager chain config") + } + if _, ok := res.GetDeleteFeedsManagerChainConfig().(*generated.DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess); ok { + return nil + } + return fmt.Errorf("failed to delete feeds manager chain config") } -func (c *client) GetJobProposal(ctx context.Context, id string) (*generated.GetJobProposalResponse, error) { - return generated.GetJobProposal(ctx, c.gqlClient, id) +func (c *client) GetJobProposal(ctx context.Context, id string) (*generated.GetJobProposalJobProposal, error) { + proposal, err := generated.GetJobProposal(ctx, c.gqlClient, id) + if err != nil { + return nil, err + } + if proposal == nil { + return nil, fmt.Errorf("no job proposal found") + } + if success, ok := proposal.GetJobProposal().(*generated.GetJobProposalJobProposal); ok { + return success, nil + } + return nil, fmt.Errorf("failed to get job proposal") } func (c *client) ApproveJobProposalSpec(ctx context.Context, id string, force bool) (*JobProposalApprovalSuccessSpec, error) { diff --git a/integration-tests/web/sdk/internal/generated/generated.go b/integration-tests/web/sdk/internal/generated/generated.go index 8efde4c453f..68ab3e48e4f 100644 --- a/integration-tests/web/sdk/internal/generated/generated.go +++ b/integration-tests/web/sdk/internal/generated/generated.go @@ -1194,6 +1194,11 @@ func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFeedsManag return v.FeedsManagerParts.CreatedAt } +// GetJobProposals returns CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFeedsManager.JobProposals, and is useful for accessing the field via an interface. +func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFeedsManager) GetJobProposals() []FeedsManagerPartsJobProposalsJobProposal { + return v.FeedsManagerParts.JobProposals +} + func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFeedsManager) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -1231,6 +1236,8 @@ type __premarshalCreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFe IsConnectionActive bool `json:"isConnectionActive"` CreatedAt string `json:"createdAt"` + + JobProposals []FeedsManagerPartsJobProposalsJobProposal `json:"jobProposals"` } func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFeedsManager) MarshalJSON() ([]byte, error) { @@ -1250,6 +1257,7 @@ func (v *CreateFeedsManagerCreateFeedsManagerCreateFeedsManagerSuccessFeedsManag retval.PublicKey = v.FeedsManagerParts.PublicKey retval.IsConnectionActive = v.FeedsManagerParts.IsConnectionActive retval.CreatedAt = v.FeedsManagerParts.CreatedAt + retval.JobProposals = v.FeedsManagerParts.JobProposals return &retval, nil } @@ -1425,6 +1433,200 @@ func (v *CreateFeedsManagerResponse) __premarshalJSON() (*__premarshalCreateFeed return &retval, nil } +// DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload includes the requested fields of the GraphQL interface DeleteFeedsManagerChainConfigPayload. +// +// DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload is implemented by the following types: +// DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess +// DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError +type DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload interface { + implementsGraphQLInterfaceDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() string +} + +func (v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess) implementsGraphQLInterfaceDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload() { +} +func (v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError) implementsGraphQLInterfaceDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload() { +} + +func __unmarshalDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload(b []byte, v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload) error { + if string(b) == "null" { + return nil + } + + var tn struct { + TypeName string `json:"__typename"` + } + err := json.Unmarshal(b, &tn) + if err != nil { + return err + } + + switch tn.TypeName { + case "DeleteFeedsManagerChainConfigSuccess": + *v = new(DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess) + return json.Unmarshal(b, *v) + case "NotFoundError": + *v = new(DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError) + return json.Unmarshal(b, *v) + case "": + return fmt.Errorf( + "response was missing DeleteFeedsManagerChainConfigPayload.__typename") + default: + return fmt.Errorf( + `unexpected concrete type for DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload: "%v"`, tn.TypeName) + } +} + +func __marshalDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload(v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload) ([]byte, error) { + + var typename string + switch v := (*v).(type) { + case *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess: + typename = "DeleteFeedsManagerChainConfigSuccess" + + result := struct { + TypeName string `json:"__typename"` + *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess + }{typename, v} + return json.Marshal(result) + case *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError: + typename = "NotFoundError" + + result := struct { + TypeName string `json:"__typename"` + *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError + }{typename, v} + return json.Marshal(result) + case nil: + return []byte("null"), nil + default: + return nil, fmt.Errorf( + `unexpected concrete type for DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload: "%T"`, v) + } +} + +// DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess includes the requested fields of the GraphQL type DeleteFeedsManagerChainConfigSuccess. +type DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess struct { + Typename string `json:"__typename"` + ChainConfig DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig `json:"chainConfig"` +} + +// GetTypename returns DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess.Typename, and is useful for accessing the field via an interface. +func (v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess) GetTypename() string { + return v.Typename +} + +// GetChainConfig returns DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess.ChainConfig, and is useful for accessing the field via an interface. +func (v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccess) GetChainConfig() DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig { + return v.ChainConfig +} + +// DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig includes the requested fields of the GraphQL type FeedsManagerChainConfig. +type DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig struct { + Id string `json:"id"` +} + +// GetId returns DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig.Id, and is useful for accessing the field via an interface. +func (v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigSuccessChainConfigFeedsManagerChainConfig) GetId() string { + return v.Id +} + +// DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError includes the requested fields of the GraphQL type NotFoundError. +type DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError struct { + Typename string `json:"__typename"` + Message string `json:"message"` + Code ErrorCode `json:"code"` +} + +// GetTypename returns DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError.Typename, and is useful for accessing the field via an interface. +func (v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError) GetTypename() string { + return v.Typename +} + +// GetMessage returns DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError.Message, and is useful for accessing the field via an interface. +func (v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError) GetMessage() string { + return v.Message +} + +// GetCode returns DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError.Code, and is useful for accessing the field via an interface. +func (v *DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigNotFoundError) GetCode() ErrorCode { + return v.Code +} + +// DeleteFeedsManagerChainConfigResponse is returned by DeleteFeedsManagerChainConfig on success. +type DeleteFeedsManagerChainConfigResponse struct { + DeleteFeedsManagerChainConfig DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload `json:"-"` +} + +// GetDeleteFeedsManagerChainConfig returns DeleteFeedsManagerChainConfigResponse.DeleteFeedsManagerChainConfig, and is useful for accessing the field via an interface. +func (v *DeleteFeedsManagerChainConfigResponse) GetDeleteFeedsManagerChainConfig() DeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload { + return v.DeleteFeedsManagerChainConfig +} + +func (v *DeleteFeedsManagerChainConfigResponse) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *DeleteFeedsManagerChainConfigResponse + DeleteFeedsManagerChainConfig json.RawMessage `json:"deleteFeedsManagerChainConfig"` + graphql.NoUnmarshalJSON + } + firstPass.DeleteFeedsManagerChainConfigResponse = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.DeleteFeedsManagerChainConfig + src := firstPass.DeleteFeedsManagerChainConfig + if len(src) != 0 && string(src) != "null" { + err = __unmarshalDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal DeleteFeedsManagerChainConfigResponse.DeleteFeedsManagerChainConfig: %w", err) + } + } + } + return nil +} + +type __premarshalDeleteFeedsManagerChainConfigResponse struct { + DeleteFeedsManagerChainConfig json.RawMessage `json:"deleteFeedsManagerChainConfig"` +} + +func (v *DeleteFeedsManagerChainConfigResponse) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *DeleteFeedsManagerChainConfigResponse) __premarshalJSON() (*__premarshalDeleteFeedsManagerChainConfigResponse, error) { + var retval __premarshalDeleteFeedsManagerChainConfigResponse + + { + + dst := &retval.DeleteFeedsManagerChainConfig + src := v.DeleteFeedsManagerChainConfig + var err error + *dst, err = __marshalDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigDeleteFeedsManagerChainConfigPayload( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal DeleteFeedsManagerChainConfigResponse.DeleteFeedsManagerChainConfig: %w", err) + } + } + return &retval, nil +} + type ErrorCode string const ( @@ -1435,12 +1637,13 @@ const ( // FeedsManagerParts includes the GraphQL fields of FeedsManager requested by the fragment FeedsManagerParts. type FeedsManagerParts struct { - Id string `json:"id"` - Name string `json:"name"` - Uri string `json:"uri"` - PublicKey string `json:"publicKey"` - IsConnectionActive bool `json:"isConnectionActive"` - CreatedAt string `json:"createdAt"` + Id string `json:"id"` + Name string `json:"name"` + Uri string `json:"uri"` + PublicKey string `json:"publicKey"` + IsConnectionActive bool `json:"isConnectionActive"` + CreatedAt string `json:"createdAt"` + JobProposals []FeedsManagerPartsJobProposalsJobProposal `json:"jobProposals"` } // GetId returns FeedsManagerParts.Id, and is useful for accessing the field via an interface. @@ -1461,6 +1664,137 @@ func (v *FeedsManagerParts) GetIsConnectionActive() bool { return v.IsConnection // GetCreatedAt returns FeedsManagerParts.CreatedAt, and is useful for accessing the field via an interface. func (v *FeedsManagerParts) GetCreatedAt() string { return v.CreatedAt } +// GetJobProposals returns FeedsManagerParts.JobProposals, and is useful for accessing the field via an interface. +func (v *FeedsManagerParts) GetJobProposals() []FeedsManagerPartsJobProposalsJobProposal { + return v.JobProposals +} + +// FeedsManagerPartsJobProposalsJobProposal includes the requested fields of the GraphQL type JobProposal. +type FeedsManagerPartsJobProposalsJobProposal struct { + Id string `json:"id"` + Status JobProposalStatus `json:"status"` + RemoteUUID string `json:"remoteUUID"` + ExternalJobID string `json:"externalJobID"` + JobID string `json:"jobID"` + Specs []FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec `json:"specs"` + LatestSpec FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec `json:"latestSpec"` +} + +// GetId returns FeedsManagerPartsJobProposalsJobProposal.Id, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposal) GetId() string { return v.Id } + +// GetStatus returns FeedsManagerPartsJobProposalsJobProposal.Status, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposal) GetStatus() JobProposalStatus { return v.Status } + +// GetRemoteUUID returns FeedsManagerPartsJobProposalsJobProposal.RemoteUUID, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposal) GetRemoteUUID() string { return v.RemoteUUID } + +// GetExternalJobID returns FeedsManagerPartsJobProposalsJobProposal.ExternalJobID, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposal) GetExternalJobID() string { return v.ExternalJobID } + +// GetJobID returns FeedsManagerPartsJobProposalsJobProposal.JobID, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposal) GetJobID() string { return v.JobID } + +// GetSpecs returns FeedsManagerPartsJobProposalsJobProposal.Specs, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposal) GetSpecs() []FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec { + return v.Specs +} + +// GetLatestSpec returns FeedsManagerPartsJobProposalsJobProposal.LatestSpec, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposal) GetLatestSpec() FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec { + return v.LatestSpec +} + +// FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec includes the requested fields of the GraphQL type JobProposalSpec. +type FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec struct { + Id string `json:"id"` + Definition string `json:"definition"` + Version int `json:"version"` + Status SpecStatus `json:"status"` + StatusUpdatedAt string `json:"statusUpdatedAt"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +// GetId returns FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec.Id, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec) GetId() string { + return v.Id +} + +// GetDefinition returns FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec.Definition, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec) GetDefinition() string { + return v.Definition +} + +// GetVersion returns FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec.Version, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec) GetVersion() int { + return v.Version +} + +// GetStatus returns FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec.Status, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec) GetStatus() SpecStatus { + return v.Status +} + +// GetStatusUpdatedAt returns FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec.StatusUpdatedAt, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec) GetStatusUpdatedAt() string { + return v.StatusUpdatedAt +} + +// GetCreatedAt returns FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec.CreatedAt, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec) GetCreatedAt() string { + return v.CreatedAt +} + +// GetUpdatedAt returns FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec.UpdatedAt, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalLatestSpecJobProposalSpec) GetUpdatedAt() string { + return v.UpdatedAt +} + +// FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec includes the requested fields of the GraphQL type JobProposalSpec. +type FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec struct { + Id string `json:"id"` + Definition string `json:"definition"` + Version int `json:"version"` + Status SpecStatus `json:"status"` + StatusUpdatedAt string `json:"statusUpdatedAt"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +// GetId returns FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec.Id, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec) GetId() string { return v.Id } + +// GetDefinition returns FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec.Definition, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec) GetDefinition() string { + return v.Definition +} + +// GetVersion returns FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec.Version, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec) GetVersion() int { + return v.Version +} + +// GetStatus returns FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec.Status, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec) GetStatus() SpecStatus { + return v.Status +} + +// GetStatusUpdatedAt returns FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec.StatusUpdatedAt, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec) GetStatusUpdatedAt() string { + return v.StatusUpdatedAt +} + +// GetCreatedAt returns FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec.CreatedAt, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec) GetCreatedAt() string { + return v.CreatedAt +} + +// GetUpdatedAt returns FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec.UpdatedAt, and is useful for accessing the field via an interface. +func (v *FeedsManagerPartsJobProposalsJobProposalSpecsJobProposalSpec) GetUpdatedAt() string { + return v.UpdatedAt +} + // FetchAccountsEthKeysEthKeysPayload includes the requested fields of the GraphQL type EthKeysPayload. type FetchAccountsEthKeysEthKeysPayload struct { Results []FetchAccountsEthKeysEthKeysPayloadResultsEthKey `json:"results"` @@ -1929,6 +2263,11 @@ func (v *GetFeedsManagerFeedsManager) GetIsConnectionActive() bool { // GetCreatedAt returns GetFeedsManagerFeedsManager.CreatedAt, and is useful for accessing the field via an interface. func (v *GetFeedsManagerFeedsManager) GetCreatedAt() string { return v.FeedsManagerParts.CreatedAt } +// GetJobProposals returns GetFeedsManagerFeedsManager.JobProposals, and is useful for accessing the field via an interface. +func (v *GetFeedsManagerFeedsManager) GetJobProposals() []FeedsManagerPartsJobProposalsJobProposal { + return v.FeedsManagerParts.JobProposals +} + func (v *GetFeedsManagerFeedsManager) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -1968,6 +2307,8 @@ type __premarshalGetFeedsManagerFeedsManager struct { IsConnectionActive bool `json:"isConnectionActive"` CreatedAt string `json:"createdAt"` + + JobProposals []FeedsManagerPartsJobProposalsJobProposal `json:"jobProposals"` } func (v *GetFeedsManagerFeedsManager) MarshalJSON() ([]byte, error) { @@ -1988,6 +2329,7 @@ func (v *GetFeedsManagerFeedsManager) __premarshalJSON() (*__premarshalGetFeedsM retval.PublicKey = v.FeedsManagerParts.PublicKey retval.IsConnectionActive = v.FeedsManagerParts.IsConnectionActive retval.CreatedAt = v.FeedsManagerParts.CreatedAt + retval.JobProposals = v.FeedsManagerParts.JobProposals return &retval, nil } @@ -2465,6 +2807,11 @@ func (v *GetJobProposalJobProposalFeedsManager) GetCreatedAt() string { return v.FeedsManagerParts.CreatedAt } +// GetJobProposals returns GetJobProposalJobProposalFeedsManager.JobProposals, and is useful for accessing the field via an interface. +func (v *GetJobProposalJobProposalFeedsManager) GetJobProposals() []FeedsManagerPartsJobProposalsJobProposal { + return v.FeedsManagerParts.JobProposals +} + func (v *GetJobProposalJobProposalFeedsManager) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -2502,6 +2849,8 @@ type __premarshalGetJobProposalJobProposalFeedsManager struct { IsConnectionActive bool `json:"isConnectionActive"` CreatedAt string `json:"createdAt"` + + JobProposals []FeedsManagerPartsJobProposalsJobProposal `json:"jobProposals"` } func (v *GetJobProposalJobProposalFeedsManager) MarshalJSON() ([]byte, error) { @@ -2521,6 +2870,7 @@ func (v *GetJobProposalJobProposalFeedsManager) __premarshalJSON() (*__premarsha retval.PublicKey = v.FeedsManagerParts.PublicKey retval.IsConnectionActive = v.FeedsManagerParts.IsConnectionActive retval.CreatedAt = v.FeedsManagerParts.CreatedAt + retval.JobProposals = v.FeedsManagerParts.JobProposals return &retval, nil } @@ -3650,6 +4000,11 @@ func (v *ListFeedsManagersFeedsManagersFeedsManagersPayloadResultsFeedsManager) return v.FeedsManagerParts.CreatedAt } +// GetJobProposals returns ListFeedsManagersFeedsManagersFeedsManagersPayloadResultsFeedsManager.JobProposals, and is useful for accessing the field via an interface. +func (v *ListFeedsManagersFeedsManagersFeedsManagersPayloadResultsFeedsManager) GetJobProposals() []FeedsManagerPartsJobProposalsJobProposal { + return v.FeedsManagerParts.JobProposals +} + func (v *ListFeedsManagersFeedsManagersFeedsManagersPayloadResultsFeedsManager) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -3687,6 +4042,8 @@ type __premarshalListFeedsManagersFeedsManagersFeedsManagersPayloadResultsFeedsM IsConnectionActive bool `json:"isConnectionActive"` CreatedAt string `json:"createdAt"` + + JobProposals []FeedsManagerPartsJobProposalsJobProposal `json:"jobProposals"` } func (v *ListFeedsManagersFeedsManagersFeedsManagersPayloadResultsFeedsManager) MarshalJSON() ([]byte, error) { @@ -3706,6 +4063,7 @@ func (v *ListFeedsManagersFeedsManagersFeedsManagersPayloadResultsFeedsManager) retval.PublicKey = v.FeedsManagerParts.PublicKey retval.IsConnectionActive = v.FeedsManagerParts.IsConnectionActive retval.CreatedAt = v.FeedsManagerParts.CreatedAt + retval.JobProposals = v.FeedsManagerParts.JobProposals return &retval, nil } @@ -4469,6 +4827,11 @@ func (v *UpdateFeedsManagerUpdateFeedsManagerUpdateFeedsManagerSuccessFeedsManag return v.FeedsManagerParts.CreatedAt } +// GetJobProposals returns UpdateFeedsManagerUpdateFeedsManagerUpdateFeedsManagerSuccessFeedsManager.JobProposals, and is useful for accessing the field via an interface. +func (v *UpdateFeedsManagerUpdateFeedsManagerUpdateFeedsManagerSuccessFeedsManager) GetJobProposals() []FeedsManagerPartsJobProposalsJobProposal { + return v.FeedsManagerParts.JobProposals +} + func (v *UpdateFeedsManagerUpdateFeedsManagerUpdateFeedsManagerSuccessFeedsManager) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -4506,6 +4869,8 @@ type __premarshalUpdateFeedsManagerUpdateFeedsManagerUpdateFeedsManagerSuccessFe IsConnectionActive bool `json:"isConnectionActive"` CreatedAt string `json:"createdAt"` + + JobProposals []FeedsManagerPartsJobProposalsJobProposal `json:"jobProposals"` } func (v *UpdateFeedsManagerUpdateFeedsManagerUpdateFeedsManagerSuccessFeedsManager) MarshalJSON() ([]byte, error) { @@ -4525,6 +4890,7 @@ func (v *UpdateFeedsManagerUpdateFeedsManagerUpdateFeedsManagerSuccessFeedsManag retval.PublicKey = v.FeedsManagerParts.PublicKey retval.IsConnectionActive = v.FeedsManagerParts.IsConnectionActive retval.CreatedAt = v.FeedsManagerParts.CreatedAt + retval.JobProposals = v.FeedsManagerParts.JobProposals return &retval, nil } @@ -4803,6 +5169,14 @@ type __CreateFeedsManagerInput struct { // GetInput returns __CreateFeedsManagerInput.Input, and is useful for accessing the field via an interface. func (v *__CreateFeedsManagerInput) GetInput() CreateFeedsManagerInput { return v.Input } +// __DeleteFeedsManagerChainConfigInput is used internally by genqlient +type __DeleteFeedsManagerChainConfigInput struct { + Id string `json:"id"` +} + +// GetId returns __DeleteFeedsManagerChainConfigInput.Id, and is useful for accessing the field via an interface. +func (v *__DeleteFeedsManagerChainConfigInput) GetId() string { return v.Id } + // __GetBridgeInput is used internally by genqlient type __GetBridgeInput struct { Id string `json:"id"` @@ -5033,6 +5407,31 @@ fragment FeedsManagerParts on FeedsManager { publicKey isConnectionActive createdAt + jobProposals { + id + status + remoteUUID + externalJobID + jobID + specs { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + latestSpec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } } ` @@ -5142,6 +5541,50 @@ func CreateFeedsManagerChainConfig( return &data_, err_ } +// The query or mutation executed by DeleteFeedsManagerChainConfig. +const DeleteFeedsManagerChainConfig_Operation = ` +mutation DeleteFeedsManagerChainConfig ($id: ID!) { + deleteFeedsManagerChainConfig(id: $id) { + __typename + ... on DeleteFeedsManagerChainConfigSuccess { + chainConfig { + id + } + } + ... on NotFoundError { + message + code + } + } +} +` + +func DeleteFeedsManagerChainConfig( + ctx_ context.Context, + client_ graphql.Client, + id string, +) (*DeleteFeedsManagerChainConfigResponse, error) { + req_ := &graphql.Request{ + OpName: "DeleteFeedsManagerChainConfig", + Query: DeleteFeedsManagerChainConfig_Operation, + Variables: &__DeleteFeedsManagerChainConfigInput{ + Id: id, + }, + } + var err_ error + + var data_ DeleteFeedsManagerChainConfigResponse + resp_ := &graphql.Response{Data: &data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return &data_, err_ +} + // The query or mutation executed by FetchAccounts. const FetchAccounts_Operation = ` query FetchAccounts { @@ -5357,6 +5800,31 @@ fragment FeedsManagerParts on FeedsManager { publicKey isConnectionActive createdAt + jobProposals { + id + status + remoteUUID + externalJobID + jobID + specs { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + latestSpec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } } ` @@ -5514,6 +5982,31 @@ fragment FeedsManagerParts on FeedsManager { publicKey isConnectionActive createdAt + jobProposals { + id + status + remoteUUID + externalJobID + jobID + specs { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + latestSpec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } } ` @@ -5610,6 +6103,31 @@ fragment FeedsManagerParts on FeedsManager { publicKey isConnectionActive createdAt + jobProposals { + id + status + remoteUUID + externalJobID + jobID + specs { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + latestSpec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } } ` @@ -5796,6 +6314,31 @@ fragment FeedsManagerParts on FeedsManager { publicKey isConnectionActive createdAt + jobProposals { + id + status + remoteUUID + externalJobID + jobID + specs { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + latestSpec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } } ` diff --git a/integration-tests/web/sdk/internal/genqlient.graphql b/integration-tests/web/sdk/internal/genqlient.graphql index cd1912b88cf..06baf4f7913 100644 --- a/integration-tests/web/sdk/internal/genqlient.graphql +++ b/integration-tests/web/sdk/internal/genqlient.graphql @@ -212,6 +212,31 @@ fragment FeedsManagerParts on FeedsManager { publicKey isConnectionActive createdAt + jobProposals { + id + status + remoteUUID + externalJobID + jobID + specs { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + latestSpec { + id + definition + version + status + statusUpdatedAt + createdAt + updatedAt + } + } } query GetFeedsManager($id: ID!) { @@ -328,6 +353,19 @@ mutation CreateFeedsManagerChainConfig($input: CreateFeedsManagerChainConfigInpu } } +mutation DeleteFeedsManagerChainConfig($id: ID!) { + deleteFeedsManagerChainConfig(id: $id) { + ... on DeleteFeedsManagerChainConfigSuccess { + chainConfig { + id + } + } + ... on NotFoundError { + message + code + } + } +} ##################### # Job Proposals @@ -418,4 +456,4 @@ mutation UpdateJobProposalSpecDefinition( code } } -} +} \ No newline at end of file From 3e9e058da4f2ba4b51286d4f05aa7efcba179e79 Mon Sep 17 00:00:00 2001 From: Sri Kidambi <1702865+kidambisrinivas@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:29:56 +0100 Subject: [PATCH 04/28] Log Event Trigger Capability (#14308) * Log Event Trigger Capability * Minor refactoring * Moved main script to plugins/cmd * Added initial implementation for UnregisterTrigger * Create NewContractReader in RegisterTrigger flow of the trigger capability * Refactoring to integrate with ChainReader QueryKey API * Integrate with ChainReader QueryKey interface * Minor changes * Send cursor in QueryKey in subsequent calls * Test utils for LOOP capability * Happy path test for log event trigger capability * Float64 fix in values * Happy path integration test for Log Event Trigger Capability * Fix code lint annotations * Addressed PR comments * Added changeset * Addressed Lint errors * Addressed PR comments * Addressed more lint issues * Simplified trigger ctx creation and cancel flows * Added comment * Addressed PR comments * Implemented Start/Close pattern in logEventTrigger and used stopChan to track listener * Addressed more PR comments * Handled errors from Info and Close methods * Fixed lint errors and pass ctx to Info * Handle race conditions in log event trigger service * Fixed lint errors * Minor change * Test fix and lint fixes * Move EVM specific tests out of chain-agnostic capability * Set block time * Check existence of trigger in slow path * Complete usage of services.Service with StartOnce and StopOnce with tests updated * Wait for all goroutines to exit in test * Cleanup logpoller and headtracker after test --- .changeset/chilly-crews-retire.md | 5 + .../capabilities/triggers/logevent/service.go | 157 +++++++++++++ core/capabilities/triggers/logevent/store.go | 82 +++++++ .../capabilities/triggers/logevent/trigger.go | 210 ++++++++++++++++++ .../capabilities/log_event_trigger_test.go | 89 ++++++++ .../evm/capabilities/testutils/backend.go | 120 ++++++++++ .../capabilities/testutils/chain_reader.go | 169 ++++++++++++++ core/services/relay/evm/chain_reader.go | 1 - .../capabilities/log-event-trigger/main.go | 118 ++++++++++ 9 files changed, 950 insertions(+), 1 deletion(-) create mode 100644 .changeset/chilly-crews-retire.md create mode 100644 core/capabilities/triggers/logevent/service.go create mode 100644 core/capabilities/triggers/logevent/store.go create mode 100644 core/capabilities/triggers/logevent/trigger.go create mode 100644 core/services/relay/evm/capabilities/log_event_trigger_test.go create mode 100644 core/services/relay/evm/capabilities/testutils/backend.go create mode 100644 core/services/relay/evm/capabilities/testutils/chain_reader.go create mode 100644 plugins/cmd/capabilities/log-event-trigger/main.go diff --git a/.changeset/chilly-crews-retire.md b/.changeset/chilly-crews-retire.md new file mode 100644 index 00000000000..28b531a9ddb --- /dev/null +++ b/.changeset/chilly-crews-retire.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +#added log-event-trigger LOOPP capability, using ChainReader diff --git a/core/capabilities/triggers/logevent/service.go b/core/capabilities/triggers/logevent/service.go new file mode 100644 index 00000000000..7ed4855e097 --- /dev/null +++ b/core/capabilities/triggers/logevent/service.go @@ -0,0 +1,157 @@ +package logevent + +import ( + "context" + "errors" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" +) + +const ID = "log-event-trigger-%s-%s@1.0.0" + +const defaultSendChannelBufferSize = 1000 + +// Log Event Trigger Capability Input +type Input struct { +} + +// Log Event Trigger Capabilities Manager +// Manages different log event triggers using an underlying triggerStore +type TriggerService struct { + services.StateMachine + capabilities.CapabilityInfo + capabilities.Validator[RequestConfig, Input, capabilities.TriggerResponse] + lggr logger.Logger + triggers CapabilitiesStore[logEventTrigger, capabilities.TriggerResponse] + relayer core.Relayer + logEventConfig Config + stopCh services.StopChan +} + +// Common capability level config across all workflows +type Config struct { + ChainID string `json:"chainId"` + Network string `json:"network"` + LookbackBlocks uint64 `json:"lookbakBlocks"` + PollPeriod uint32 `json:"pollPeriod"` +} + +func (config Config) Version(capabilityVersion string) string { + return fmt.Sprintf(capabilityVersion, config.Network, config.ChainID) +} + +var _ capabilities.TriggerCapability = (*TriggerService)(nil) +var _ services.Service = &TriggerService{} + +// Creates a new Cron Trigger Service. +// Scheduling will commence on calling .Start() +func NewTriggerService(ctx context.Context, + lggr logger.Logger, + relayer core.Relayer, + logEventConfig Config) (*TriggerService, error) { + l := logger.Named(lggr, "LogEventTriggerCapabilityService") + + logEventStore := NewCapabilitiesStore[logEventTrigger, capabilities.TriggerResponse]() + + s := &TriggerService{ + lggr: l, + triggers: logEventStore, + relayer: relayer, + logEventConfig: logEventConfig, + stopCh: make(services.StopChan), + } + var err error + s.CapabilityInfo, err = s.Info(ctx) + if err != nil { + return s, err + } + s.Validator = capabilities.NewValidator[RequestConfig, Input, capabilities.TriggerResponse](capabilities.ValidatorArgs{Info: s.CapabilityInfo}) + return s, nil +} + +func (s *TriggerService) Info(ctx context.Context) (capabilities.CapabilityInfo, error) { + return capabilities.NewCapabilityInfo( + s.logEventConfig.Version(ID), + capabilities.CapabilityTypeTrigger, + "A trigger that listens for specific contract log events and starts a workflow run.", + ) +} + +// Register a new trigger +// Can register triggers before the service is actively scheduling +func (s *TriggerService) RegisterTrigger(ctx context.Context, req capabilities.TriggerRegistrationRequest) (<-chan capabilities.TriggerResponse, error) { + if req.Config == nil { + return nil, errors.New("config is required to register a log event trigger") + } + reqConfig, err := s.ValidateConfig(req.Config) + if err != nil { + return nil, err + } + // Add log event trigger with Contract details to CapabilitiesStore + var respCh chan capabilities.TriggerResponse + ok := s.IfNotStopped(func() { + respCh, err = s.triggers.InsertIfNotExists(req.TriggerID, func() (*logEventTrigger, chan capabilities.TriggerResponse, error) { + l, ch, tErr := newLogEventTrigger(ctx, s.lggr, req.Metadata.WorkflowID, reqConfig, s.logEventConfig, s.relayer) + if tErr != nil { + return l, ch, tErr + } + tErr = l.Start(ctx) + return l, ch, tErr + }) + }) + if !ok { + return nil, fmt.Errorf("cannot create new trigger since LogEventTriggerService has been stopped") + } + if err != nil { + return nil, fmt.Errorf("create new trigger failed %w", err) + } + s.lggr.Infow("RegisterTrigger", "triggerId", req.TriggerID, "WorkflowID", req.Metadata.WorkflowID) + return respCh, nil +} + +func (s *TriggerService) UnregisterTrigger(ctx context.Context, req capabilities.TriggerRegistrationRequest) error { + trigger, ok := s.triggers.Read(req.TriggerID) + if !ok { + return fmt.Errorf("triggerId %s not found", req.TriggerID) + } + // Close callback channel and stop log event trigger listener + err := trigger.Close() + if err != nil { + return fmt.Errorf("error closing trigger %s (chainID %s): %w", req.TriggerID, s.logEventConfig.ChainID, err) + } + // Remove from triggers context + s.triggers.Delete(req.TriggerID) + s.lggr.Infow("UnregisterTrigger", "triggerId", req.TriggerID, "WorkflowID", req.Metadata.WorkflowID) + return nil +} + +// Start the service. +func (s *TriggerService) Start(ctx context.Context) error { + return s.StartOnce("LogEventTriggerCapabilityService", func() error { + s.lggr.Info("Starting LogEventTriggerCapabilityService") + return nil + }) +} + +// Close stops the Service. +// After this call the Service cannot be started again, +// The service will need to be re-built to start scheduling again. +func (s *TriggerService) Close() error { + return s.StopOnce("LogEventTriggerCapabilityService", func() error { + s.lggr.Infow("Stopping LogEventTriggerCapabilityService") + triggers := s.triggers.ReadAll() + return services.MultiCloser(triggers).Close() + }) +} + +func (s *TriggerService) HealthReport() map[string]error { + return map[string]error{s.Name(): s.Healthy()} +} + +func (s *TriggerService) Name() string { + return s.lggr.Name() +} diff --git a/core/capabilities/triggers/logevent/store.go b/core/capabilities/triggers/logevent/store.go new file mode 100644 index 00000000000..ac9d3741cd1 --- /dev/null +++ b/core/capabilities/triggers/logevent/store.go @@ -0,0 +1,82 @@ +package logevent + +import ( + "fmt" + "sync" +) + +type RegisterCapabilityFn[T any, Resp any] func() (*T, chan Resp, error) + +// Interface of the capabilities store +type CapabilitiesStore[T any, Resp any] interface { + Read(capabilityID string) (value *T, ok bool) + ReadAll() (values []*T) + Write(capabilityID string, value *T) + InsertIfNotExists(capabilityID string, fn RegisterCapabilityFn[T, Resp]) (chan Resp, error) + Delete(capabilityID string) +} + +// Implementation for the CapabilitiesStore interface +type capabilitiesStore[T any, Resp any] struct { + mu sync.RWMutex + capabilities map[string]*T +} + +var _ CapabilitiesStore[string, string] = (CapabilitiesStore[string, string])(nil) + +// Constructor for capabilitiesStore struct implementing CapabilitiesStore interface +func NewCapabilitiesStore[T any, Resp any]() CapabilitiesStore[T, Resp] { + return &capabilitiesStore[T, Resp]{ + capabilities: map[string]*T{}, + } +} + +func (cs *capabilitiesStore[T, Resp]) Read(capabilityID string) (value *T, ok bool) { + cs.mu.RLock() + defer cs.mu.RUnlock() + trigger, ok := cs.capabilities[capabilityID] + return trigger, ok +} + +func (cs *capabilitiesStore[T, Resp]) ReadAll() (values []*T) { + cs.mu.RLock() + defer cs.mu.RUnlock() + vals := make([]*T, 0) + for _, v := range cs.capabilities { + vals = append(vals, v) + } + return vals +} + +func (cs *capabilitiesStore[T, Resp]) Write(capabilityID string, value *T) { + cs.mu.Lock() + defer cs.mu.Unlock() + cs.capabilities[capabilityID] = value +} + +func (cs *capabilitiesStore[T, Resp]) InsertIfNotExists(capabilityID string, fn RegisterCapabilityFn[T, Resp]) (chan Resp, error) { + cs.mu.RLock() + _, ok := cs.capabilities[capabilityID] + cs.mu.RUnlock() + if ok { + return nil, fmt.Errorf("capabilityID %v already exists", capabilityID) + } + cs.mu.Lock() + defer cs.mu.Unlock() + _, ok = cs.capabilities[capabilityID] + if ok { + return nil, fmt.Errorf("capabilityID %v already exists", capabilityID) + } + value, respCh, err := fn() + if err != nil { + return nil, fmt.Errorf("error registering capability: %v", err) + } + cs.capabilities[capabilityID] = value + return respCh, nil +} + +func (cs *capabilitiesStore[T, Resp]) Delete(capabilityID string) { + cs.mu.Lock() + defer cs.mu.Unlock() + delete(cs.capabilities, capabilityID) +} diff --git a/core/capabilities/triggers/logevent/trigger.go b/core/capabilities/triggers/logevent/trigger.go new file mode 100644 index 00000000000..9a0e1d036c7 --- /dev/null +++ b/core/capabilities/triggers/logevent/trigger.go @@ -0,0 +1,210 @@ +package logevent + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" + "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + "github.com/smartcontractkit/chainlink-common/pkg/values" +) + +// Log Event Trigger Capability Request Config Details +type RequestConfig struct { + ContractName string `json:"contractName"` + ContractAddress string `json:"contractAddress"` + ContractEventName string `json:"contractEventName"` + // Log Event Trigger capability takes in a []byte as ContractReaderConfig + // to not depend on evm ChainReaderConfig type and be chain agnostic + ContractReaderConfig map[string]any `json:"contractReaderConfig"` +} + +// LogEventTrigger struct to listen for Contract events using ContractReader gRPC client +// in a loop with a periodic delay of pollPeriod milliseconds, which is specified in +// the job spec +type logEventTrigger struct { + ch chan<- capabilities.TriggerResponse + lggr logger.Logger + + // Contract address and Event Signature to monitor for + reqConfig *RequestConfig + contractReader types.ContractReader + relayer core.Relayer + startBlockNum uint64 + + // Log Event Trigger config with pollPeriod and lookbackBlocks + logEventConfig Config + ticker *time.Ticker + stopChan services.StopChan + done chan bool +} + +// Construct for logEventTrigger struct +func newLogEventTrigger(ctx context.Context, + lggr logger.Logger, + workflowID string, + reqConfig *RequestConfig, + logEventConfig Config, + relayer core.Relayer) (*logEventTrigger, chan capabilities.TriggerResponse, error) { + jsonBytes, err := json.Marshal(reqConfig.ContractReaderConfig) + if err != nil { + return nil, nil, err + } + + // Create a New Contract Reader client, which brings a corresponding ContractReader gRPC service + // in Chainlink Core service + contractReader, err := relayer.NewContractReader(ctx, jsonBytes) + if err != nil { + return nil, nil, + fmt.Errorf("error fetching contractReader for chainID %s from relayerSet: %w", logEventConfig.ChainID, err) + } + + // Bind Contract in ContractReader + boundContracts := []types.BoundContract{{Name: reqConfig.ContractName, Address: reqConfig.ContractAddress}} + err = contractReader.Bind(ctx, boundContracts) + if err != nil { + return nil, nil, err + } + + // Get current block HEAD/tip of the blockchain to start polling from + latestHead, err := relayer.LatestHead(ctx) + if err != nil { + return nil, nil, fmt.Errorf("error getting latestHead from relayer client: %w", err) + } + height, err := strconv.ParseUint(latestHead.Height, 10, 64) + if err != nil { + return nil, nil, fmt.Errorf("invalid height in latestHead from relayer client: %w", err) + } + startBlockNum := uint64(0) + if height > logEventConfig.LookbackBlocks { + startBlockNum = height - logEventConfig.LookbackBlocks + } + + // Setup callback channel, logger and ticker to poll ContractReader + callbackCh := make(chan capabilities.TriggerResponse, defaultSendChannelBufferSize) + ticker := time.NewTicker(time.Duration(logEventConfig.PollPeriod) * time.Millisecond) + + // Initialise a Log Event Trigger + l := &logEventTrigger{ + ch: callbackCh, + lggr: logger.Named(lggr, fmt.Sprintf("LogEventTrigger.%s", workflowID)), + + reqConfig: reqConfig, + contractReader: contractReader, + relayer: relayer, + startBlockNum: startBlockNum, + + logEventConfig: logEventConfig, + ticker: ticker, + stopChan: make(services.StopChan), + done: make(chan bool), + } + return l, callbackCh, nil +} + +func (l *logEventTrigger) Start(ctx context.Context) error { + go l.listen() + return nil +} + +// Start to contract events and trigger workflow runs +func (l *logEventTrigger) listen() { + ctx, cancel := l.stopChan.NewCtx() + defer cancel() + defer close(l.done) + + // Listen for events from lookbackPeriod + var logs []types.Sequence + var err error + logData := make(map[string]any) + cursor := "" + limitAndSort := query.LimitAndSort{ + SortBy: []query.SortBy{query.NewSortByTimestamp(query.Asc)}, + } + for { + select { + case <-ctx.Done(): + l.lggr.Infow("Closing trigger server for (waiting for waitGroup)", "ChainID", l.logEventConfig.ChainID, + "ContractName", l.reqConfig.ContractName, + "ContractAddress", l.reqConfig.ContractAddress, + "ContractEventName", l.reqConfig.ContractEventName) + return + case t := <-l.ticker.C: + l.lggr.Infow("Polling event logs from ContractReader using QueryKey at", "time", t, + "startBlockNum", l.startBlockNum, + "cursor", cursor) + if cursor != "" { + limitAndSort.Limit = query.Limit{Cursor: cursor} + } + logs, err = l.contractReader.QueryKey( + ctx, + types.BoundContract{Name: l.reqConfig.ContractName, Address: l.reqConfig.ContractAddress}, + query.KeyFilter{ + Key: l.reqConfig.ContractEventName, + Expressions: []query.Expression{ + query.Confidence(primitives.Finalized), + query.Block(fmt.Sprintf("%d", l.startBlockNum), primitives.Gte), + }, + }, + limitAndSort, + &logData, + ) + if err != nil { + l.lggr.Errorw("QueryKey failure", "err", err) + continue + } + // ChainReader QueryKey API provides logs including the cursor value and not + // after the cursor value. If the response only consists of the log corresponding + // to the cursor and no log after it, then we understand that there are no new + // logs + if len(logs) == 1 && logs[0].Cursor == cursor { + l.lggr.Infow("No new logs since", "cursor", cursor) + continue + } + for _, log := range logs { + if log.Cursor == cursor { + continue + } + triggerResp := createTriggerResponse(log, l.logEventConfig.Version(ID)) + l.ch <- triggerResp + cursor = log.Cursor + } + } + } +} + +// Create log event trigger capability response +func createTriggerResponse(log types.Sequence, version string) capabilities.TriggerResponse { + wrappedPayload, err := values.WrapMap(log) + if err != nil { + return capabilities.TriggerResponse{ + Err: fmt.Errorf("error wrapping trigger event: %s", err), + } + } + return capabilities.TriggerResponse{ + Event: capabilities.TriggerEvent{ + TriggerType: version, + ID: log.Cursor, + Outputs: wrappedPayload, + }, + } +} + +// Close contract event listener for the current contract +// This function is called when UnregisterTrigger is called individually +// for a specific ContractAddress and EventName +// When the whole capability service is stopped, stopChan of the service +// is closed, which would stop all triggers +func (l *logEventTrigger) Close() error { + close(l.stopChan) + <-l.done + return nil +} diff --git a/core/services/relay/evm/capabilities/log_event_trigger_test.go b/core/services/relay/evm/capabilities/log_event_trigger_test.go new file mode 100644 index 00000000000..f2104529b7f --- /dev/null +++ b/core/services/relay/evm/capabilities/log_event_trigger_test.go @@ -0,0 +1,89 @@ +package logevent_test + +import ( + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + commonmocks "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent" + coretestutils "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/capabilities/testutils" +) + +// Test for Log Event Trigger Capability happy path for EVM +func TestLogEventTriggerEVMHappyPath(t *testing.T) { + th := testutils.NewContractReaderTH(t) + + logEventConfig := logevent.Config{ + ChainID: th.BackendTH.ChainID.String(), + Network: "evm", + LookbackBlocks: 1000, + PollPeriod: 1000, + } + + // Create a new contract reader to return from mock relayer + ctx := coretestutils.Context(t) + + // Fetch latest head from simulated backend to return from mock relayer + height, err := th.BackendTH.EVMClient.LatestBlockHeight(ctx) + require.NoError(t, err) + block, err := th.BackendTH.EVMClient.BlockByNumber(ctx, height) + require.NoError(t, err) + + // Mock relayer to return a New ContractReader instead of gRPC client of a ContractReader + relayer := commonmocks.NewRelayer(t) + relayer.On("NewContractReader", mock.Anything, th.LogEmitterContractReaderCfg).Return(th.LogEmitterContractReader, nil).Once() + relayer.On("LatestHead", mock.Anything).Return(commontypes.Head{ + Height: height.String(), + Hash: block.Hash().Bytes(), + Timestamp: block.Time(), + }, nil).Once() + + // Create Log Event Trigger Service and register trigger + logEventTriggerService, err := logevent.NewTriggerService(ctx, + th.BackendTH.Lggr, + relayer, + logEventConfig) + require.NoError(t, err) + + // Start the service + servicetest.Run(t, logEventTriggerService) + + log1Ch, err := logEventTriggerService.RegisterTrigger(ctx, th.LogEmitterRegRequest) + require.NoError(t, err) + + expectedLogVal := int64(10) + + // Send a blockchain transaction that emits logs + done := make(chan struct{}) + t.Cleanup(func() { <-done }) + go func() { + defer close(done) + _, err = + th.LogEmitterContract.EmitLog1(th.BackendTH.ContractsOwner, []*big.Int{big.NewInt(expectedLogVal)}) + assert.NoError(t, err) + th.BackendTH.Backend.Commit() + th.BackendTH.Backend.Commit() + th.BackendTH.Backend.Commit() + }() + + // Wait for logs with a timeout + _, output, err := testutils.WaitForLog(th.BackendTH.Lggr, log1Ch, 15*time.Second) + require.NoError(t, err) + th.BackendTH.Lggr.Infow("EmitLog", "output", output) + // Verify if valid cursor is returned + cursor, err := testutils.GetStrVal(output, "Cursor") + require.NoError(t, err) + require.True(t, len(cursor) > 60) + // Verify if Arg0 is correct + actualLogVal, err := testutils.GetBigIntValL2(output, "Data", "Arg0") + require.NoError(t, err) + require.Equal(t, expectedLogVal, actualLogVal.Int64()) +} diff --git a/core/services/relay/evm/capabilities/testutils/backend.go b/core/services/relay/evm/capabilities/testutils/backend.go new file mode 100644 index 00000000000..ef5761b3e4c --- /dev/null +++ b/core/services/relay/evm/capabilities/testutils/backend.go @@ -0,0 +1,120 @@ +package testutils + +import ( + "context" + "encoding/json" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" + "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" + evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +// Test harness with EVM backend and chainlink core services like +// Log Poller and Head Tracker +type EVMBackendTH struct { + // Backend details + Lggr logger.Logger + ChainID *big.Int + Backend *backends.SimulatedBackend + EVMClient evmclient.Client + + ContractsOwner *bind.TransactOpts + ContractsOwnerKey ethkey.KeyV2 + + HeadTracker logpoller.HeadTracker + LogPoller logpoller.LogPoller +} + +// Test harness to create a simulated backend for testing a LOOPCapability +func NewEVMBackendTH(t *testing.T) *EVMBackendTH { + lggr := logger.TestLogger(t) + + ownerKey := cltest.MustGenerateRandomKey(t) + contractsOwner, err := bind.NewKeyedTransactorWithChainID(ownerKey.ToEcdsaPrivKey(), testutils.SimulatedChainID) + require.NoError(t, err) + + // Setup simulated go-ethereum EVM backend + genesisData := core.GenesisAlloc{ + contractsOwner.From: {Balance: assets.Ether(100000).ToInt()}, + } + chainID := testutils.SimulatedChainID + gasLimit := uint32(ethconfig.Defaults.Miner.GasCeil) //nolint:gosec + backend := cltest.NewSimulatedBackend(t, genesisData, gasLimit) + blockTime := time.UnixMilli(int64(backend.Blockchain().CurrentHeader().Time)) //nolint:gosec + err = backend.AdjustTime(time.Since(blockTime) - 24*time.Hour) + require.NoError(t, err) + backend.Commit() + + // Setup backend client + client := evmclient.NewSimulatedBackendClient(t, backend, chainID) + + th := &EVMBackendTH{ + Lggr: lggr, + ChainID: chainID, + Backend: backend, + EVMClient: client, + + ContractsOwner: contractsOwner, + ContractsOwnerKey: ownerKey, + } + th.HeadTracker, th.LogPoller = th.SetupCoreServices(t) + + return th +} + +// Setup core services like log poller and head tracker for the simulated backend +func (th *EVMBackendTH) SetupCoreServices(t *testing.T) (logpoller.HeadTracker, logpoller.LogPoller) { + db := pgtest.NewSqlxDB(t) + const finalityDepth = 2 + ht := headtracker.NewSimulatedHeadTracker(th.EVMClient, false, finalityDepth) + lp := logpoller.NewLogPoller( + logpoller.NewORM(testutils.SimulatedChainID, db, th.Lggr), + th.EVMClient, + th.Lggr, + ht, + logpoller.Opts{ + PollPeriod: 100 * time.Millisecond, + FinalityDepth: finalityDepth, + BackfillBatchSize: 3, + RpcBatchSize: 2, + KeepFinalizedBlocksDepth: 1000, + }, + ) + require.NoError(t, ht.Start(testutils.Context(t))) + require.NoError(t, lp.Start(testutils.Context(t))) + t.Cleanup(func() { ht.Close() }) + t.Cleanup(func() { lp.Close() }) + return ht, lp +} + +func (th *EVMBackendTH) NewContractReader(ctx context.Context, t *testing.T, cfg []byte) (types.ContractReader, error) { + crCfg := &evmrelaytypes.ChainReaderConfig{} + if err := json.Unmarshal(cfg, crCfg); err != nil { + return nil, err + } + + svc, err := evm.NewChainReaderService(ctx, th.Lggr, th.LogPoller, th.HeadTracker, th.EVMClient, *crCfg) + if err != nil { + return nil, err + } + + return svc, svc.Start(ctx) +} diff --git a/core/services/relay/evm/capabilities/testutils/chain_reader.go b/core/services/relay/evm/capabilities/testutils/chain_reader.go new file mode 100644 index 00000000000..3f0bf82da81 --- /dev/null +++ b/core/services/relay/evm/capabilities/testutils/chain_reader.go @@ -0,0 +1,169 @@ +package testutils + +import ( + "encoding/json" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + commoncaps "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + commonvalues "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" + coretestutils "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +// Test harness with EVM backend and chainlink core services like +// Log Poller and Head Tracker +type ContractReaderTH struct { + BackendTH *EVMBackendTH + + LogEmitterAddress *common.Address + LogEmitterContract *log_emitter.LogEmitter + LogEmitterContractReader commontypes.ContractReader + LogEmitterRegRequest commoncaps.TriggerRegistrationRequest + LogEmitterContractReaderCfg []byte +} + +// Creates a new test harness for Contract Reader tests +func NewContractReaderTH(t *testing.T) *ContractReaderTH { + backendTH := NewEVMBackendTH(t) + + // Deploy a test contract LogEmitter for testing ContractReader + logEmitterAddress, _, _, err := + log_emitter.DeployLogEmitter(backendTH.ContractsOwner, backendTH.Backend) + require.NoError(t, err) + logEmitter, err := log_emitter.NewLogEmitter(logEmitterAddress, backendTH.Backend) + require.NoError(t, err) + + // Create new contract reader + reqConfig := logevent.RequestConfig{ + ContractName: "LogEmitter", + ContractAddress: logEmitterAddress.Hex(), + ContractEventName: "Log1", + } + contractReaderCfg := evmtypes.ChainReaderConfig{ + Contracts: map[string]evmtypes.ChainContractReader{ + reqConfig.ContractName: { + ContractPollingFilter: evmtypes.ContractPollingFilter{ + GenericEventNames: []string{reqConfig.ContractEventName}, + }, + ContractABI: log_emitter.LogEmitterABI, + Configs: map[string]*evmtypes.ChainReaderDefinition{ + reqConfig.ContractEventName: { + ChainSpecificName: reqConfig.ContractEventName, + ReadType: evmtypes.Event, + }, + }, + }, + }, + } + + // Encode contractReaderConfig as JSON and decode it into a map[string]any for + // the capability request config. Log Event Trigger capability takes in a + // []byte as ContractReaderConfig to not depend on evm ChainReaderConfig type + // and be chain agnostic + contractReaderCfgBytes, err := json.Marshal(contractReaderCfg) + require.NoError(t, err) + contractReaderCfgMap := make(map[string]any) + err = json.Unmarshal(contractReaderCfgBytes, &contractReaderCfgMap) + require.NoError(t, err) + // Encode the config map as JSON to specify in the expected call in mocked object + // The LogEventTrigger Capability receives a config map, encodes it and + // calls NewContractReader with it + contractReaderCfgBytes, err = json.Marshal(contractReaderCfgMap) + require.NoError(t, err) + + reqConfig.ContractReaderConfig = contractReaderCfgMap + + config, err := commonvalues.WrapMap(reqConfig) + require.NoError(t, err) + req := commoncaps.TriggerRegistrationRequest{ + TriggerID: "logeventtrigger_log1", + Config: config, + Metadata: commoncaps.RequestMetadata{ + ReferenceID: "logeventtrigger", + }, + } + + // Create a new contract reader to return from mock relayer + ctx := coretestutils.Context(t) + contractReader, err := backendTH.NewContractReader(ctx, t, contractReaderCfgBytes) + require.NoError(t, err) + + return &ContractReaderTH{ + BackendTH: backendTH, + + LogEmitterAddress: &logEmitterAddress, + LogEmitterContract: logEmitter, + LogEmitterContractReader: contractReader, + LogEmitterRegRequest: req, + LogEmitterContractReaderCfg: contractReaderCfgBytes, + } +} + +// Wait for a specific log to be emitted to a response channel by ChainReader +func WaitForLog(lggr logger.Logger, logCh <-chan commoncaps.TriggerResponse, timeout time.Duration) ( + *commoncaps.TriggerResponse, map[string]any, error) { + select { + case <-time.After(timeout): + return nil, nil, fmt.Errorf("timeout waiting for Log1 event from ContractReader") + case log := <-logCh: + lggr.Infow("Received log from ContractReader", "event", log.Event.ID) + if log.Err != nil { + return nil, nil, fmt.Errorf("error listening for Log1 event from ContractReader: %v", log.Err) + } + v := make(map[string]any) + err := log.Event.Outputs.UnwrapTo(&v) + if err != nil { + return nil, nil, fmt.Errorf("error unwrapping log to map: (log %v) %v", log.Event.Outputs, log.Err) + } + return &log, v, nil + } +} + +// Get the string value of a key from a generic map[string]any +func GetStrVal(m map[string]any, k string) (string, error) { + v, ok := m[k] + if !ok { + return "", fmt.Errorf("key %s not found", k) + } + vstr, ok := v.(string) + if !ok { + return "", fmt.Errorf("key %s not a string (%T)", k, v) + } + return vstr, nil +} + +// Get int value of a key from a generic map[string]any +func GetBigIntVal(m map[string]any, k string) (*big.Int, error) { + v, ok := m[k] + if !ok { + return nil, fmt.Errorf("key %s not found", k) + } + val, ok := v.(*big.Int) + if !ok { + return nil, fmt.Errorf("key %s not a *big.Int (%T)", k, v) + } + return val, nil +} + +// Get the int value from a map[string]map[string]any +func GetBigIntValL2(m map[string]any, level1Key string, level2Key string) (*big.Int, error) { + v, ok := m[level1Key] + if !ok { + return nil, fmt.Errorf("key %s not found", level1Key) + } + level2Map, ok := v.(map[string]any) + if !ok { + return nil, fmt.Errorf("key %s not a map[string]any (%T)", level1Key, v) + } + return GetBigIntVal(level2Map, level2Key) +} diff --git a/core/services/relay/evm/chain_reader.go b/core/services/relay/evm/chain_reader.go index e5041f5486a..6b9f9411789 100644 --- a/core/services/relay/evm/chain_reader.go +++ b/core/services/relay/evm/chain_reader.go @@ -19,7 +19,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink-common/pkg/values" - evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" diff --git a/plugins/cmd/capabilities/log-event-trigger/main.go b/plugins/cmd/capabilities/log-event-trigger/main.go new file mode 100644 index 00000000000..8abecf54aeb --- /dev/null +++ b/plugins/cmd/capabilities/log-event-trigger/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hashicorp/go-plugin" + + "github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" +) + +const ( + serviceName = "LogEventTriggerCapability" +) + +type LogEventTriggerGRPCService struct { + trigger capabilities.TriggerCapability + s *loop.Server + config logevent.Config +} + +func main() { + s := loop.MustNewStartedServer(serviceName) + defer s.Stop() + + s.Logger.Infof("Starting %s", serviceName) + + stopCh := make(chan struct{}) + defer close(stopCh) + + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: loop.StandardCapabilitiesHandshakeConfig(), + Plugins: map[string]plugin.Plugin{ + loop.PluginStandardCapabilitiesName: &loop.StandardCapabilitiesLoop{ + PluginServer: &LogEventTriggerGRPCService{ + s: s, + }, + BrokerConfig: loop.BrokerConfig{Logger: s.Logger, StopCh: stopCh, GRPCOpts: s.GRPCOpts}, + }, + }, + GRPCServer: s.GRPCOpts.NewServer, + }) +} + +func (cs *LogEventTriggerGRPCService) Start(ctx context.Context) error { + return nil +} + +func (cs *LogEventTriggerGRPCService) Close() error { + return nil +} + +func (cs *LogEventTriggerGRPCService) Ready() error { + return nil +} + +func (cs *LogEventTriggerGRPCService) HealthReport() map[string]error { + return nil +} + +func (cs *LogEventTriggerGRPCService) Name() string { + return serviceName +} + +func (cs *LogEventTriggerGRPCService) Infos(ctx context.Context) ([]capabilities.CapabilityInfo, error) { + triggerInfo, err := cs.trigger.Info(ctx) + if err != nil { + return nil, err + } + + return []capabilities.CapabilityInfo{ + triggerInfo, + }, nil +} + +func (cs *LogEventTriggerGRPCService) Initialise( + ctx context.Context, + config string, + telemetryService core.TelemetryService, + store core.KeyValueStore, + capabilityRegistry core.CapabilitiesRegistry, + errorLog core.ErrorLog, + pipelineRunner core.PipelineRunnerService, + relayerSet core.RelayerSet, +) error { + cs.s.Logger.Debugf("Initialising %s", serviceName) + + var logEventConfig logevent.Config + err := json.Unmarshal([]byte(config), &logEventConfig) + if err != nil { + return fmt.Errorf("error decoding log_event_trigger config: %v", err) + } + + relayID := types.NewRelayID(logEventConfig.Network, logEventConfig.ChainID) + relayer, err := relayerSet.Get(ctx, relayID) + if err != nil { + return fmt.Errorf("error fetching relayer for chainID %s from relayerSet: %v", logEventConfig.ChainID, err) + } + + // Set relayer and trigger in LogEventTriggerGRPCService + cs.config = logEventConfig + cs.trigger, err = logevent.NewTriggerService(ctx, cs.s.Logger, relayer, logEventConfig) + if err != nil { + return fmt.Errorf("error creating new trigger for chainID %s: %v", logEventConfig.ChainID, err) + } + + if err := capabilityRegistry.Add(ctx, cs.trigger); err != nil { + return fmt.Errorf("error when adding cron trigger to the registry: %w", err) + } + + return nil +} From 74a5b4fa5c9e4c1dd4ef0a2aebe93fe2ef2767cd Mon Sep 17 00:00:00 2001 From: Connor Stein Date: Mon, 30 Sep 2024 17:22:31 -0400 Subject: [PATCH 05/28] Unorder changesets (#14608) * Unorder * Typo * Fix smoke * Rename test --- integration-tests/deployment/README.md | 15 ++++++++------- .../ccip/changeset/{1_cap_reg.go => cap_reg.go} | 4 ++-- .../{2_initial_deploy.go => initial_deploy.go} | 2 +- ...tial_deploy_test.go => initial_deploy_test.go} | 6 +++--- integration-tests/smoke/ccip_test.go | 4 ++-- 5 files changed, 16 insertions(+), 15 deletions(-) rename integration-tests/deployment/ccip/changeset/{1_cap_reg.go => cap_reg.go} (79%) rename integration-tests/deployment/ccip/changeset/{2_initial_deploy.go => initial_deploy.go} (88%) rename integration-tests/deployment/ccip/changeset/{2_initial_deploy_test.go => initial_deploy_test.go} (95%) diff --git a/integration-tests/deployment/README.md b/integration-tests/deployment/README.md index 000219c8aba..c0ac1bbc530 100644 --- a/integration-tests/deployment/README.md +++ b/integration-tests/deployment/README.md @@ -20,7 +20,6 @@ environments like testnet/mainnet. - EVM only /deployment/devenv -- Coming soon - package name `devenv` - Docker environment for higher fidelity testing - Support non-EVMs (yet to be implemented) @@ -36,20 +35,22 @@ environments like testnet/mainnet. - package name `changeset` imported as `ccipchangesets` - These function like scripts describing state transitions you wish to apply to _persistent_ environments like testnet/mainnet -- Ordered list of Go functions following the format +- They should be go functions where the first argument is an + environment and the second argument is a config struct which can be unique to the + changeset. The return value should be a `deployment.ChangesetOutput` and an error. ```Go -0001_descriptive_name.go -func Apply0001(env deployment.Environment, c ccipdeployment.Config) (deployment.ChangesetOutput, error) +do_something.go +func DoSomethingChangeSet(env deployment.Environment, c ccipdeployment.Config) (deployment.ChangesetOutput, error) { // Deploy contracts, generate MCMS proposals, generate // job specs according to contracts etc. return deployment.ChangesetOutput{}, nil } -0001_descriptive_name_test.go -func TestApply0001(t *testing.T) +do_something_test.go +func TestDoSomething(t *testing.T) { // Set up memory env - // Apply0001 function + // DoSomethingChangeSet function // Take the artifacts from ChangeSet output // Apply them to the memory env // Send traffic, run assertions etc. diff --git a/integration-tests/deployment/ccip/changeset/1_cap_reg.go b/integration-tests/deployment/ccip/changeset/cap_reg.go similarity index 79% rename from integration-tests/deployment/ccip/changeset/1_cap_reg.go rename to integration-tests/deployment/ccip/changeset/cap_reg.go index 1ca288321cd..0c12d20d94a 100644 --- a/integration-tests/deployment/ccip/changeset/1_cap_reg.go +++ b/integration-tests/deployment/ccip/changeset/cap_reg.go @@ -7,8 +7,8 @@ import ( ccipdeployment "github.com/smartcontractkit/chainlink/integration-tests/deployment/ccip" ) -// Separate migration because cap reg is an env var for CL nodes. -func Apply0001(env deployment.Environment, homeChainSel uint64) (deployment.ChangesetOutput, error) { +// Separated changset because cap reg is an env var for CL nodes. +func CapRegChangeSet(env deployment.Environment, homeChainSel uint64) (deployment.ChangesetOutput, error) { // Note we also deploy the cap reg. ab, _, err := ccipdeployment.DeployCapReg(env.Logger, env.Chains[homeChainSel]) if err != nil { diff --git a/integration-tests/deployment/ccip/changeset/2_initial_deploy.go b/integration-tests/deployment/ccip/changeset/initial_deploy.go similarity index 88% rename from integration-tests/deployment/ccip/changeset/2_initial_deploy.go rename to integration-tests/deployment/ccip/changeset/initial_deploy.go index 99d16d21c40..9a97150ade7 100644 --- a/integration-tests/deployment/ccip/changeset/2_initial_deploy.go +++ b/integration-tests/deployment/ccip/changeset/initial_deploy.go @@ -12,7 +12,7 @@ import ( // TODO: Maybe there's a generics approach here? // Note if the change set is a deployment and it fails we have 2 options: // - Just throw away the addresses, fix issue and try again (potentially expensive on mainnet) -func Apply0002(env deployment.Environment, c ccipdeployment.DeployCCIPContractConfig) (deployment.ChangesetOutput, error) { +func InitialDeployChangeSet(env deployment.Environment, c ccipdeployment.DeployCCIPContractConfig) (deployment.ChangesetOutput, error) { ab, err := ccipdeployment.DeployCCIPContracts(env, c) if err != nil { env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "addresses", ab) diff --git a/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go b/integration-tests/deployment/ccip/changeset/initial_deploy_test.go similarity index 95% rename from integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go rename to integration-tests/deployment/ccip/changeset/initial_deploy_test.go index 5744dfbea95..6f40d41acb9 100644 --- a/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go +++ b/integration-tests/deployment/ccip/changeset/initial_deploy_test.go @@ -17,7 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" ) -func Test0002_InitialDeploy(t *testing.T) { +func TestInitialDeploy(t *testing.T) { lggr := logger.TestLogger(t) ctx := ccdeploy.Context(t) tenv := ccdeploy.NewMemoryEnvironment(t, lggr, 3) @@ -35,8 +35,8 @@ func Test0002_InitialDeploy(t *testing.T) { DeviationPPB: cciptypes.NewBigIntFromInt64(1e9), }, ) - // Apply migration - output, err := Apply0002(tenv.Env, ccdeploy.DeployCCIPContractConfig{ + // Apply changeset + output, err := InitialDeployChangeSet(tenv.Env, ccdeploy.DeployCCIPContractConfig{ HomeChainSel: tenv.HomeChainSel, FeedChainSel: tenv.FeedChainSel, ChainsToDeploy: tenv.Env.AllChainSelectors(), diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index b4961113131..8e80985620d 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -16,7 +16,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" ) -func Test0002_InitialDeployOnLocal(t *testing.T) { +func TestInitialDeployOnLocal(t *testing.T) { lggr := logger.TestLogger(t) ctx := ccdeploy.Context(t) tenv := ccdeploy.NewLocalDevEnvironment(t, lggr) @@ -35,7 +35,7 @@ func Test0002_InitialDeployOnLocal(t *testing.T) { }, ) // Apply migration - output, err := changeset.Apply0002(tenv.Env, ccdeploy.DeployCCIPContractConfig{ + output, err := changeset.InitialDeployChangeSet(tenv.Env, ccdeploy.DeployCCIPContractConfig{ HomeChainSel: tenv.HomeChainSel, FeedChainSel: tenv.FeedChainSel, ChainsToDeploy: tenv.Env.AllChainSelectors(), From 41443fa65db786edfa73f15d221446ea2977070e Mon Sep 17 00:00:00 2001 From: DavidOrchard Date: Mon, 30 Sep 2024 15:02:09 -0700 Subject: [PATCH 06/28] Web API Trigger capability and handler (#14580) * Web API Trigger capability and handler * PR review comments * PR review comments: remove array of topics from client, timestamp on triggereventId and more * increase test timeout and handle context closure * fix lint * fix lint errors from cicd * go mod tidy to pickup latest -common * downgrade -common * lint * change TriggerConfig to Config to avoid stutter of triger.TriggerConfig * PR Review comments. Mostly return error from processTrigger * add check for at least one successful workflow invoke and test --- core/capabilities/webapi/trigger.go | 281 ++++++++++++- core/capabilities/webapi/trigger_test.go | 383 ++++++++++++++++++ .../gateway/web_api_trigger/invoke_trigger.go | 147 +++++++ core/services/gateway/handler_factory.go | 3 + .../handlers/webapicapabilities/handler.go | 119 +++++- .../webapicapabilities/handler_test.go | 185 +++++++++ .../handlers/webapicapabilities/webapi.go | 48 +++ .../services/standardcapabilities/delegate.go | 4 +- 8 files changed, 1157 insertions(+), 13 deletions(-) create mode 100644 core/capabilities/webapi/trigger_test.go create mode 100644 core/scripts/gateway/web_api_trigger/invoke_trigger.go create mode 100644 core/services/gateway/handlers/webapicapabilities/handler_test.go diff --git a/core/capabilities/webapi/trigger.go b/core/capabilities/webapi/trigger.go index db0df7d1410..611879c7a0a 100644 --- a/core/capabilities/webapi/trigger.go +++ b/core/capabilities/webapi/trigger.go @@ -1,18 +1,279 @@ -package webapi +package trigger import ( + "context" + "encoding/json" + "errors" + "fmt" + "sync" + + ethCommon "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types/core" + "github.com/smartcontractkit/chainlink-common/pkg/values" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" - "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/webapicapabilities" ) -func NewTrigger(config string, registry core.CapabilitiesRegistry, connector connector.GatewayConnector, lggr logger.Logger) (job.ServiceCtx, error) { - // TODO (CAPPL-22, CAPPL-24): - // - decode config - // - create an implementation of the capability API and add it to the Registry - // - create a handler and register it with Gateway Connector - // - manage trigger subscriptions - // - process incoming trigger events and related metadata - return nil, nil +const defaultSendChannelBufferSize = 1000 + +const TriggerType = "web-trigger@1.0.0" + +var webapiTriggerInfo = capabilities.MustNewCapabilityInfo( + TriggerType, + capabilities.CapabilityTypeTrigger, + "A trigger to start workflow execution from a web api call", +) + +type Input struct { +} +type Config struct { + AllowedSenders []string `toml:"allowedSenders"` + AllowedTopics []string `toml:"allowedTopics"` + RateLimiter common.RateLimiterConfig `toml:"rateLimiter"` + // RequiredParams is advisory to the web trigger message sender it is not enforced. + RequiredParams []string `toml:"requiredParams"` +} + +type webapiTrigger struct { + allowedSenders map[string]bool + allowedTopics map[string]bool + ch chan<- capabilities.TriggerResponse + config Config + rateLimiter *common.RateLimiter +} + +type triggerConnectorHandler struct { + services.StateMachine + + capabilities.CapabilityInfo + capabilities.Validator[Config, Input, capabilities.TriggerResponse] + connector connector.GatewayConnector + lggr logger.Logger + mu sync.Mutex + registeredWorkflows map[string]webapiTrigger +} + +var _ capabilities.TriggerCapability = (*triggerConnectorHandler)(nil) +var _ services.Service = &triggerConnectorHandler{} + +func NewTrigger(config string, registry core.CapabilitiesRegistry, connector connector.GatewayConnector, lggr logger.Logger) (*triggerConnectorHandler, error) { + if connector == nil { + return nil, errors.New("missing connector") + } + handler := &triggerConnectorHandler{ + Validator: capabilities.NewValidator[Config, Input, capabilities.TriggerResponse](capabilities.ValidatorArgs{Info: webapiTriggerInfo}), + connector: connector, + registeredWorkflows: map[string]webapiTrigger{}, + lggr: lggr.Named("WorkflowConnectorHandler"), + } + + return handler, nil +} + +// processTrigger iterates over each topic, checking against senders and rateLimits, then starting event processing and responding +func (h *triggerConnectorHandler) processTrigger(ctx context.Context, gatewayID string, body *api.MessageBody, sender ethCommon.Address, payload webapicapabilities.TriggerRequestPayload) error { + // Pass on the payload with the expectation that it's in an acceptable format for the executor + wrappedPayload, err := values.WrapMap(payload) + if err != nil { + return fmt.Errorf("error wrapping payload %s", err) + } + topics := payload.Topics + + // empty topics is error for V1 + if len(topics) == 0 { + return fmt.Errorf("empty Workflow Topics") + } + + // workflows that have matched topics + matchedWorkflows := 0 + // workflows that have matched topic and passed all checks + fullyMatchedWorkflows := 0 + for _, trigger := range h.registeredWorkflows { + for _, topic := range topics { + if trigger.allowedTopics[topic] { + matchedWorkflows++ + if !trigger.allowedSenders[sender.String()] { + err = fmt.Errorf("unauthorized Sender %s, messageID %s", sender.String(), body.MessageId) + h.lggr.Debugw(err.Error()) + continue + } + if !trigger.rateLimiter.Allow(body.Sender) { + err = fmt.Errorf("request rate-limited for sender %s, messageID %s", sender.String(), body.MessageId) + continue + } + fullyMatchedWorkflows++ + TriggerEventID := body.Sender + payload.TriggerEventID + tr := capabilities.TriggerResponse{ + Event: capabilities.TriggerEvent{ + TriggerType: TriggerType, + ID: TriggerEventID, + Outputs: wrappedPayload, + }, + } + select { + case <-ctx.Done(): + return nil + case trigger.ch <- tr: + // Sending n topics that match a workflow with n allowedTopics, can only be triggered once. + break + } + } + } + } + if matchedWorkflows == 0 { + return fmt.Errorf("no Matching Workflow Topics") + } + + if fullyMatchedWorkflows > 0 { + return nil + } + return err +} + +func (h *triggerConnectorHandler) HandleGatewayMessage(ctx context.Context, gatewayID string, msg *api.Message) { + // TODO: Validate Signature + body := &msg.Body + sender := ethCommon.HexToAddress(body.Sender) + var payload webapicapabilities.TriggerRequestPayload + err := json.Unmarshal(body.Payload, &payload) + if err != nil { + h.lggr.Errorw("error decoding payload", "err", err) + err = h.sendResponse(ctx, gatewayID, body, webapicapabilities.TriggerResponsePayload{Status: "ERROR", ErrorMessage: fmt.Errorf("error %s decoding payload", err.Error()).Error()}) + if err != nil { + h.lggr.Errorw("error sending response", "err", err) + } + return + } + + switch body.Method { + case webapicapabilities.MethodWebAPITrigger: + resp := h.processTrigger(ctx, gatewayID, body, sender, payload) + var response webapicapabilities.TriggerResponsePayload + if resp == nil { + response = webapicapabilities.TriggerResponsePayload{Status: "ACCEPTED"} + } else { + response = webapicapabilities.TriggerResponsePayload{Status: "ERROR", ErrorMessage: resp.Error()} + h.lggr.Errorw("Error processing trigger", "gatewayID", gatewayID, "body", body, "response", resp) + } + err = h.sendResponse(ctx, gatewayID, body, response) + if err != nil { + h.lggr.Errorw("Error sending response", "body", body, "response", response, "err", err) + } + return + + default: + h.lggr.Errorw("unsupported method", "id", gatewayID, "method", body.Method) + err = h.sendResponse(ctx, gatewayID, body, webapicapabilities.TriggerResponsePayload{Status: "ERROR", ErrorMessage: fmt.Errorf("unsupported method %s", body.Method).Error()}) + if err != nil { + h.lggr.Errorw("error sending response", "err", err) + } + } +} + +func (h *triggerConnectorHandler) RegisterTrigger(ctx context.Context, req capabilities.TriggerRegistrationRequest) (<-chan capabilities.TriggerResponse, error) { + cfg := req.Config + if cfg == nil { + return nil, errors.New("config is required to register a web api trigger") + } + + reqConfig, err := h.ValidateConfig(cfg) + if err != nil { + return nil, err + } + + if len(reqConfig.AllowedSenders) == 0 { + return nil, errors.New("allowedSenders must have at least 1 entry") + } + + h.mu.Lock() + defer h.mu.Unlock() + _, errBool := h.registeredWorkflows[req.TriggerID] + if errBool { + return nil, fmt.Errorf("triggerId %s already registered", req.TriggerID) + } + + rateLimiter, err := common.NewRateLimiter(reqConfig.RateLimiter) + if err != nil { + return nil, err + } + + allowedSendersMap := map[string]bool{} + for _, k := range reqConfig.AllowedSenders { + allowedSendersMap[k] = true + } + + allowedTopicsMap := map[string]bool{} + for _, k := range reqConfig.AllowedTopics { + allowedTopicsMap[k] = true + } + + ch := make(chan capabilities.TriggerResponse, defaultSendChannelBufferSize) + + h.registeredWorkflows[req.TriggerID] = webapiTrigger{ + allowedTopics: allowedTopicsMap, + allowedSenders: allowedSendersMap, + ch: ch, + config: *reqConfig, + rateLimiter: rateLimiter, + } + + return ch, nil +} + +func (h *triggerConnectorHandler) UnregisterTrigger(ctx context.Context, req capabilities.TriggerRegistrationRequest) error { + h.mu.Lock() + defer h.mu.Unlock() + workflow, ok := h.registeredWorkflows[req.TriggerID] + if !ok { + return fmt.Errorf("triggerId %s not registered", req.TriggerID) + } + + close(workflow.ch) + delete(h.registeredWorkflows, req.TriggerID) + return nil +} + +func (h *triggerConnectorHandler) Start(ctx context.Context) error { + return h.StartOnce("GatewayConnectorServiceWrapper", func() error { + return h.connector.AddHandler([]string{"web_trigger"}, h) + }) +} +func (h *triggerConnectorHandler) Close() error { + return h.StopOnce("GatewayConnectorServiceWrapper", func() error { + return nil + }) +} + +func (h *triggerConnectorHandler) HealthReport() map[string]error { + return map[string]error{h.Name(): h.Healthy()} +} + +func (h *triggerConnectorHandler) Name() string { + return "WebAPITrigger" +} + +func (h *triggerConnectorHandler) sendResponse(ctx context.Context, gatewayID string, requestBody *api.MessageBody, payload any) error { + payloadJSON, err := json.Marshal(payload) + if err != nil { + h.lggr.Errorw("error marshalling payload", "err", err) + payloadJSON, _ = json.Marshal(webapicapabilities.TriggerResponsePayload{Status: "ERROR", ErrorMessage: fmt.Errorf("error %s marshalling payload", err.Error()).Error()}) + } + + msg := &api.Message{ + Body: api.MessageBody{ + MessageId: requestBody.MessageId, + DonId: requestBody.DonId, + Method: requestBody.Method, + Receiver: requestBody.Sender, + Payload: payloadJSON, + }, + } + + return h.connector.SendToGateway(ctx, gatewayID, msg) } diff --git a/core/capabilities/webapi/trigger_test.go b/core/capabilities/webapi/trigger_test.go new file mode 100644 index 00000000000..d370b1ec7ac --- /dev/null +++ b/core/capabilities/webapi/trigger_test.go @@ -0,0 +1,383 @@ +package trigger + +import ( + "context" + "encoding/json" + "errors" + "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + registrymock "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" + "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + corelogger "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" + gcmocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/webapicapabilities" +) + +const ( + privateKey1 = "65456ffb8af4a2b93959256a8e04f6f2fe0943579fb3c9c3350593aabb89023f" + privateKey2 = "65456ffb8af4a2b93959256a8e04f6f2fe0943579fb3c9c3350593aabb89023e" + triggerID1 = "5" + triggerID2 = "6" + workflowID1 = "15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0" + workflowExecutionID1 = "95ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0abbadeed" + owner1 = "0x00000000000000000000000000000000000000aa" + address1 = "0x853d51d5d9935964267a5050aC53aa63ECA39bc5" + address2 = "0x853d51d5d9935964267a5050aC53aa63ECA39bc6" +) + +type testHarness struct { + registry *registrymock.CapabilitiesRegistry + connector *gcmocks.GatewayConnector + lggr logger.Logger + config string + trigger *triggerConnectorHandler +} + +func workflowTriggerConfig(_ testHarness, addresses []string, topics []string) (*values.Map, error) { + var rateLimitConfig, err = values.NewMap(map[string]any{ + "GlobalRPS": 100.0, + "GlobalBurst": 101, + "PerSenderRPS": 102.0, + "PerSenderBurst": 103, + }) + if err != nil { + return nil, err + } + + triggerRegistrationConfig, err := values.NewMap(map[string]interface{}{ + "RateLimiter": rateLimitConfig, + "AllowedSenders": addresses, + "AllowedTopics": topics, + "RequiredParams": []string{"bid", "ask"}, + }) + return triggerRegistrationConfig, err +} + +func setup(t *testing.T) testHarness { + registry := registrymock.NewCapabilitiesRegistry(t) + connector := gcmocks.NewGatewayConnector(t) + lggr := corelogger.TestLogger(t) + config := "" + + trigger, err := NewTrigger(config, registry, connector, lggr) + require.NoError(t, err) + + return testHarness{ + registry: registry, + connector: connector, + lggr: lggr, + config: config, + trigger: trigger, + } +} + +func gatewayRequest(t *testing.T, privateKey string, topics string, methodName string) *api.Message { + messageID := "12345" + if methodName == "" { + methodName = webapicapabilities.MethodWebAPITrigger + } + donID := "workflow_don_1" + + key, err := crypto.HexToECDSA(privateKey) + require.NoError(t, err) + + payload := `{ + "trigger_id": "` + TriggerType + `", + "trigger_event_id": "action_1234567890", + "timestamp": 1234567890, + "topics": ` + topics + `, + "params": { + "bid": "101", + "ask": "102" + } + } +` + payloadJSON := []byte(payload) + msg := &api.Message{ + Body: api.MessageBody{ + MessageId: messageID, + Method: methodName, + DonId: donID, + Payload: json.RawMessage(payloadJSON), + }, + } + err = msg.Sign(key) + require.NoError(t, err) + return msg +} + +func getResponseFromArg(arg interface{}) (webapicapabilities.TriggerResponsePayload, error) { + var response webapicapabilities.TriggerResponsePayload + err := json.Unmarshal((&(arg.(*api.Message)).Body).Payload, &response) + return response, err +} + +func requireNoChanMsg[T any](t *testing.T, ch <-chan T) { + timedOut := false + select { + case <-ch: + case <-time.After(100 * time.Millisecond): + timedOut = true + } + require.True(t, timedOut) +} + +func requireChanMsg[T capabilities.TriggerResponse](t *testing.T, ch <-chan capabilities.TriggerResponse) (capabilities.TriggerResponse, error) { + timedOut := false + select { + case resp := <-ch: + return resp, nil + case <-time.After(100 * time.Millisecond): + timedOut = true + } + require.False(t, timedOut) + return capabilities.TriggerResponse{}, errors.New("channel timeout") +} + +func TestTriggerExecute(t *testing.T) { + th := setup(t) + ctx := testutils.Context(t) + ctx, cancelContext := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) + Config, _ := workflowTriggerConfig(th, []string{address1}, []string{"daily_price_update", "ad_hoc_price_update"}) + triggerReq := capabilities.TriggerRegistrationRequest{ + TriggerID: triggerID1, + Metadata: capabilities.RequestMetadata{ + WorkflowID: workflowID1, + WorkflowOwner: owner1, + }, + Config: Config, + } + channel, err := th.trigger.RegisterTrigger(ctx, triggerReq) + require.NoError(t, err) + + Config2, err := workflowTriggerConfig(th, []string{address1}, []string{"daily_price_update2", "ad_hoc_price_update"}) + require.NoError(t, err) + + triggerReq2 := capabilities.TriggerRegistrationRequest{ + TriggerID: triggerID2, + Metadata: capabilities.RequestMetadata{ + WorkflowID: workflowID1, + WorkflowOwner: owner1, + }, + Config: Config2, + } + channel2, err := th.trigger.RegisterTrigger(ctx, triggerReq2) + require.NoError(t, err) + + t.Run("happy case single topic to single workflow", func(t *testing.T) { + gatewayRequest := gatewayRequest(t, privateKey1, `["daily_price_update"]`, "") + + th.connector.On("SendToGateway", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + resp, _ := getResponseFromArg(args.Get(2)) + require.Equal(t, webapicapabilities.TriggerResponsePayload{Status: "ACCEPTED"}, resp) + }).Return(nil).Once() + + th.trigger.HandleGatewayMessage(ctx, "gateway1", gatewayRequest) + + received, chanErr := requireChanMsg(t, channel) + require.Equal(t, received.Event.TriggerType, TriggerType) + require.NoError(t, chanErr) + + requireNoChanMsg(t, channel2) + data := received.Event.Outputs + var payload webapicapabilities.TriggerRequestPayload + unwrapErr := data.UnwrapTo(&payload) + require.NoError(t, unwrapErr) + require.Equal(t, payload.Topics, []string{"daily_price_update"}) + }) + + t.Run("happy case single different topic 2 workflows.", func(t *testing.T) { + gatewayRequest := gatewayRequest(t, privateKey1, `["ad_hoc_price_update"]`, "") + + th.connector.On("SendToGateway", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + resp, _ := getResponseFromArg(args.Get(2)) + require.Equal(t, webapicapabilities.TriggerResponsePayload{Status: "ACCEPTED"}, resp) + }).Return(nil).Once() + + th.trigger.HandleGatewayMessage(ctx, "gateway1", gatewayRequest) + + sent := <-channel + require.Equal(t, sent.Event.TriggerType, TriggerType) + data := sent.Event.Outputs + var payload webapicapabilities.TriggerRequestPayload + unwrapErr := data.UnwrapTo(&payload) + require.NoError(t, unwrapErr) + require.Equal(t, payload.Topics, []string{"ad_hoc_price_update"}) + + sent2 := <-channel2 + require.Equal(t, sent2.Event.TriggerType, TriggerType) + data2 := sent2.Event.Outputs + var payload2 webapicapabilities.TriggerRequestPayload + err2 := data2.UnwrapTo(&payload2) + require.NoError(t, err2) + require.Equal(t, payload2.Topics, []string{"ad_hoc_price_update"}) + }) + + t.Run("sad case empty topic 2 workflows", func(t *testing.T) { + gatewayRequest := gatewayRequest(t, privateKey1, `[]`, "") + + th.connector.On("SendToGateway", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + resp, _ := getResponseFromArg(args.Get(2)) + require.Equal(t, webapicapabilities.TriggerResponsePayload{Status: "ERROR", ErrorMessage: "empty Workflow Topics"}, resp) + }).Return(nil).Once() + + th.trigger.HandleGatewayMessage(ctx, "gateway1", gatewayRequest) + + requireNoChanMsg(t, channel) + requireNoChanMsg(t, channel2) + }) + + t.Run("sad case topic with no workflows", func(t *testing.T) { + gatewayRequest := gatewayRequest(t, privateKey1, `["foo"]`, "") + th.connector.On("SendToGateway", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + resp, _ := getResponseFromArg(args.Get(2)) + require.Equal(t, webapicapabilities.TriggerResponsePayload{Status: "ERROR", ErrorMessage: "no Matching Workflow Topics"}, resp) + }).Return(nil).Once() + + th.trigger.HandleGatewayMessage(ctx, "gateway1", gatewayRequest) + requireNoChanMsg(t, channel) + requireNoChanMsg(t, channel2) + }) + + t.Run("sad case Not Allowed Sender", func(t *testing.T) { + gatewayRequest := gatewayRequest(t, privateKey2, `["ad_hoc_price_update"]`, "") + th.connector.On("SendToGateway", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + resp, _ := getResponseFromArg(args.Get(2)) + + require.Equal(t, webapicapabilities.TriggerResponsePayload{Status: "ERROR", ErrorMessage: "unauthorized Sender 0x2dAC9f74Ee66e2D55ea1B8BE284caFedE048dB3A, messageID 12345"}, resp) + }).Return(nil).Once() + + th.trigger.HandleGatewayMessage(ctx, "gateway1", gatewayRequest) + requireNoChanMsg(t, channel) + requireNoChanMsg(t, channel2) + }) + + t.Run("sad case Invalid Method", func(t *testing.T) { + gatewayRequest := gatewayRequest(t, privateKey2, `["ad_hoc_price_update"]`, "boo") + th.connector.On("SendToGateway", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + resp, _ := getResponseFromArg(args.Get(2)) + require.Equal(t, webapicapabilities.TriggerResponsePayload{Status: "ERROR", ErrorMessage: "unsupported method boo"}, resp) + }).Return(nil).Once() + + th.trigger.HandleGatewayMessage(ctx, "gateway1", gatewayRequest) + requireNoChanMsg(t, channel) + requireNoChanMsg(t, channel2) + }) + + err = th.trigger.UnregisterTrigger(ctx, triggerReq) + require.NoError(t, err) + err = th.trigger.UnregisterTrigger(ctx, triggerReq2) + require.NoError(t, err) + cancelContext() +} + +func TestRegisterNoAllowedSenders(t *testing.T) { + th := setup(t) + ctx := testutils.Context(t) + Config, _ := workflowTriggerConfig(th, []string{}, []string{"daily_price_update"}) + + triggerReq := capabilities.TriggerRegistrationRequest{ + TriggerID: triggerID1, + Metadata: capabilities.RequestMetadata{ + WorkflowID: workflowID1, + WorkflowOwner: owner1, + }, + Config: Config, + } + _, err := th.trigger.RegisterTrigger(ctx, triggerReq) + require.Error(t, err) + + gatewayRequest(t, privateKey1, `["daily_price_update"]`, "") +} + +func TestTriggerExecute2WorkflowsSameTopicDifferentAllowLists(t *testing.T) { + th := setup(t) + ctx := testutils.Context(t) + ctx, cancelContext := context.WithDeadline(ctx, time.Now().Add(10*time.Second)) + Config, _ := workflowTriggerConfig(th, []string{address2}, []string{"daily_price_update"}) + triggerReq := capabilities.TriggerRegistrationRequest{ + TriggerID: triggerID1, + Metadata: capabilities.RequestMetadata{ + WorkflowID: workflowID1, + WorkflowOwner: owner1, + }, + Config: Config, + } + channel, err := th.trigger.RegisterTrigger(ctx, triggerReq) + require.NoError(t, err) + + Config2, err := workflowTriggerConfig(th, []string{address1}, []string{"daily_price_update"}) + require.NoError(t, err) + + triggerReq2 := capabilities.TriggerRegistrationRequest{ + TriggerID: triggerID2, + Metadata: capabilities.RequestMetadata{ + WorkflowID: workflowID1, + WorkflowOwner: owner1, + }, + Config: Config2, + } + channel2, err := th.trigger.RegisterTrigger(ctx, triggerReq2) + require.NoError(t, err) + + t.Run("happy case single topic to single workflow", func(t *testing.T) { + gatewayRequest := gatewayRequest(t, privateKey1, `["daily_price_update"]`, "") + + th.connector.On("SendToGateway", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + resp, _ := getResponseFromArg(args.Get(2)) + require.Equal(t, webapicapabilities.TriggerResponsePayload{Status: "ACCEPTED"}, resp) + }).Return(nil).Once() + + th.trigger.HandleGatewayMessage(ctx, "gateway1", gatewayRequest) + + requireNoChanMsg(t, channel) + received, chanErr := requireChanMsg(t, channel2) + require.Equal(t, received.Event.TriggerType, TriggerType) + require.NoError(t, chanErr) + data := received.Event.Outputs + var payload webapicapabilities.TriggerRequestPayload + unwrapErr := data.UnwrapTo(&payload) + require.NoError(t, unwrapErr) + require.Equal(t, payload.Topics, []string{"daily_price_update"}) + }) + err = th.trigger.UnregisterTrigger(ctx, triggerReq) + require.NoError(t, err) + err = th.trigger.UnregisterTrigger(ctx, triggerReq2) + require.NoError(t, err) + cancelContext() +} + +func TestRegisterUnregister(t *testing.T) { + th := setup(t) + ctx := testutils.Context(t) + Config, err := workflowTriggerConfig(th, []string{address1}, []string{"daily_price_update"}) + require.NoError(t, err) + + triggerReq := capabilities.TriggerRegistrationRequest{ + TriggerID: triggerID1, + Metadata: capabilities.RequestMetadata{ + WorkflowID: workflowID1, + WorkflowOwner: owner1, + }, + Config: Config, + } + + channel, err := th.trigger.RegisterTrigger(ctx, triggerReq) + require.NoError(t, err) + require.NotEmpty(t, th.trigger.registeredWorkflows[triggerID1]) + + err = th.trigger.UnregisterTrigger(ctx, triggerReq) + require.NoError(t, err) + _, open := <-channel + require.Equal(t, open, false) +} diff --git a/core/scripts/gateway/web_api_trigger/invoke_trigger.go b/core/scripts/gateway/web_api_trigger/invoke_trigger.go new file mode 100644 index 00000000000..00bc08b3489 --- /dev/null +++ b/core/scripts/gateway/web_api_trigger/invoke_trigger.go @@ -0,0 +1,147 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/joho/godotenv" + + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" +) + +// https://gateway-us-1.chain.link/web-trigger +// { +// jsonrpc: "2.0", +// id: "...", +// method: "web-trigger", +// params: { +// signature: "...", +// body: { +// don_id: "workflow_123", +// payload: { +// trigger_id: "web-trigger@1.0.0", +// trigger_event_id: "action_1234567890", +// timestamp: 1234567890, +// sub-events: [ +// { +// topics: ["daily_price_update"], +// params: { +// bid: "101", +// ask: "102" +// } +// }, +// { +// topics: ["daily_message", "summary"], +// params: { +// message: "all good!", +// } +// }, +// ] +// } +// } +// } +// } + +func main() { + gatewayURL := flag.String("gateway_url", "http://localhost:5002", "Gateway URL") + privateKey := flag.String("private_key", "65456ffb8af4a2b93959256a8e04f6f2fe0943579fb3c9c3350593aabb89023f", "Private key to sign the message with") + messageID := flag.String("id", "12345", "Request ID") + methodName := flag.String("method", "web_trigger", "Method name") + donID := flag.String("don_id", "workflow_don_1", "DON ID") + + flag.Parse() + + if privateKey == nil || *privateKey == "" { + if err := godotenv.Load(); err != nil { + panic(err) + } + + privateKeyEnvVar := os.Getenv("PRIVATE_KEY") + privateKey = &privateKeyEnvVar + fmt.Println("Loaded private key from .env") + } + + // validate key and extract address + key, err := crypto.HexToECDSA(*privateKey) + if err != nil { + fmt.Println("error parsing private key", err) + return + } + + payload := `{ + "trigger_id": "web-trigger@1.0.0", + "trigger_event_id": "action_1234567890", + "timestamp": 1234567890, + "topics": ["daily_price_update"], + "params": { + "bid": "101", + "ask": "102" + } + } +` + payloadJSON := []byte(payload) + msg := &api.Message{ + Body: api.MessageBody{ + MessageId: *messageID, + Method: *methodName, + DonId: *donID, + Payload: json.RawMessage(payloadJSON), + }, + } + if err = msg.Sign(key); err != nil { + fmt.Println("error signing message", err) + return + } + codec := api.JsonRPCCodec{} + rawMsg, err := codec.EncodeRequest(msg) + if err != nil { + fmt.Println("error JSON-RPC encoding", err) + return + } + + createRequest := func() (req *http.Request, err error) { + req, err = http.NewRequestWithContext(context.Background(), "POST", *gatewayURL, bytes.NewBuffer(rawMsg)) + if err == nil { + req.Header.Set("Content-Type", "application/json") + } + return + } + + client := &http.Client{} + + sendRequest := func() { + req, err2 := createRequest() + if err2 != nil { + fmt.Println("error creating a request", err2) + return + } + + resp, err2 := client.Do(req) + if err2 != nil { + fmt.Println("error sending a request", err2) + return + } + defer resp.Body.Close() + + body, err2 := io.ReadAll(resp.Body) + if err2 != nil { + fmt.Println("error sending a request", err2) + return + } + + var prettyJSON bytes.Buffer + if err2 = json.Indent(&prettyJSON, body, "", " "); err2 != nil { + fmt.Println(string(body)) + } else { + fmt.Println(prettyJSON.String()) + } + } + sendRequest() +} diff --git a/core/services/gateway/handler_factory.go b/core/services/gateway/handler_factory.go index 6793350f317..92ad48b5395 100644 --- a/core/services/gateway/handler_factory.go +++ b/core/services/gateway/handler_factory.go @@ -10,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/webapicapabilities" ) const ( @@ -38,6 +39,8 @@ func (hf *handlerFactory) NewHandler(handlerType HandlerType, handlerConfig json switch handlerType { case FunctionsHandlerType: return functions.NewFunctionsHandlerFromConfig(handlerConfig, donConfig, don, hf.legacyChains, hf.ds, hf.lggr) + case WebAPICapabilitiesType: + return webapicapabilities.NewWorkflowHandler(handlerConfig, donConfig, don, hf.lggr) case DummyHandlerType: return handlers.NewDummyHandler(donConfig, don, hf.lggr) default: diff --git a/core/services/gateway/handlers/webapicapabilities/handler.go b/core/services/gateway/handlers/webapicapabilities/handler.go index a38651d40fc..d6caf067dd0 100644 --- a/core/services/gateway/handlers/webapicapabilities/handler.go +++ b/core/services/gateway/handlers/webapicapabilities/handler.go @@ -1,6 +1,123 @@ package webapicapabilities +import ( + "context" + "encoding/json" + "fmt" + "sync" + "time" + + "go.uber.org/multierr" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" +) + const ( // NOTE: more methods will go here. HTTP trigger/action/target; etc. - MethodWebAPITarget = "web_api_target" + MethodWebAPITarget = "web_api_target" + MethodWebAPITrigger = "web_api_trigger" ) + +type handler struct { + config HandlerConfig + donConfig *config.DONConfig + don handlers.DON + savedCallbacks map[string]*savedCallback + mu sync.Mutex + lggr logger.Logger +} + +type HandlerConfig struct { + MaxAllowedMessageAgeSec uint +} +type savedCallback struct { + id string + callbackCh chan<- handlers.UserCallbackPayload +} + +var _ handlers.Handler = (*handler)(nil) + +func NewWorkflowHandler(handlerConfig json.RawMessage, donConfig *config.DONConfig, don handlers.DON, lggr logger.Logger) (*handler, error) { + var cfg HandlerConfig + err := json.Unmarshal(handlerConfig, &cfg) + if err != nil { + return nil, err + } + + return &handler{ + config: cfg, + donConfig: donConfig, + don: don, + savedCallbacks: make(map[string]*savedCallback), + lggr: lggr.Named("WorkflowHandler." + donConfig.DonId), + }, nil +} + +func (d *handler) HandleUserMessage(ctx context.Context, msg *api.Message, callbackCh chan<- handlers.UserCallbackPayload) error { + d.mu.Lock() + d.savedCallbacks[msg.Body.MessageId] = &savedCallback{msg.Body.MessageId, callbackCh} + don := d.don + d.mu.Unlock() + body := msg.Body + var payload TriggerRequestPayload + err := json.Unmarshal(body.Payload, &payload) + if err != nil { + d.lggr.Errorw("error decoding payload", "err", err) + callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.UserMessageParseError, ErrMsg: fmt.Sprintf("error decoding payload %s", err.Error())} + close(callbackCh) + return nil + } + + if payload.Timestamp == 0 { + d.lggr.Errorw("error decoding payload") + callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.UserMessageParseError, ErrMsg: "error decoding payload"} + close(callbackCh) + return nil + } + + if uint(time.Now().Unix())-d.config.MaxAllowedMessageAgeSec > uint(payload.Timestamp) { + callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.HandlerError, ErrMsg: "stale message"} + close(callbackCh) + return nil + } + // TODO: apply allowlist and rate-limiting here + if msg.Body.Method != MethodWebAPITrigger { + d.lggr.Errorw("unsupported method", "method", body.Method) + callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.HandlerError, ErrMsg: fmt.Sprintf("invalid method %s", msg.Body.Method)} + close(callbackCh) + return nil + } + + // Send to all nodes. + for _, member := range d.donConfig.Members { + err = multierr.Combine(err, don.SendToNode(ctx, member.Address, msg)) + } + return err +} + +func (d *handler) HandleNodeMessage(ctx context.Context, msg *api.Message, _ string) error { + d.mu.Lock() + savedCb, found := d.savedCallbacks[msg.Body.MessageId] + delete(d.savedCallbacks, msg.Body.MessageId) + d.mu.Unlock() + + if found { + // Send first response from a node back to the user, ignore any other ones. + // TODO: in practice, we should wait for at least 2F+1 nodes to respond and then return an aggregated response + // back to the user. + savedCb.callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.NoError, ErrMsg: ""} + close(savedCb.callbackCh) + } + return nil +} + +func (d *handler) Start(context.Context) error { + return nil +} + +func (d *handler) Close() error { + return nil +} diff --git a/core/services/gateway/handlers/webapicapabilities/handler_test.go b/core/services/gateway/handlers/webapicapabilities/handler_test.go new file mode 100644 index 00000000000..ef278e40ffd --- /dev/null +++ b/core/services/gateway/handlers/webapicapabilities/handler_test.go @@ -0,0 +1,185 @@ +package webapicapabilities + +import ( + "encoding/json" + "fmt" + "strconv" + "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/mock" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" + gwcommon "github.com/smartcontractkit/chainlink/v2/core/services/gateway/common" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" + + handlermocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/mocks" +) + +const ( + defaultSendChannelBufferSize = 1000 + privateKey1 = "65456ffb8af4a2b93959256a8e04f6f2fe0943579fb3c9c3350593aabb89023f" + privateKey2 = "65456ffb8af4a2b93959256a8e04f6f2fe0943579fb3c9c3350593aabb89023e" + triggerID1 = "5" + triggerID2 = "6" + workflowID1 = "15c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0" + workflowExecutionID1 = "95ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce0abbadeed" + owner1 = "0x00000000000000000000000000000000000000aa" + address1 = "0x853d51d5d9935964267a5050aC53aa63ECA39bc5" +) + +func setupHandler(t *testing.T) (*handler, *handlermocks.DON, []gwcommon.TestNode) { + lggr := logger.TestLogger(t) + don := handlermocks.NewDON(t) + + handlerConfig := HandlerConfig{ + MaxAllowedMessageAgeSec: 30, + } + cfgBytes, err := json.Marshal(handlerConfig) + require.NoError(t, err) + donConfig := &config.DONConfig{ + Members: []config.NodeConfig{}, + F: 1, + } + nodes := gwcommon.NewTestNodes(t, 2) + for id, n := range nodes { + donConfig.Members = append(donConfig.Members, config.NodeConfig{ + Name: fmt.Sprintf("node_%d", id), + Address: n.Address, + }) + } + + handler, err := NewWorkflowHandler(json.RawMessage(cfgBytes), donConfig, don, lggr) + require.NoError(t, err) + return handler, don, nodes +} + +func triggerRequest(t *testing.T, privateKey string, topics string, methodName string, timestamp string, payload string) *api.Message { + messageID := "12345" + if methodName == "" { + methodName = MethodWebAPITrigger + } + if timestamp == "" { + timestamp = strconv.FormatInt(time.Now().Unix(), 10) + } + donID := "workflow_don_1" + + key, err := crypto.HexToECDSA(privateKey) + require.NoError(t, err) + if payload == "" { + payload = `{ + "trigger_id": "web-trigger@1.0.0", + "trigger_event_id": "action_1234567890", + "timestamp": ` + timestamp + `, + "topics": ` + topics + `, + "params": { + "bid": "101", + "ask": "102" + } + } + ` + } + payloadJSON := []byte(payload) + msg := &api.Message{ + Body: api.MessageBody{ + MessageId: messageID, + Method: methodName, + DonId: donID, + Payload: json.RawMessage(payloadJSON), + }, + } + err = msg.Sign(key) + require.NoError(t, err) + return msg +} + +func requireNoChanMsg[T any](t *testing.T, ch <-chan T) { + timedOut := false + select { + case <-ch: + case <-time.After(100 * time.Millisecond): + timedOut = true + } + require.True(t, timedOut) +} + +func TestHandlerReceiveHTTPMessageFromClient(t *testing.T) { + handler, don, _ := setupHandler(t) + ctx := testutils.Context(t) + msg := triggerRequest(t, privateKey1, `["daily_price_update"]`, "", "", "") + + t.Run("happy case", func(t *testing.T) { + ch := make(chan handlers.UserCallbackPayload, defaultSendChannelBufferSize) + + // sends to 2 dons + don.On("SendToNode", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + require.Equal(t, msg, args.Get(2)) + }).Return(nil).Once() + don.On("SendToNode", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + require.Equal(t, msg, args.Get(2)) + }).Return(nil).Once() + + err := handler.HandleUserMessage(ctx, msg, ch) + require.NoError(t, err) + requireNoChanMsg(t, ch) + + err = handler.HandleNodeMessage(ctx, msg, "") + require.NoError(t, err) + + resp := <-ch + require.Equal(t, handlers.UserCallbackPayload{Msg: msg, ErrCode: api.NoError, ErrMsg: ""}, resp) + _, open := <-ch + require.Equal(t, open, false) + }) + + t.Run("sad case invalid method", func(t *testing.T) { + invalidMsg := triggerRequest(t, privateKey1, `["daily_price_update"]`, "foo", "", "") + ch := make(chan handlers.UserCallbackPayload, defaultSendChannelBufferSize) + err := handler.HandleUserMessage(ctx, invalidMsg, ch) + require.NoError(t, err) + resp := <-ch + require.Equal(t, handlers.UserCallbackPayload{Msg: invalidMsg, ErrCode: api.HandlerError, ErrMsg: "invalid method foo"}, resp) + _, open := <-ch + require.Equal(t, open, false) + }) + + t.Run("sad case stale message", func(t *testing.T) { + invalidMsg := triggerRequest(t, privateKey1, `["daily_price_update"]`, "", "123456", "") + ch := make(chan handlers.UserCallbackPayload, defaultSendChannelBufferSize) + err := handler.HandleUserMessage(ctx, invalidMsg, ch) + require.NoError(t, err) + resp := <-ch + require.Equal(t, handlers.UserCallbackPayload{Msg: invalidMsg, ErrCode: api.HandlerError, ErrMsg: "stale message"}, resp) + _, open := <-ch + require.Equal(t, open, false) + }) + + t.Run("sad case empty payload", func(t *testing.T) { + invalidMsg := triggerRequest(t, privateKey1, `["daily_price_update"]`, "", "123456", "{}") + ch := make(chan handlers.UserCallbackPayload, defaultSendChannelBufferSize) + err := handler.HandleUserMessage(ctx, invalidMsg, ch) + require.NoError(t, err) + resp := <-ch + require.Equal(t, handlers.UserCallbackPayload{Msg: invalidMsg, ErrCode: api.UserMessageParseError, ErrMsg: "error decoding payload"}, resp) + _, open := <-ch + require.Equal(t, open, false) + }) + + t.Run("sad case invalid payload", func(t *testing.T) { + invalidMsg := triggerRequest(t, privateKey1, `["daily_price_update"]`, "", "123456", `{"foo":"bar"}`) + ch := make(chan handlers.UserCallbackPayload, defaultSendChannelBufferSize) + err := handler.HandleUserMessage(ctx, invalidMsg, ch) + require.NoError(t, err) + resp := <-ch + require.Equal(t, handlers.UserCallbackPayload{Msg: invalidMsg, ErrCode: api.UserMessageParseError, ErrMsg: "error decoding payload"}, resp) + _, open := <-ch + require.Equal(t, open, false) + }) + // TODO: Validate Senders and rate limit chck, pending question in trigger about where senders and rate limits are validated +} diff --git a/core/services/gateway/handlers/webapicapabilities/webapi.go b/core/services/gateway/handlers/webapicapabilities/webapi.go index e300b61d85b..97ba401881b 100644 --- a/core/services/gateway/handlers/webapicapabilities/webapi.go +++ b/core/services/gateway/handlers/webapicapabilities/webapi.go @@ -1,5 +1,9 @@ package webapicapabilities +import ( + "github.com/smartcontractkit/chainlink-common/pkg/values" +) + type TargetRequestPayload struct { URL string `json:"url"` // URL to query, only http and https protocols are supported. Method string `json:"method,omitempty"` // HTTP verb, defaults to GET. @@ -15,3 +19,47 @@ type TargetResponsePayload struct { Headers map[string]string `json:"headers,omitempty"` // HTTP headers Body []byte `json:"body,omitempty"` // HTTP response body } + +// https://gateway-us-1.chain.link/web-trigger +// +// { +// jsonrpc: "2.0", +// id: "...", +// method: "web-trigger", +// params: { +// signature: "...", +// body: { +// don_id: "workflow_123", +// payload: { +// trigger_id: "web-trigger@1.0.0", +// trigger_event_id: "action_1234567890", +// timestamp: 1234567890, +// topics: ["daily_price_update"], +// params: { +// bid: "101", +// ask: "102" +// } +// } +// } +// } +// } +// +// from Web API Trigger Doc, with modifications. +// trigger_id - ID of the trigger corresponding to the capability ID +// trigger_event_id - uniquely identifies generated event (scoped to trigger_id and sender) +// timestamp - timestamp of the event (unix time), needs to be within certain freshness to be processed +// topics - an array of a single topic (string) to be started by this event +// params - key-value pairs for the workflow engine, untranslated. +type TriggerRequestPayload struct { + TriggerID string `json:"trigger_id"` + TriggerEventID string `json:"trigger_event_id"` + Timestamp int64 `json:"timestamp"` + Topics []string `json:"topics"` + Params values.Map `json:"params"` +} + +type TriggerResponsePayload struct { + ErrorMessage string `json:"error_message,omitempty"` + // ERROR, ACCEPTED, PENDING, COMPLETED + Status string `json:"status"` +} diff --git a/core/services/standardcapabilities/delegate.go b/core/services/standardcapabilities/delegate.go index 15c829fbf84..1e27d2ffb33 100644 --- a/core/services/standardcapabilities/delegate.go +++ b/core/services/standardcapabilities/delegate.go @@ -13,7 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/core" gatewayconnector "github.com/smartcontractkit/chainlink/v2/core/capabilities/gateway_connector" - "github.com/smartcontractkit/chainlink/v2/core/capabilities/webapi" + trigger "github.com/smartcontractkit/chainlink/v2/core/capabilities/webapi" webapitarget "github.com/smartcontractkit/chainlink/v2/core/capabilities/webapi/target" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -82,7 +82,7 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) ([]job.Ser return nil, errors.New("gateway connector is required for web API Trigger capability") } connector := d.gatewayConnectorWrapper.GetGatewayConnector() - triggerSrvc, err := webapi.NewTrigger(spec.StandardCapabilitiesSpec.Config, d.registry, connector, log) + triggerSrvc, err := trigger.NewTrigger(spec.StandardCapabilitiesSpec.Config, d.registry, connector, log) if err != nil { return nil, fmt.Errorf("failed to create a Web API Trigger service: %w", err) } From 4b977021ed2150678bbe497afeb1312aa64e62cf Mon Sep 17 00:00:00 2001 From: jinhoonbang Date: Mon, 30 Sep 2024 20:50:53 -0700 Subject: [PATCH 07/28] add gateway handler and http client for outgoing messages (#14536) * implement HTTP target capability and connector handler * self-review * fix linter * more linting fixes * fix build * regenerate mocks * Update core/capabilities/webapi/target/connector_handler.go Co-authored-by: Street <5597260+MStreet3@users.noreply.github.com> * address feedback * add gateway handler for node messages and http client for outgoing messages. pending rate limiting and unit tests. * implement rate limiter and add unit test http client * add http client mock and unit tests * fix failing tests * add tag to changeset * fix linting issue * fix failing race * fix linting --------- Co-authored-by: Street <5597260+MStreet3@users.noreply.github.com> --- .changeset/empty-bees-fix.md | 5 + .mockery.yaml | 1 + .../capabilities/webapi/target/target_test.go | 8 +- core/capabilities/webapi/target/types.go | 2 +- core/scripts/gateway/run_gateway.go | 2 +- core/services/gateway/config/config.go | 4 +- core/services/gateway/delegate.go | 7 +- core/services/gateway/gateway_test.go | 12 +- core/services/gateway/handler_factory.go | 9 +- core/services/gateway/handlers/handler.go | 3 +- .../handlers/webapicapabilities/handler.go | 210 ++++++++++++++---- .../webapicapabilities/handler_test.go | 146 +++++++++++- .../handlers/webapicapabilities/webapi.go | 10 +- .../gateway_integration_test.go | 9 +- core/services/gateway/network/httpclient.go | 88 ++++++++ .../gateway/network/httpclient_test.go | 147 ++++++++++++ .../gateway/network/mocks/http_client.go | 96 ++++++++ 17 files changed, 679 insertions(+), 80 deletions(-) create mode 100644 .changeset/empty-bees-fix.md create mode 100644 core/services/gateway/network/httpclient.go create mode 100644 core/services/gateway/network/httpclient_test.go create mode 100644 core/services/gateway/network/mocks/http_client.go diff --git a/.changeset/empty-bees-fix.md b/.changeset/empty-bees-fix.md new file mode 100644 index 00000000000..e76ee621253 --- /dev/null +++ b/.changeset/empty-bees-fix.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +#wip implement gateway handler that forwards outgoing request from http target capability. introduce gateway http client diff --git a/.mockery.yaml b/.mockery.yaml index b22875e3f9f..709134b05bd 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -212,6 +212,7 @@ packages: HttpServer: HTTPRequestHandler: WebSocketServer: + HTTPClient: github.com/smartcontractkit/chainlink/v2/core/services/job: interfaces: ServiceCtx: diff --git a/core/capabilities/webapi/target/target_test.go b/core/capabilities/webapi/target/target_test.go index 67923ef4c80..a4064c7e7fe 100644 --- a/core/capabilities/webapi/target/target_test.go +++ b/core/capabilities/webapi/target/target_test.go @@ -107,10 +107,10 @@ func gatewayResponse(t *testing.T, msgID string) *api.Message { headers := map[string]string{"Content-Type": "application/json"} body := []byte("response body") responsePayload, err := json.Marshal(webapicapabilities.TargetResponsePayload{ - StatusCode: 200, - Headers: headers, - Body: body, - Success: true, + StatusCode: 200, + Headers: headers, + Body: body, + ExecutionError: false, }) require.NoError(t, err) return &api.Message{ diff --git a/core/capabilities/webapi/target/types.go b/core/capabilities/webapi/target/types.go index 6152d1496b7..63356baa96c 100644 --- a/core/capabilities/webapi/target/types.go +++ b/core/capabilities/webapi/target/types.go @@ -22,7 +22,7 @@ type WorkflowConfig struct { DeliveryMode string `json:"deliveryMode,omitempty"` // DeliveryMode describes how request should be delivered to gateway nodes, defaults to SingleNode. } -// CapabilityConfigConfig is the configuration for the Target capability and handler +// Config is the configuration for the Target capability and handler // TODO: handle retry configurations here CM-472 // Note that workflow executions have their own internal timeouts and retries set by the user // that are separate from this configuration diff --git a/core/scripts/gateway/run_gateway.go b/core/scripts/gateway/run_gateway.go index 2daca5190a5..5dbcd02bf56 100644 --- a/core/scripts/gateway/run_gateway.go +++ b/core/scripts/gateway/run_gateway.go @@ -48,7 +48,7 @@ func main() { lggr, _ := logger.NewLogger() - handlerFactory := gateway.NewHandlerFactory(nil, nil, lggr) + handlerFactory := gateway.NewHandlerFactory(nil, nil, nil, lggr) gw, err := gateway.NewGatewayFromConfig(&cfg, handlerFactory, lggr) if err != nil { fmt.Println("error creating Gateway object:", err) diff --git a/core/services/gateway/config/config.go b/core/services/gateway/config/config.go index a4d94155c8f..02c1b44869f 100644 --- a/core/services/gateway/config/config.go +++ b/core/services/gateway/config/config.go @@ -10,7 +10,9 @@ type GatewayConfig struct { UserServerConfig gw_net.HTTPServerConfig NodeServerConfig gw_net.WebSocketServerConfig ConnectionManagerConfig ConnectionManagerConfig - Dons []DONConfig + // HTTPClientConfig is configuration for outbound HTTP calls to external endpoints + HTTPClientConfig gw_net.HTTPClientConfig + Dons []DONConfig } type ConnectionManagerConfig struct { diff --git a/core/services/gateway/delegate.go b/core/services/gateway/delegate.go index 5a30228db4c..ba059b15a35 100644 --- a/core/services/gateway/delegate.go +++ b/core/services/gateway/delegate.go @@ -12,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" ) @@ -54,7 +55,11 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services if err2 != nil { return nil, errors.Wrap(err2, "unmarshal gateway config") } - handlerFactory := NewHandlerFactory(d.legacyChains, d.ds, d.lggr) + httpClient, err := network.NewHTTPClient(gatewayConfig.HTTPClientConfig, d.lggr) + if err != nil { + return nil, err + } + handlerFactory := NewHandlerFactory(d.legacyChains, d.ds, httpClient, d.lggr) gateway, err := NewGatewayFromConfig(&gatewayConfig, handlerFactory, d.lggr) if err != nil { return nil, err diff --git a/core/services/gateway/gateway_test.go b/core/services/gateway/gateway_test.go index 3218c5428a2..7a5457c788c 100644 --- a/core/services/gateway/gateway_test.go +++ b/core/services/gateway/gateway_test.go @@ -57,7 +57,7 @@ Address = "0x0001020304050607080900010203040506070809" `) lggr := logger.TestLogger(t) - _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, lggr), lggr) + _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, nil, lggr), lggr) require.NoError(t, err) } @@ -75,7 +75,7 @@ HandlerName = "dummy" `) lggr := logger.TestLogger(t) - _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, lggr), lggr) + _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, nil, lggr), lggr) require.Error(t, err) } @@ -89,7 +89,7 @@ HandlerName = "no_such_handler" `) lggr := logger.TestLogger(t) - _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, lggr), lggr) + _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, nil, lggr), lggr) require.Error(t, err) } @@ -103,7 +103,7 @@ SomeOtherField = "abcd" `) lggr := logger.TestLogger(t) - _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, lggr), lggr) + _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, nil, lggr), lggr) require.Error(t, err) } @@ -121,7 +121,7 @@ Address = "0xnot_an_address" `) lggr := logger.TestLogger(t) - _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, lggr), lggr) + _, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, tomlConfig), gateway.NewHandlerFactory(nil, nil, nil, lggr), lggr) require.Error(t, err) } @@ -129,7 +129,7 @@ func TestGateway_CleanStartAndClose(t *testing.T) { t.Parallel() lggr := logger.TestLogger(t) - gateway, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, buildConfig("")), gateway.NewHandlerFactory(nil, nil, lggr), lggr) + gateway, err := gateway.NewGatewayFromConfig(parseTOMLConfig(t, buildConfig("")), gateway.NewHandlerFactory(nil, nil, nil, lggr), lggr) require.NoError(t, err) servicetest.Run(t, gateway) } diff --git a/core/services/gateway/handler_factory.go b/core/services/gateway/handler_factory.go index 92ad48b5395..0c1eeaf676e 100644 --- a/core/services/gateway/handler_factory.go +++ b/core/services/gateway/handler_factory.go @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/functions" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/webapicapabilities" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" ) const ( @@ -23,15 +24,17 @@ type handlerFactory struct { legacyChains legacyevm.LegacyChainContainer ds sqlutil.DataSource lggr logger.Logger + httpClient network.HTTPClient } var _ HandlerFactory = (*handlerFactory)(nil) -func NewHandlerFactory(legacyChains legacyevm.LegacyChainContainer, ds sqlutil.DataSource, lggr logger.Logger) HandlerFactory { +func NewHandlerFactory(legacyChains legacyevm.LegacyChainContainer, ds sqlutil.DataSource, httpClient network.HTTPClient, lggr logger.Logger) HandlerFactory { return &handlerFactory{ legacyChains, ds, lggr, + httpClient, } } @@ -39,10 +42,10 @@ func (hf *handlerFactory) NewHandler(handlerType HandlerType, handlerConfig json switch handlerType { case FunctionsHandlerType: return functions.NewFunctionsHandlerFromConfig(handlerConfig, donConfig, don, hf.legacyChains, hf.ds, hf.lggr) - case WebAPICapabilitiesType: - return webapicapabilities.NewWorkflowHandler(handlerConfig, donConfig, don, hf.lggr) case DummyHandlerType: return handlers.NewDummyHandler(donConfig, don, hf.lggr) + case WebAPICapabilitiesType: + return webapicapabilities.NewHandler(handlerConfig, donConfig, don, hf.httpClient, hf.lggr) default: return nil, fmt.Errorf("unsupported handler type %s", handlerType) } diff --git a/core/services/gateway/handlers/handler.go b/core/services/gateway/handlers/handler.go index 6994488707f..b9fe4234d25 100644 --- a/core/services/gateway/handlers/handler.go +++ b/core/services/gateway/handlers/handler.go @@ -31,7 +31,8 @@ type Handler interface { // 2. waits on callbackCh with a timeout HandleUserMessage(ctx context.Context, msg *api.Message, callbackCh chan<- UserCallbackPayload) error - // Handlers should not make any assumptions about goroutines calling HandleNodeMessage + // Handlers should not make any assumptions about goroutines calling HandleNodeMessage. + // should be non-blocking HandleNodeMessage(ctx context.Context, msg *api.Message, nodeAddr string) error } diff --git a/core/services/gateway/handlers/webapicapabilities/handler.go b/core/services/gateway/handlers/webapicapabilities/handler.go index d6caf067dd0..744bdc17406 100644 --- a/core/services/gateway/handlers/webapicapabilities/handler.go +++ b/core/services/gateway/handlers/webapicapabilities/handler.go @@ -13,6 +13,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" ) const ( @@ -22,17 +24,22 @@ const ( ) type handler struct { - config HandlerConfig - donConfig *config.DONConfig - don handlers.DON - savedCallbacks map[string]*savedCallback - mu sync.Mutex - lggr logger.Logger + config HandlerConfig + don handlers.DON + donConfig *config.DONConfig + savedCallbacks map[string]*savedCallback + mu sync.Mutex + lggr logger.Logger + httpClient network.HTTPClient + nodeRateLimiter *common.RateLimiter + wg sync.WaitGroup } type HandlerConfig struct { - MaxAllowedMessageAgeSec uint + NodeRateLimiter common.RateLimiterConfig `json:"nodeRateLimiter"` + MaxAllowedMessageAgeSec uint `json:"maxAllowedMessageAgeSec"` } + type savedCallback struct { id string callbackCh chan<- handlers.UserCallbackPayload @@ -40,84 +47,193 @@ type savedCallback struct { var _ handlers.Handler = (*handler)(nil) -func NewWorkflowHandler(handlerConfig json.RawMessage, donConfig *config.DONConfig, don handlers.DON, lggr logger.Logger) (*handler, error) { +func NewHandler(handlerConfig json.RawMessage, donConfig *config.DONConfig, don handlers.DON, httpClient network.HTTPClient, lggr logger.Logger) (*handler, error) { var cfg HandlerConfig err := json.Unmarshal(handlerConfig, &cfg) if err != nil { return nil, err } + nodeRateLimiter, err := common.NewRateLimiter(cfg.NodeRateLimiter) + if err != nil { + return nil, err + } return &handler{ - config: cfg, - donConfig: donConfig, - don: don, - savedCallbacks: make(map[string]*savedCallback), - lggr: lggr.Named("WorkflowHandler." + donConfig.DonId), + config: cfg, + don: don, + donConfig: donConfig, + lggr: lggr.Named("WebAPIHandler." + donConfig.DonId), + httpClient: httpClient, + nodeRateLimiter: nodeRateLimiter, + wg: sync.WaitGroup{}, + savedCallbacks: make(map[string]*savedCallback), + }, nil +} + +// sendHTTPMessageToClient is an outgoing message from the gateway to external endpoints +// returns message to be sent back to the capability node +func (h *handler) sendHTTPMessageToClient(ctx context.Context, req network.HTTPRequest, msg *api.Message) (*api.Message, error) { + var payload TargetResponsePayload + resp, err := h.httpClient.Send(ctx, req) + if err != nil { + return nil, err + } + payload = TargetResponsePayload{ + ExecutionError: false, + StatusCode: resp.StatusCode, + Headers: resp.Headers, + Body: resp.Body, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + return &api.Message{ + Body: api.MessageBody{ + MessageId: msg.Body.MessageId, + Method: msg.Body.Method, + DonId: msg.Body.DonId, + Payload: payloadBytes, + }, }, nil } -func (d *handler) HandleUserMessage(ctx context.Context, msg *api.Message, callbackCh chan<- handlers.UserCallbackPayload) error { - d.mu.Lock() - d.savedCallbacks[msg.Body.MessageId] = &savedCallback{msg.Body.MessageId, callbackCh} - don := d.don - d.mu.Unlock() +func (h *handler) handleWebAPITargetMessage(ctx context.Context, msg *api.Message, nodeAddr string) error { + h.lggr.Debugw("handling web api target message", "messageId", msg.Body.MessageId, "nodeAddr", nodeAddr) + if !h.nodeRateLimiter.Allow(nodeAddr) { + return fmt.Errorf("rate limit exceeded for node %s", nodeAddr) + } + var targetPayload TargetRequestPayload + err := json.Unmarshal(msg.Body.Payload, &targetPayload) + if err != nil { + return err + } + // send message to target + timeout := time.Duration(targetPayload.TimeoutMs) * time.Millisecond + req := network.HTTPRequest{ + Method: targetPayload.Method, + URL: targetPayload.URL, + Headers: targetPayload.Headers, + Body: targetPayload.Body, + Timeout: timeout, + } + // this handle method must be non-blocking + // send response to node (target capability) async + // if there is a non-HTTP error (e.g. malformed request), send payload with success set to false and error messages + h.wg.Add(1) + go func() { + defer h.wg.Done() + // not cancelled when parent is cancelled to ensure the goroutine can finish + newCtx := context.WithoutCancel(ctx) + newCtx, cancel := context.WithTimeout(newCtx, timeout) + defer cancel() + l := h.lggr.With("url", targetPayload.URL, "messageId", msg.Body.MessageId, "method", targetPayload.Method) + respMsg, err := h.sendHTTPMessageToClient(newCtx, req, msg) + if err != nil { + l.Errorw("error while sending HTTP request to external endpoint", "err", err) + payload := TargetResponsePayload{ + ExecutionError: true, + ErrorMessage: err.Error(), + } + payloadBytes, err2 := json.Marshal(payload) + if err2 != nil { + // should not happen + l.Errorw("error while marshalling payload", "err", err2) + return + } + respMsg = &api.Message{ + Body: api.MessageBody{ + MessageId: msg.Body.MessageId, + Method: msg.Body.Method, + DonId: msg.Body.DonId, + Payload: payloadBytes, + }, + } + } + err = h.don.SendToNode(newCtx, nodeAddr, respMsg) + if err != nil { + l.Errorw("failed to send to node", "err", err, "to", nodeAddr) + return + } + }() + return nil +} + +func (h *handler) handleWebAPITriggerMessage(ctx context.Context, msg *api.Message, nodeAddr string) error { + h.mu.Lock() + savedCb, found := h.savedCallbacks[msg.Body.MessageId] + delete(h.savedCallbacks, msg.Body.MessageId) + h.mu.Unlock() + + if found { + // Send first response from a node back to the user, ignore any other ones. + // TODO: in practice, we should wait for at least 2F+1 nodes to respond and then return an aggregated response + // back to the user. + savedCb.callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.NoError, ErrMsg: ""} + close(savedCb.callbackCh) + } + return nil +} + +func (h *handler) HandleNodeMessage(ctx context.Context, msg *api.Message, nodeAddr string) error { + switch msg.Body.Method { + case MethodWebAPITrigger: + return h.handleWebAPITriggerMessage(ctx, msg, nodeAddr) + case MethodWebAPITarget: + return h.handleWebAPITargetMessage(ctx, msg, nodeAddr) + default: + return fmt.Errorf("unsupported method: %s", msg.Body.Method) + } +} + +func (h *handler) Start(context.Context) error { + return nil +} + +func (h *handler) Close() error { + h.wg.Wait() + return nil +} + +func (h *handler) HandleUserMessage(ctx context.Context, msg *api.Message, callbackCh chan<- handlers.UserCallbackPayload) error { + h.mu.Lock() + h.savedCallbacks[msg.Body.MessageId] = &savedCallback{msg.Body.MessageId, callbackCh} + don := h.don + h.mu.Unlock() body := msg.Body var payload TriggerRequestPayload err := json.Unmarshal(body.Payload, &payload) if err != nil { - d.lggr.Errorw("error decoding payload", "err", err) + h.lggr.Errorw("error decoding payload", "err", err) callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.UserMessageParseError, ErrMsg: fmt.Sprintf("error decoding payload %s", err.Error())} close(callbackCh) return nil } if payload.Timestamp == 0 { - d.lggr.Errorw("error decoding payload") + h.lggr.Errorw("error decoding payload") callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.UserMessageParseError, ErrMsg: "error decoding payload"} close(callbackCh) return nil } - if uint(time.Now().Unix())-d.config.MaxAllowedMessageAgeSec > uint(payload.Timestamp) { + if uint(time.Now().Unix())-h.config.MaxAllowedMessageAgeSec > uint(payload.Timestamp) { callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.HandlerError, ErrMsg: "stale message"} close(callbackCh) return nil } // TODO: apply allowlist and rate-limiting here if msg.Body.Method != MethodWebAPITrigger { - d.lggr.Errorw("unsupported method", "method", body.Method) + h.lggr.Errorw("unsupported method", "method", body.Method) callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.HandlerError, ErrMsg: fmt.Sprintf("invalid method %s", msg.Body.Method)} close(callbackCh) return nil } // Send to all nodes. - for _, member := range d.donConfig.Members { + for _, member := range h.donConfig.Members { err = multierr.Combine(err, don.SendToNode(ctx, member.Address, msg)) } return err } - -func (d *handler) HandleNodeMessage(ctx context.Context, msg *api.Message, _ string) error { - d.mu.Lock() - savedCb, found := d.savedCallbacks[msg.Body.MessageId] - delete(d.savedCallbacks, msg.Body.MessageId) - d.mu.Unlock() - - if found { - // Send first response from a node back to the user, ignore any other ones. - // TODO: in practice, we should wait for at least 2F+1 nodes to respond and then return an aggregated response - // back to the user. - savedCb.callbackCh <- handlers.UserCallbackPayload{Msg: msg, ErrCode: api.NoError, ErrMsg: ""} - close(savedCb.callbackCh) - } - return nil -} - -func (d *handler) Start(context.Context) error { - return nil -} - -func (d *handler) Close() error { - return nil -} diff --git a/core/services/gateway/handlers/webapicapabilities/handler_test.go b/core/services/gateway/handlers/webapicapabilities/handler_test.go index ef278e40ffd..e631111ff1d 100644 --- a/core/services/gateway/handlers/webapicapabilities/handler_test.go +++ b/core/services/gateway/handlers/webapicapabilities/handler_test.go @@ -3,23 +3,28 @@ package webapicapabilities import ( "encoding/json" "fmt" - "strconv" "testing" "time" - "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" + "strconv" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" gwcommon "github.com/smartcontractkit/chainlink/v2/core/services/gateway/common" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers" - + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" handlermocks "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network/mocks" ) const ( @@ -34,13 +39,21 @@ const ( address1 = "0x853d51d5d9935964267a5050aC53aa63ECA39bc5" ) -func setupHandler(t *testing.T) (*handler, *handlermocks.DON, []gwcommon.TestNode) { +func setupHandler(t *testing.T) (*handler, *mocks.HTTPClient, *handlermocks.DON, []gwcommon.TestNode) { lggr := logger.TestLogger(t) + httpClient := mocks.NewHTTPClient(t) don := handlermocks.NewDON(t) - + nodeRateLimiterConfig := common.RateLimiterConfig{ + GlobalRPS: 100.0, + GlobalBurst: 100, + PerSenderRPS: 100.0, + PerSenderBurst: 100, + } handlerConfig := HandlerConfig{ + NodeRateLimiter: nodeRateLimiterConfig, MaxAllowedMessageAgeSec: 30, } + cfgBytes, err := json.Marshal(handlerConfig) require.NoError(t, err) donConfig := &config.DONConfig{ @@ -54,10 +67,125 @@ func setupHandler(t *testing.T) (*handler, *handlermocks.DON, []gwcommon.TestNod Address: n.Address, }) } + handler, err := NewHandler(json.RawMessage(cfgBytes), donConfig, don, httpClient, lggr) + require.NoError(t, err) + return handler, httpClient, don, nodes +} - handler, err := NewWorkflowHandler(json.RawMessage(cfgBytes), donConfig, don, lggr) +func TestHandler_SendHTTPMessageToClient(t *testing.T) { + handler, httpClient, don, nodes := setupHandler(t) + ctx := testutils.Context(t) + nodeAddr := nodes[0].Address + payload := TargetRequestPayload{ + Method: "GET", + URL: "http://example.com", + Headers: map[string]string{}, + Body: nil, + TimeoutMs: 2000, + } + payloadBytes, err := json.Marshal(payload) require.NoError(t, err) - return handler, don, nodes + msg := &api.Message{ + Body: api.MessageBody{ + MessageId: "123", + Method: MethodWebAPITarget, + DonId: "testDonId", + Payload: json.RawMessage(payloadBytes), + }, + } + + t.Run("happy case", func(t *testing.T) { + httpClient.EXPECT().Send(mock.Anything, mock.Anything).Return(&network.HTTPResponse{ + StatusCode: 200, + Headers: map[string]string{}, + Body: []byte("response body"), + }, nil).Once() + + don.EXPECT().SendToNode(mock.Anything, nodes[0].Address, mock.MatchedBy(func(m *api.Message) bool { + var payload TargetResponsePayload + err2 := json.Unmarshal(m.Body.Payload, &payload) + if err2 != nil { + return false + } + return "123" == m.Body.MessageId && + MethodWebAPITarget == m.Body.Method && + "testDonId" == m.Body.DonId && + 200 == payload.StatusCode && + 0 == len(payload.Headers) && + string(payload.Body) == "response body" && + !payload.ExecutionError + })).Return(nil).Once() + + err = handler.HandleNodeMessage(ctx, msg, nodeAddr) + require.NoError(t, err) + + require.Eventually(t, func() bool { + // ensure all goroutines close + err2 := handler.Close() + require.NoError(t, err2) + return httpClient.AssertExpectations(t) && don.AssertExpectations(t) + }, tests.WaitTimeout(t), 100*time.Millisecond) + }) + + t.Run("http client non-HTTP error", func(t *testing.T) { + httpClient.EXPECT().Send(mock.Anything, mock.Anything).Return(&network.HTTPResponse{ + StatusCode: 404, + Headers: map[string]string{}, + Body: []byte("access denied"), + }, nil).Once() + + don.EXPECT().SendToNode(mock.Anything, nodes[0].Address, mock.MatchedBy(func(m *api.Message) bool { + var payload TargetResponsePayload + err2 := json.Unmarshal(m.Body.Payload, &payload) + if err2 != nil { + return false + } + return "123" == m.Body.MessageId && + MethodWebAPITarget == m.Body.Method && + "testDonId" == m.Body.DonId && + 404 == payload.StatusCode && + string(payload.Body) == "access denied" && + 0 == len(payload.Headers) && + !payload.ExecutionError + })).Return(nil).Once() + + err = handler.HandleNodeMessage(ctx, msg, nodeAddr) + require.NoError(t, err) + + require.Eventually(t, func() bool { + // // ensure all goroutines close + err2 := handler.Close() + require.NoError(t, err2) + return httpClient.AssertExpectations(t) && don.AssertExpectations(t) + }, tests.WaitTimeout(t), 100*time.Millisecond) + }) + + t.Run("http client non-HTTP error", func(t *testing.T) { + httpClient.EXPECT().Send(mock.Anything, mock.Anything).Return(nil, fmt.Errorf("error while marshalling")).Once() + + don.EXPECT().SendToNode(mock.Anything, nodes[0].Address, mock.MatchedBy(func(m *api.Message) bool { + var payload TargetResponsePayload + err2 := json.Unmarshal(m.Body.Payload, &payload) + if err2 != nil { + return false + } + return "123" == m.Body.MessageId && + MethodWebAPITarget == m.Body.Method && + "testDonId" == m.Body.DonId && + payload.ExecutionError && + "error while marshalling" == payload.ErrorMessage + })).Return(nil).Once() + + err = handler.HandleNodeMessage(ctx, msg, nodeAddr) + require.NoError(t, err) + + require.Eventually(t, func() bool { + // // ensure all goroutines close + err2 := handler.Close() + require.NoError(t, err2) + return httpClient.AssertExpectations(t) && don.AssertExpectations(t) + }, tests.WaitTimeout(t), 100*time.Millisecond) + }) } func triggerRequest(t *testing.T, privateKey string, topics string, methodName string, timestamp string, payload string) *api.Message { @@ -110,7 +238,7 @@ func requireNoChanMsg[T any](t *testing.T, ch <-chan T) { } func TestHandlerReceiveHTTPMessageFromClient(t *testing.T) { - handler, don, _ := setupHandler(t) + handler, _, don, _ := setupHandler(t) ctx := testutils.Context(t) msg := triggerRequest(t, privateKey1, `["daily_price_update"]`, "", "", "") diff --git a/core/services/gateway/handlers/webapicapabilities/webapi.go b/core/services/gateway/handlers/webapicapabilities/webapi.go index 97ba401881b..25f3bca6c1d 100644 --- a/core/services/gateway/handlers/webapicapabilities/webapi.go +++ b/core/services/gateway/handlers/webapicapabilities/webapi.go @@ -13,11 +13,11 @@ type TargetRequestPayload struct { } type TargetResponsePayload struct { - Success bool `json:"success"` // true if HTTP request was successful - ErrorMessage string `json:"error_message,omitempty"` // error message in case of failure - StatusCode uint8 `json:"statusCode"` // HTTP status code - Headers map[string]string `json:"headers,omitempty"` // HTTP headers - Body []byte `json:"body,omitempty"` // HTTP response body + ExecutionError bool `json:"executionError"` // true if there were non-HTTP errors. false if HTTP request was sent regardless of status (2xx, 4xx, 5xx) + ErrorMessage string `json:"errorMessage,omitempty"` // error message in case of failure + StatusCode int `json:"statusCode,omitempty"` // HTTP status code + Headers map[string]string `json:"headers,omitempty"` // HTTP headers + Body []byte `json:"body,omitempty"` // HTTP response body } // https://gateway-us-1.chain.link/web-trigger diff --git a/core/services/gateway/integration_tests/gateway_integration_test.go b/core/services/gateway/integration_tests/gateway_integration_test.go index 59418819b61..0ddf47bec04 100644 --- a/core/services/gateway/integration_tests/gateway_integration_test.go +++ b/core/services/gateway/integration_tests/gateway_integration_test.go @@ -10,6 +10,7 @@ import ( "strings" "sync/atomic" "testing" + "time" "github.com/jonboulle/clockwork" "github.com/onsi/gomega" @@ -24,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/gateway/common" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/config" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" ) const gatewayConfigTemplate = ` @@ -143,7 +145,12 @@ func TestIntegration_Gateway_NoFullNodes_BasicConnectionAndMessage(t *testing.T) // Launch Gateway lggr := logger.TestLogger(t) gatewayConfig := fmt.Sprintf(gatewayConfigTemplate, nodeKeys.Address) - gateway, err := gateway.NewGatewayFromConfig(parseGatewayConfig(t, gatewayConfig), gateway.NewHandlerFactory(nil, nil, lggr), lggr) + c, err := network.NewHTTPClient(network.HTTPClientConfig{ + DefaultTimeout: 5 * time.Second, + MaxResponseBytes: 1000, + }, lggr) + require.NoError(t, err) + gateway, err := gateway.NewGatewayFromConfig(parseGatewayConfig(t, gatewayConfig), gateway.NewHandlerFactory(nil, nil, c, lggr), lggr) require.NoError(t, err) servicetest.Run(t, gateway) userPort, nodePort := gateway.GetUserPort(), gateway.GetNodePort() diff --git a/core/services/gateway/network/httpclient.go b/core/services/gateway/network/httpclient.go new file mode 100644 index 00000000000..4aecaaed3cd --- /dev/null +++ b/core/services/gateway/network/httpclient.go @@ -0,0 +1,88 @@ +package network + +import ( + "bytes" + "context" + "io" + "net/http" + "strings" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" +) + +// HTTPClient interfaces defines a method to send HTTP requests +type HTTPClient interface { + Send(ctx context.Context, req HTTPRequest) (*HTTPResponse, error) +} + +type HTTPClientConfig struct { + MaxResponseBytes uint32 + DefaultTimeout time.Duration +} + +type HTTPRequest struct { + Method string + URL string + Headers map[string]string + Body []byte + Timeout time.Duration +} +type HTTPResponse struct { + StatusCode int // HTTP status code + Headers map[string]string // HTTP headers + Body []byte // HTTP response body +} + +type httpClient struct { + client *http.Client + config HTTPClientConfig + lggr logger.Logger +} + +// NewHTTPClient creates a new NewHTTPClient +// As of now, the client does not support TLS configuration but may be extended in the future +func NewHTTPClient(config HTTPClientConfig, lggr logger.Logger) (HTTPClient, error) { + return &httpClient{ + config: config, + client: &http.Client{ + Timeout: config.DefaultTimeout, + Transport: http.DefaultTransport, + }, + lggr: lggr, + }, nil +} + +func (c *httpClient) Send(ctx context.Context, req HTTPRequest) (*HTTPResponse, error) { + timeoutCtx, cancel := context.WithTimeout(ctx, req.Timeout) + defer cancel() + r, err := http.NewRequestWithContext(timeoutCtx, req.Method, req.URL, bytes.NewBuffer(req.Body)) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + reader := http.MaxBytesReader(nil, resp.Body, int64(c.config.MaxResponseBytes)) + body, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + headers := make(map[string]string) + for k, v := range resp.Header { + // header values are usually an array of size 1 + // joining them to a single string in case array size is greater than 1 + headers[k] = strings.Join(v, ",") + } + c.lggr.Debugw("received HTTP response", "statusCode", resp.StatusCode, "body", string(body), "url", req.URL, "headers", headers) + + return &HTTPResponse{ + Headers: headers, + StatusCode: resp.StatusCode, + Body: body, + }, nil +} diff --git a/core/services/gateway/network/httpclient_test.go b/core/services/gateway/network/httpclient_test.go new file mode 100644 index 00000000000..2f4cc448ef5 --- /dev/null +++ b/core/services/gateway/network/httpclient_test.go @@ -0,0 +1,147 @@ +package network_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" +) + +func TestHTTPClient_Send(t *testing.T) { + t.Parallel() + + // Setup the test environment + lggr := logger.Test(t) + config := network.HTTPClientConfig{ + MaxResponseBytes: 1024, + DefaultTimeout: 5 * time.Second, + } + client, err := network.NewHTTPClient(config, lggr) + require.NoError(t, err) + + // Define test cases + tests := []struct { + name string + setupServer func() *httptest.Server + request network.HTTPRequest + expectedError error + expectedResp *network.HTTPResponse + }{ + { + name: "successful request", + setupServer: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err2 := w.Write([]byte("success")) + require.NoError(t, err2) + })) + }, + request: network.HTTPRequest{ + Method: "GET", + URL: "/", + Headers: map[string]string{}, + Body: nil, + Timeout: 2 * time.Second, + }, + expectedError: nil, + expectedResp: &network.HTTPResponse{ + StatusCode: http.StatusOK, + Headers: map[string]string{"Content-Length": "7"}, + Body: []byte("success"), + }, + }, + { + name: "request timeout", + setupServer: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(10 * time.Second) + w.WriteHeader(http.StatusOK) + _, err2 := w.Write([]byte("success")) + require.NoError(t, err2) + })) + }, + request: network.HTTPRequest{ + Method: "GET", + URL: "/", + Headers: map[string]string{}, + Body: nil, + Timeout: 1 * time.Second, + }, + expectedError: context.DeadlineExceeded, + expectedResp: nil, + }, + { + name: "server error", + setupServer: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, err2 := w.Write([]byte("error")) + require.NoError(t, err2) + })) + }, + request: network.HTTPRequest{ + Method: "GET", + URL: "/", + Headers: map[string]string{}, + Body: nil, + Timeout: 2 * time.Second, + }, + expectedError: nil, + expectedResp: &network.HTTPResponse{ + StatusCode: http.StatusInternalServerError, + Headers: map[string]string{"Content-Length": "5"}, + Body: []byte("error"), + }, + }, + { + name: "response too long", + setupServer: func() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err2 := w.Write(make([]byte, 2048)) + require.NoError(t, err2) + })) + }, + request: network.HTTPRequest{ + Method: "GET", + URL: "/", + Headers: map[string]string{}, + Body: nil, + Timeout: 2 * time.Second, + }, + expectedError: &http.MaxBytesError{}, + expectedResp: nil, + }, + } + + // Execute test cases + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := tt.setupServer() + defer server.Close() + + tt.request.URL = server.URL + tt.request.URL + + resp, err := client.Send(context.Background(), tt.request) + if tt.expectedError != nil { + require.Error(t, err) + require.ErrorContains(t, err, tt.expectedError.Error()) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedResp.StatusCode, resp.StatusCode) + for k, v := range tt.expectedResp.Headers { + value, ok := resp.Headers[k] + require.True(t, ok) + require.Equal(t, v, value) + } + require.Equal(t, tt.expectedResp.Body, resp.Body) + } + }) + } +} diff --git a/core/services/gateway/network/mocks/http_client.go b/core/services/gateway/network/mocks/http_client.go new file mode 100644 index 00000000000..8b5bff2cccf --- /dev/null +++ b/core/services/gateway/network/mocks/http_client.go @@ -0,0 +1,96 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + network "github.com/smartcontractkit/chainlink/v2/core/services/gateway/network" + mock "github.com/stretchr/testify/mock" +) + +// HTTPClient is an autogenerated mock type for the HTTPClient type +type HTTPClient struct { + mock.Mock +} + +type HTTPClient_Expecter struct { + mock *mock.Mock +} + +func (_m *HTTPClient) EXPECT() *HTTPClient_Expecter { + return &HTTPClient_Expecter{mock: &_m.Mock} +} + +// Send provides a mock function with given fields: ctx, req +func (_m *HTTPClient) Send(ctx context.Context, req network.HTTPRequest) (*network.HTTPResponse, error) { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for Send") + } + + var r0 *network.HTTPResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, network.HTTPRequest) (*network.HTTPResponse, error)); ok { + return rf(ctx, req) + } + if rf, ok := ret.Get(0).(func(context.Context, network.HTTPRequest) *network.HTTPResponse); ok { + r0 = rf(ctx, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*network.HTTPResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, network.HTTPRequest) error); ok { + r1 = rf(ctx, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HTTPClient_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send' +type HTTPClient_Send_Call struct { + *mock.Call +} + +// Send is a helper method to define mock.On call +// - ctx context.Context +// - req network.HTTPRequest +func (_e *HTTPClient_Expecter) Send(ctx interface{}, req interface{}) *HTTPClient_Send_Call { + return &HTTPClient_Send_Call{Call: _e.mock.On("Send", ctx, req)} +} + +func (_c *HTTPClient_Send_Call) Run(run func(ctx context.Context, req network.HTTPRequest)) *HTTPClient_Send_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(network.HTTPRequest)) + }) + return _c +} + +func (_c *HTTPClient_Send_Call) Return(_a0 *network.HTTPResponse, _a1 error) *HTTPClient_Send_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *HTTPClient_Send_Call) RunAndReturn(run func(context.Context, network.HTTPRequest) (*network.HTTPResponse, error)) *HTTPClient_Send_Call { + _c.Call.Return(run) + return _c +} + +// NewHTTPClient creates a new instance of HTTPClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewHTTPClient(t interface { + mock.TestingT + Cleanup(func()) +}) *HTTPClient { + mock := &HTTPClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From e96210b52af0cffab059281d7e625aa54a3305ae Mon Sep 17 00:00:00 2001 From: Domino Valdano Date: Mon, 30 Sep 2024 22:54:11 -0700 Subject: [PATCH 08/28] [BCFR-900] Add id column as PRIMARY KEY for evm.logs & evm.log_poller_blocks (#14451) * Add id column as PRIMARY KEY for evm.logs & evm.log_poller_blocks Also: - Add UNIQUE INDEXes to replace previous primary keys (still necessary, both for optimizing queries and for enforcing uniqueness constraints) - Replace all SELECT *'s with helper functions for selecting all columns - Refactor nestedBlockQuery into withConfs, and make a bit more use of it * Clean up db indexes Some of the columns in these indexes (such as created_at) are no longer used. Others were not optimized for the queries we need. * Fix 2 unrelated bugs I noticed * Update ExpiredLogs query to use id column * Update test for fromBlock >= :block_number Previously it was using fromBlock > :block_number which is inconsistent with the other fromBlocks in queries * Increase staggering of initial pruning runs * Update go version 1.22.5 -> 1.22.7 * Update DeleteBlocksBefore query to use block_number index instead of LIMIT * Split off SelectUnmatchedLogs from DeleteExpiredLogs * Rename Id -> ID for linter * Add helper function for tickers * Stagger initial unmatched logs prune * Reorganize sql migration script, adding comments to clarify Also, adds a missing CREATE INDEX line that was left out of the Goose Down section: CREATE INDEX idx_evm_log_poller_blocks_order_by_block ON evm_log_poller_blocks (evm_chain_id, block_number DESC); And removes an extraneous DROP line that was in the Goose Up section: DROP INDEX IF EXISTS evm.idx_logs_chain_address_event_block_logindex; * Remove log_index from idx_logs_chain_block_logindex to save some disk space * add WHERE evm_chain_id and handle sql.ErrNoRows * Fix lint --- .tool-versions | 2 +- core/chains/evm/logpoller/log_poller.go | 57 ++- core/chains/evm/logpoller/observability.go | 12 + .../evm/logpoller/observability_test.go | 4 +- core/chains/evm/logpoller/orm.go | 484 ++++++++++-------- core/chains/evm/logpoller/orm_test.go | 48 +- core/chains/evm/logpoller/parser.go | 7 +- core/chains/evm/logpoller/parser_test.go | 125 +++-- core/scripts/go.mod | 2 +- .../0254_log_poller_primary_keys.sql | 42 ++ dashboard-lib/go.mod | 2 +- go.mod | 2 +- integration-tests/go.mod | 2 +- integration-tests/load/go.mod | 2 +- 14 files changed, 482 insertions(+), 309 deletions(-) create mode 100644 core/store/migrate/migrations/0254_log_poller_primary_keys.sql diff --git a/.tool-versions b/.tool-versions index 6cd01e3f913..345e10e2afa 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,4 +1,4 @@ -golang 1.22.5 +golang 1.22.7 mockery 2.43.2 nodejs 20.13.1 pnpm 9.4.0 diff --git a/core/chains/evm/logpoller/log_poller.go b/core/chains/evm/logpoller/log_poller.go index dd7e0c5242b..b19e3f53c39 100644 --- a/core/chains/evm/logpoller/log_poller.go +++ b/core/chains/evm/logpoller/log_poller.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "math/big" + "math/rand" "sort" "strings" "sync" @@ -24,8 +25,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/timeutil" "github.com/smartcontractkit/chainlink-common/pkg/types/query" - "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink-common/pkg/utils/mathutil" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" @@ -558,6 +559,15 @@ func (lp *logPoller) loadFilters(ctx context.Context) error { return nil } +// tickStaggeredDelay chooses a uniformly random amount of time to delay between minDelay and minDelay + period +func tickStaggeredDelay(minDelay time.Duration, period time.Duration) <-chan time.Time { + return time.After(minDelay + timeutil.JitterPct(1.0).Apply(period/2)) +} + +func tickWithDefaultJitter(interval time.Duration) <-chan time.Time { + return time.After(services.DefaultJitter.Apply(interval)) +} + func (lp *logPoller) run() { defer lp.wg.Done() ctx, cancel := lp.stopCh.NewCtx() @@ -635,31 +645,52 @@ func (lp *logPoller) backgroundWorkerRun() { ctx, cancel := lp.stopCh.NewCtx() defer cancel() + blockPruneShortInterval := lp.pollPeriod * 100 + blockPruneInterval := blockPruneShortInterval * 10 + logPruneShortInterval := lp.pollPeriod * 241 // no common factors with 100 + logPruneInterval := logPruneShortInterval * 10 + // Avoid putting too much pressure on the database by staggering the pruning of old blocks and logs. // Usually, node after restart will have some work to boot the plugins and other services. - // Deferring first prune by minutes reduces risk of putting too much pressure on the database. - blockPruneTick := time.After(5 * time.Minute) - logPruneTick := time.After(10 * time.Minute) + // Deferring first prune by at least 5 mins reduces risk of putting too much pressure on the database. + blockPruneTick := tickStaggeredDelay(5*time.Minute, blockPruneInterval) + logPruneTick := tickStaggeredDelay(5*time.Minute, logPruneInterval) + + // Start initial prune of unmatched logs after 5-15 successful expired log prunes, so that not all chains start + // around the same time. After that, every 20 successful expired log prunes. + successfulExpiredLogPrunes := 5 + rand.Intn(10) //nolint:gosec for { select { case <-ctx.Done(): return case <-blockPruneTick: - blockPruneTick = time.After(utils.WithJitter(lp.pollPeriod * 1000)) + blockPruneTick = tickWithDefaultJitter(blockPruneInterval) if allRemoved, err := lp.PruneOldBlocks(ctx); err != nil { lp.lggr.Errorw("Unable to prune old blocks", "err", err) } else if !allRemoved { // Tick faster when cleanup can't keep up with the pace of new blocks - blockPruneTick = time.After(utils.WithJitter(lp.pollPeriod * 100)) + blockPruneTick = tickWithDefaultJitter(blockPruneShortInterval) } case <-logPruneTick: - logPruneTick = time.After(utils.WithJitter(lp.pollPeriod * 2401)) // = 7^5 avoids common factors with 1000 + logPruneTick = tickWithDefaultJitter(logPruneInterval) if allRemoved, err := lp.PruneExpiredLogs(ctx); err != nil { lp.lggr.Errorw("Unable to prune expired logs", "err", err) } else if !allRemoved { // Tick faster when cleanup can't keep up with the pace of new logs - logPruneTick = time.After(utils.WithJitter(lp.pollPeriod * 241)) + logPruneTick = tickWithDefaultJitter(logPruneShortInterval) + } else if successfulExpiredLogPrunes == 20 { + // Only prune unmatched logs if we've successfully pruned all expired logs at least 20 times + // since the last time unmatched logs were pruned + if allRemoved, err := lp.PruneUnmatchedLogs(ctx); err != nil { + lp.lggr.Errorw("Unable to prune unmatched logs", "err", err) + } else if !allRemoved { + logPruneTick = tickWithDefaultJitter(logPruneShortInterval) + } else { + successfulExpiredLogPrunes = 0 + } + } else { + successfulExpiredLogPrunes++ } } } @@ -1097,6 +1128,16 @@ func (lp *logPoller) PruneExpiredLogs(ctx context.Context) (bool, error) { return lp.logPrunePageSize == 0 || rowsRemoved < lp.logPrunePageSize, err } +func (lp *logPoller) PruneUnmatchedLogs(ctx context.Context) (bool, error) { + ids, err := lp.orm.SelectUnmatchedLogIDs(ctx, lp.logPrunePageSize) + if err != nil { + return false, err + } + rowsRemoved, err := lp.orm.DeleteLogsByRowID(ctx, ids) + + return lp.logPrunePageSize == 0 || rowsRemoved < lp.logPrunePageSize, err +} + // Logs returns logs matching topics and address (exactly) in the given block range, // which are canonical at time of query. func (lp *logPoller) Logs(ctx context.Context, start, end int64, eventSig common.Hash, address common.Address) ([]Log, error) { diff --git a/core/chains/evm/logpoller/observability.go b/core/chains/evm/logpoller/observability.go index e0ed0cc4786..59b93fffdaf 100644 --- a/core/chains/evm/logpoller/observability.go +++ b/core/chains/evm/logpoller/observability.go @@ -136,6 +136,18 @@ func (o *ObservedORM) DeleteLogsAndBlocksAfter(ctx context.Context, start int64) }) } +func (o *ObservedORM) DeleteLogsByRowID(ctx context.Context, rowIDs []uint64) (int64, error) { + return withObservedExecAndRowsAffected(o, "DeleteLogsByRowID", del, func() (int64, error) { + return o.ORM.DeleteLogsByRowID(ctx, rowIDs) + }) +} + +func (o *ObservedORM) SelectUnmatchedLogIDs(ctx context.Context, limit int64) (ids []uint64, err error) { + return withObservedQueryAndResults[uint64](o, "SelectUnmatchedLogIDs", func() ([]uint64, error) { + return o.ORM.SelectUnmatchedLogIDs(ctx, limit) + }) +} + func (o *ObservedORM) DeleteExpiredLogs(ctx context.Context, limit int64) (int64, error) { return withObservedExecAndRowsAffected(o, "DeleteExpiredLogs", del, func() (int64, error) { return o.ORM.DeleteExpiredLogs(ctx, limit) diff --git a/core/chains/evm/logpoller/observability_test.go b/core/chains/evm/logpoller/observability_test.go index 4ea7adceab0..27b8a3c3225 100644 --- a/core/chains/evm/logpoller/observability_test.go +++ b/core/chains/evm/logpoller/observability_test.go @@ -120,8 +120,8 @@ func TestCountersAreProperlyPopulatedForWrites(t *testing.T) { rowsAffected, err := orm.DeleteExpiredLogs(ctx, 3) require.NoError(t, err) - require.Equal(t, int64(3), rowsAffected) - assert.Equal(t, 3, counterFromGaugeByLabels(orm.datasetSize, "420", "DeleteExpiredLogs", "delete")) + require.Equal(t, int64(0), rowsAffected) + assert.Equal(t, 0, counterFromGaugeByLabels(orm.datasetSize, "420", "DeleteExpiredLogs", "delete")) rowsAffected, err = orm.DeleteBlocksBefore(ctx, 30, 0) require.NoError(t, err) diff --git a/core/chains/evm/logpoller/orm.go b/core/chains/evm/logpoller/orm.go index e40fb80f108..30cd19e0447 100644 --- a/core/chains/evm/logpoller/orm.go +++ b/core/chains/evm/logpoller/orm.go @@ -3,6 +3,7 @@ package logpoller import ( "context" "database/sql" + "errors" "fmt" "math/big" "strings" @@ -33,9 +34,11 @@ type ORM interface { LoadFilters(ctx context.Context) (map[string]Filter, error) DeleteFilter(ctx context.Context, name string) error + DeleteLogsByRowID(ctx context.Context, rowIDs []uint64) (int64, error) InsertBlock(ctx context.Context, blockHash common.Hash, blockNumber int64, blockTimestamp time.Time, finalizedBlock int64) error DeleteBlocksBefore(ctx context.Context, end int64, limit int64) (int64, error) DeleteLogsAndBlocksAfter(ctx context.Context, start int64) error + SelectUnmatchedLogIDs(ctx context.Context, limit int64) (ids []uint64, err error) DeleteExpiredLogs(ctx context.Context, limit int64) (int64, error) GetBlocksRange(ctx context.Context, start int64, end int64) ([]LogPollerBlock, error) @@ -102,9 +105,9 @@ func (o *DSORM) InsertBlock(ctx context.Context, blockHash common.Hash, blockNum if err != nil { return err } - query := `INSERT INTO evm.log_poller_blocks - (evm_chain_id, block_hash, block_number, block_timestamp, finalized_block_number, created_at) - VALUES (:evm_chain_id, :block_hash, :block_number, :block_timestamp, :finalized_block_number, NOW()) + query := `INSERT INTO evm.log_poller_blocks + (evm_chain_id, block_hash, block_number, block_timestamp, finalized_block_number, created_at) + VALUES (:evm_chain_id, :block_hash, :block_number, :block_timestamp, :finalized_block_number, NOW()) ON CONFLICT DO NOTHING` _, err = o.ds.NamedExecContext(ctx, query, args) return err @@ -166,7 +169,7 @@ func (o *DSORM) DeleteFilter(ctx context.Context, name string) error { // LoadFilters returns all filters for this chain func (o *DSORM) LoadFilters(ctx context.Context) (map[string]Filter, error) { query := `SELECT name, - ARRAY_AGG(DISTINCT address)::BYTEA[] AS addresses, + ARRAY_AGG(DISTINCT address)::BYTEA[] AS addresses, ARRAY_AGG(DISTINCT event)::BYTEA[] AS event_sigs, ARRAY_AGG(DISTINCT topic2 ORDER BY topic2) FILTER(WHERE topic2 IS NOT NULL) AS topic2, ARRAY_AGG(DISTINCT topic3 ORDER BY topic3) FILTER(WHERE topic3 IS NOT NULL) AS topic3, @@ -185,9 +188,52 @@ func (o *DSORM) LoadFilters(ctx context.Context) (map[string]Filter, error) { return filters, err } +func blocksQuery(clause string) string { + return fmt.Sprintf(`SELECT %s FROM evm.log_poller_blocks %s`, strings.Join(blocksFields[:], ", "), clause) +} +func logsQuery(clause string) string { + return fmt.Sprintf(`SELECT %s FROM evm.logs %s`, strings.Join(logsFields[:], ", "), clause) +} + +func logsQueryWithTablePrefix(tableAlias string, clause string) string { + var s strings.Builder + for i, field := range logsFields { + if i > 0 { + s.WriteString(", ") + } + s.WriteString(fmt.Sprintf("%s.%s", tableAlias, field)) + } + return fmt.Sprintf(`SELECT %s FROM evm.logs AS %s %s`, s.String(), tableAlias, clause) +} + +func withConfs(query string, tableAlias string, confs evmtypes.Confirmations) string { + var lastConfirmedBlock string + + var tablePrefix string + if tableAlias != "" { + tablePrefix = tableAlias + "." + } + if confs == evmtypes.Finalized { + lastConfirmedBlock = `finalized_block_number` + } else { + lastConfirmedBlock = `block_number - :confs` + } + return fmt.Sprintf(`%s %sblock_number <= ( + SELECT %s + FROM evm.log_poller_blocks + WHERE evm_chain_id = :evm_chain_id + ORDER BY block_number DESC LIMIT 1)`, query, tablePrefix, lastConfirmedBlock) +} + +func logsQueryWithConfs(clause string, confs evmtypes.Confirmations) string { + return withConfs(logsQuery(clause), "", confs) +} + func (o *DSORM) SelectBlockByHash(ctx context.Context, hash common.Hash) (*LogPollerBlock, error) { var b LogPollerBlock - if err := o.ds.GetContext(ctx, &b, `SELECT * FROM evm.log_poller_blocks WHERE block_hash = $1 AND evm_chain_id = $2`, hash.Bytes(), ubig.New(o.chainID)); err != nil { + if err := o.ds.GetContext(ctx, &b, + blocksQuery(`WHERE block_hash = $1 AND evm_chain_id = $2`), + hash.Bytes(), ubig.New(o.chainID)); err != nil { return nil, err } return &b, nil @@ -195,7 +241,9 @@ func (o *DSORM) SelectBlockByHash(ctx context.Context, hash common.Hash) (*LogPo func (o *DSORM) SelectBlockByNumber(ctx context.Context, n int64) (*LogPollerBlock, error) { var b LogPollerBlock - if err := o.ds.GetContext(ctx, &b, `SELECT * FROM evm.log_poller_blocks WHERE block_number = $1 AND evm_chain_id = $2`, n, ubig.New(o.chainID)); err != nil { + if err := o.ds.GetContext(ctx, &b, + blocksQuery(`WHERE block_number = $1 AND evm_chain_id = $2`), n, ubig.New(o.chainID), + ); err != nil { return nil, err } return &b, nil @@ -203,7 +251,9 @@ func (o *DSORM) SelectBlockByNumber(ctx context.Context, n int64) (*LogPollerBlo func (o *DSORM) SelectLatestBlock(ctx context.Context) (*LogPollerBlock, error) { var b LogPollerBlock - if err := o.ds.GetContext(ctx, &b, `SELECT * FROM evm.log_poller_blocks WHERE evm_chain_id = $1 ORDER BY block_number DESC LIMIT 1`, ubig.New(o.chainID)); err != nil { + if err := o.ds.GetContext(ctx, &b, + blocksQuery(`WHERE evm_chain_id = $1 ORDER BY block_number DESC LIMIT 1`), ubig.New(o.chainID), + ); err != nil { return nil, err } return &b, nil @@ -211,7 +261,10 @@ func (o *DSORM) SelectLatestBlock(ctx context.Context) (*LogPollerBlock, error) func (o *DSORM) SelectOldestBlock(ctx context.Context, minAllowedBlockNumber int64) (*LogPollerBlock, error) { var b LogPollerBlock - if err := o.ds.GetContext(ctx, &b, `SELECT * FROM evm.log_poller_blocks WHERE evm_chain_id = $1 AND block_number >= $2 ORDER BY block_number ASC LIMIT 1`, ubig.New(o.chainID), minAllowedBlockNumber); err != nil { + if err := o.ds.GetContext(ctx, &b, + blocksQuery(`WHERE evm_chain_id = $1 AND block_number >= $2 ORDER BY block_number ASC LIMIT 1`), + ubig.New(o.chainID), minAllowedBlockNumber, + ); err != nil { return nil, err } return &b, nil @@ -224,15 +277,11 @@ func (o *DSORM) SelectLatestLogByEventSigWithConfs(ctx context.Context, eventSig if err != nil { return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id + query := logsQueryWithConfs( + `WHERE evm_chain_id = :evm_chain_id AND event_sig = :event_sig - AND address = :address - AND block_number <= %s - ORDER BY block_number desc, log_index DESC - LIMIT 1 - `, nestedBlockNumberQuery(confs)) + AND address = :address AND `, confs) + + `ORDER BY block_number desc, log_index DESC LIMIT 1` var l Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -248,28 +297,50 @@ func (o *DSORM) SelectLatestLogByEventSigWithConfs(ctx context.Context, eventSig // DeleteBlocksBefore delete blocks before and including end. When limit is set, it will delete at most limit blocks. // Otherwise, it will delete all blocks at once. func (o *DSORM) DeleteBlocksBefore(ctx context.Context, end int64, limit int64) (int64, error) { - if limit > 0 { - result, err := o.ds.ExecContext(ctx, - `DELETE FROM evm.log_poller_blocks - WHERE block_number IN ( - SELECT block_number FROM evm.log_poller_blocks - WHERE block_number <= $1 - AND evm_chain_id = $2 - LIMIT $3 - ) - AND evm_chain_id = $2`, - end, ubig.New(o.chainID), limit) + var result sql.Result + var err error + + if limit == 0 { + result, err = o.ds.ExecContext(ctx, `DELETE FROM evm.log_poller_blocks + WHERE block_number <= $1 AND evm_chain_id = $2`, end, ubig.New(o.chainID)) if err != nil { return 0, err } return result.RowsAffected() } - result, err := o.ds.ExecContext(ctx, `DELETE FROM evm.log_poller_blocks - WHERE block_number <= $1 AND evm_chain_id = $2`, end, ubig.New(o.chainID)) + + var limitBlock int64 + err = o.ds.GetContext(ctx, &limitBlock, `SELECT MIN(block_number) FROM evm.log_poller_blocks + WHERE evm_chain_id = $1`, ubig.New(o.chainID)) if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } return 0, err } - return result.RowsAffected() + + // Remove up to limit blocks at a time, until we've reached the limit or removed everything eligible for deletion + var deleted, rows int64 + for limitBlock += (limit - 1); deleted < limit; limitBlock += limit { + if limitBlock > end { + limitBlock = end + } + result, err = o.ds.ExecContext(ctx, `DELETE FROM evm.log_poller_blocks WHERE block_number <= $1 AND evm_chain_id = $2`, limitBlock, ubig.New(o.chainID)) + if err != nil { + return deleted, err + } + + if rows, err = result.RowsAffected(); err != nil { + return deleted, err + } + + deleted += rows + + if limitBlock == end { + break + } + } + return deleted, err } func (o *DSORM) DeleteLogsAndBlocksAfter(ctx context.Context, start int64) error { @@ -281,11 +352,11 @@ func (o *DSORM) DeleteLogsAndBlocksAfter(ctx context.Context, start int64) error // If not applied, these queries can become very slow. After some critical number // of logs, Postgres will try to scan all the logs in the index by block_number. // Latency without upper bound filter can be orders of magnitude higher for large number of logs. - _, err := o.ds.ExecContext(ctx, `DELETE FROM evm.log_poller_blocks + _, err := o.ds.ExecContext(ctx, `DELETE FROM evm.log_poller_blocks WHERE evm_chain_id = $1 - AND block_number >= $2 - AND block_number <= (SELECT MAX(block_number) - FROM evm.log_poller_blocks + AND block_number >= $2 + AND block_number <= (SELECT MAX(block_number) + FROM evm.log_poller_blocks WHERE evm_chain_id = $1)`, ubig.New(o.chainID), start) if err != nil { @@ -293,8 +364,8 @@ func (o *DSORM) DeleteLogsAndBlocksAfter(ctx context.Context, start int64) error return err } - _, err = o.ds.ExecContext(ctx, `DELETE FROM evm.logs - WHERE evm_chain_id = $1 + _, err = o.ds.ExecContext(ctx, `DELETE FROM evm.logs + WHERE evm_chain_id = $1 AND block_number >= $2 AND block_number <= (SELECT MAX(block_number) FROM evm.logs WHERE evm_chain_id = $1)`, ubig.New(o.chainID), start) @@ -314,32 +385,49 @@ type Exp struct { ShouldDelete bool } +func (o *DSORM) SelectUnmatchedLogIDs(ctx context.Context, limit int64) (ids []uint64, err error) { + query := ` + SELECT l.id FROM evm.logs l LEFT JOIN ( + SELECT evm_chain_id, address, event + FROM evm.log_poller_filters + WHERE evm_chain_id = $1 + GROUP BY evm_chain_id, address, event + ) r ON l.evm_chain_id = r.evm_chain_id AND l.address = r.address AND l.event_sig = r.event + WHERE l.evm_chain_id = $1 AND r.evm_chain_id IS NULL + ` + + if limit == 0 { + err = o.ds.SelectContext(ctx, &ids, query, ubig.New(o.chainID)) + return ids, err + } + err = o.ds.SelectContext(ctx, &ids, fmt.Sprintf("%s LIMIT %d", query, limit), ubig.New(o.chainID)) + + return ids, err +} + // DeleteExpiredLogs removes any logs which either: // - don't match any currently registered filters, or // - have a timestamp older than any matching filter's retention, UNLESS there is at // least one matching filter with retention=0 func (o *DSORM) DeleteExpiredLogs(ctx context.Context, limit int64) (int64, error) { - var err error - var result sql.Result - query := `DELETE FROM evm.logs - WHERE (evm_chain_id, address, event_sig, block_number) IN ( - SELECT l.evm_chain_id, l.address, l.event_sig, l.block_number - FROM evm.logs l - LEFT JOIN ( - SELECT address, event, CASE WHEN MIN(retention) = 0 THEN 0 ELSE MAX(retention) END AS retention - FROM evm.log_poller_filters - WHERE evm_chain_id = $1 - GROUP BY evm_chain_id, address, event - ) r ON l.address = r.address AND l.event_sig = r.event - WHERE l.evm_chain_id = $1 AND -- Must be WHERE rather than ON due to LEFT JOIN - r.retention IS NULL OR (r.retention != 0 AND l.block_timestamp <= STATEMENT_TIMESTAMP() - (r.retention / 10^9 * interval '1 second')) %s)` - + limitClause := "" if limit > 0 { - result, err = o.ds.ExecContext(ctx, fmt.Sprintf(query, "LIMIT $2"), ubig.New(o.chainID), limit) - } else { - result, err = o.ds.ExecContext(ctx, fmt.Sprintf(query, ""), ubig.New(o.chainID)) + limitClause = fmt.Sprintf("LIMIT %d", limit) } + query := fmt.Sprintf(` + WITH rows_to_delete AS ( + SELECT l.id + FROM evm.logs l JOIN ( + SELECT evm_chain_id, address, event, MAX(retention) AS retention + FROM evm.log_poller_filters + WHERE evm_chain_id = $1 + GROUP BY evm_chain_id, address, event + HAVING MIN(retention) > 0 + ) r ON l.evm_chain_id = r.evm_chain_id AND l.address = r.address AND l.event_sig = r.event AND + l.block_timestamp <= STATEMENT_TIMESTAMP() - (r.retention / 10^9 * interval '1 second') %s + ) DELETE FROM evm.logs WHERE id IN (SELECT id FROM rows_to_delete)`, limitClause) + result, err := o.ds.ExecContext(ctx, query, ubig.New(o.chainID)) if err != nil { return 0, err } @@ -384,10 +472,10 @@ func (o *DSORM) insertLogsWithinTx(ctx context.Context, logs []Log, tx sqlutil.D end = len(logs) } - query := `INSERT INTO evm.logs - (evm_chain_id, log_index, block_hash, block_number, block_timestamp, address, event_sig, topics, tx_hash, data, created_at) - VALUES - (:evm_chain_id, :log_index, :block_hash, :block_number, :block_timestamp, :address, :event_sig, :topics, :tx_hash, :data, NOW()) + query := `INSERT INTO evm.logs + (evm_chain_id, log_index, block_hash, block_number, block_timestamp, address, event_sig, topics, tx_hash, data, created_at) + VALUES + (:evm_chain_id, :log_index, :block_hash, :block_number, :block_timestamp, :address, :event_sig, :topics, :tx_hash, :data, NOW()) ON CONFLICT DO NOTHING` _, err := tx.NamedExecContext(ctx, query, logs[start:end]) @@ -422,11 +510,11 @@ func (o *DSORM) SelectLogsByBlockRange(ctx context.Context, start, end int64) ([ return nil, err } - query := `SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND block_number >= :start_block - AND block_number <= :end_block - ORDER BY block_number, log_index` + query := logsQuery(` + WHERE evm_chain_id = :evm_chain_id + AND block_number >= :start_block + AND block_number <= :end_block + ORDER BY block_number, log_index`) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -451,13 +539,13 @@ func (o *DSORM) SelectLogs(ctx context.Context, start, end int64, address common return nil, err } - query := `SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - AND block_number >= :start_block - AND block_number <= :end_block - ORDER BY block_number, log_index` + query := logsQuery(` + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND block_number >= :start_block + AND block_number <= :end_block + ORDER BY block_number, log_index`) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -482,14 +570,12 @@ func (o *DSORM) SelectLogsCreatedAfter(ctx context.Context, address common.Addre return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - AND block_timestamp > :block_timestamp_after - AND block_number <= %s - ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) + query := logsQueryWithConfs( + `WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND block_timestamp > :block_timestamp_after AND `, confs) + + `ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -516,12 +602,12 @@ func (o *DSORM) SelectLogsWithSigs(ctx context.Context, start, end int64, addres return nil, err } - query := `SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - 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` + query := logsQuery(` + WHERE evm_chain_id = :evm_chain_id + 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`) query, sqlArgs, err := o.ds.BindNamed(query, args) if err != nil { @@ -544,11 +630,11 @@ func (o *DSORM) GetBlocksRange(ctx context.Context, start int64, end int64) ([]L return nil, err } - query := `SELECT * FROM evm.log_poller_blocks - WHERE block_number >= :start_block + query := blocksQuery(` + WHERE block_number >= :start_block AND block_number <= :end_block AND evm_chain_id = :evm_chain_id - ORDER BY block_number ASC` + ORDER BY block_number ASC`) var blocks []LogPollerBlock query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -575,17 +661,17 @@ func (o *DSORM) SelectLatestLogEventSigsAddrsWithConfs(ctx context.Context, from return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs WHERE (block_number, address, event_sig) IN ( - SELECT MAX(block_number), address, event_sig FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND event_sig = ANY(:event_sig_array) - AND address = ANY(:address_array) - AND block_number > :start_block - AND block_number <= %s - GROUP BY event_sig, address - ) - ORDER BY block_number ASC`, nestedBlockNumberQuery(confs)) + query := logsQueryWithConfs(`WHERE id IN ( + SELECT LAST_VALUE(id) OVER( + PARTITION BY evm_chain_id, address, event_sig + ORDER BY block_number, log_index + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + ) FROM evm.logs + WHERE evm_chain_id = :evm_chain_id + AND event_sig = ANY(:event_sig_array) + AND address = ANY(:address_array) + AND block_number >= :start_block AND `, confs) + ` + )` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -610,13 +696,12 @@ func (o *DSORM) SelectLatestBlockByEventSigsAddrsWithConfs(ctx context.Context, if err != nil { return 0, err } - query := fmt.Sprintf(` - SELECT COALESCE(MAX(block_number), 0) FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND event_sig = ANY(:event_sig_array) - AND address = ANY(:address_array) - AND block_number > :start_block - AND block_number <= %s`, nestedBlockNumberQuery(confs)) + + query := withConfs(`SELECT COALESCE(MAX(block_number), 0) FROM evm.logs + WHERE evm_chain_id = :evm_chain_id + AND event_sig = ANY(:event_sig_array) + AND address = ANY(:address_array) + AND block_number >= :start_block AND `, "", confs) var blockNumber int64 query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -641,14 +726,12 @@ func (o *DSORM) SelectLogsDataWordRange(ctx context.Context, address common.Addr return nil, err } - query := fmt.Sprintf(`SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - 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)) + query := logsQueryWithConfs(`WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + 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 `, confs) + + `ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -672,14 +755,12 @@ func (o *DSORM) SelectLogsDataWordGreaterThan(ctx context.Context, address commo return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - 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)) + query := logsQueryWithConfs(` + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND substring(data from 32*:word_index+1 for 32) >= :word_value_min AND `, confs) + + `ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -703,15 +784,14 @@ func (o *DSORM) SelectLogsDataWordBetween(ctx context.Context, address common.Ad if err != nil { return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - 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)) + + query := logsQueryWithConfs(` + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + 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 `, confs) + + `ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -735,14 +815,11 @@ func (o *DSORM) SelectIndexedLogsTopicGreaterThan(ctx context.Context, address c return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - AND topics[:topic_index] >= :topic_value_min - AND block_number <= %s - ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) + query := logsQueryWithConfs(`WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND topics[:topic_index] >= :topic_value_min AND `, confs) + + `ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -767,15 +844,12 @@ func (o *DSORM) SelectIndexedLogsTopicRange(ctx context.Context, address common. return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - AND topics[:topic_index] >= :topic_value_min - AND topics[:topic_index] <= :topic_value_max - AND block_number <= %s - ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) + query := logsQueryWithConfs(`WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND topics[:topic_index] >= :topic_value_min + AND topics[:topic_index] <= :topic_value_max AND `, confs) + + `ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -799,14 +873,12 @@ func (o *DSORM) SelectIndexedLogs(ctx context.Context, address common.Address, e return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - AND topics[:topic_index] = ANY(:topic_values) - AND block_number <= %s - ORDER BY block_number, log_index`, nestedBlockNumberQuery(confs)) + query := logsQueryWithConfs(` + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND topics[:topic_index] = ANY(:topic_values) AND `, confs) + + `ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -832,14 +904,14 @@ func (o *DSORM) SelectIndexedLogsByBlockRange(ctx context.Context, start, end in return nil, err } - query := `SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - AND topics[:topic_index] = ANY(:topic_values) - AND block_number >= :start_block - AND block_number <= :end_block - ORDER BY block_number, log_index` + query := logsQuery(` + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND topics[:topic_index] = ANY(:topic_values) + AND block_number >= :start_block + AND block_number <= :end_block + ORDER BY block_number, log_index`) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -865,16 +937,13 @@ func (o *DSORM) SelectIndexedLogsCreatedAfter(ctx context.Context, address commo return nil, err } - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - 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)) + query := logsQueryWithConfs(` + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND topics[:topic_index] = ANY(:topic_values) + AND block_timestamp > :block_timestamp_after AND `, confs) + + `ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -898,12 +967,12 @@ func (o *DSORM) SelectIndexedLogsByTxHash(ctx context.Context, address common.Ad return nil, err } - query := `SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :event_sig - AND tx_hash = :tx_hash - ORDER BY block_number, log_index` + query := logsQuery(` + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :event_sig + AND tx_hash = :tx_hash + ORDER BY block_number, log_index`) var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -933,25 +1002,22 @@ func (o *DSORM) SelectIndexedLogsWithSigsExcluding(ctx context.Context, sigA, si return nil, err } - nestedQuery := nestedBlockNumberQuery(confs) - query := fmt.Sprintf(` - SELECT * FROM evm.logs - WHERE evm_chain_id = :evm_chain_id - AND address = :address - AND event_sig = :sigA - AND block_number BETWEEN :start_block AND :end_block - AND block_number <= %s - EXCEPT - SELECT a.* FROM evm.logs AS a - INNER JOIN evm.logs B - ON a.evm_chain_id = b.evm_chain_id - AND a.address = b.address - AND a.topics[:topic_index] = b.topics[:topic_index] - AND a.event_sig = :sigA - 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`, nestedQuery, nestedQuery) + query := logsQueryWithConfs(` + WHERE evm_chain_id = :evm_chain_id + AND address = :address + AND event_sig = :sigA + AND block_number BETWEEN :start_block AND :end_block AND `, confs) + + ` EXCEPT ` + + withConfs(logsQueryWithTablePrefix("a", ` + INNER JOIN evm.logs AS b + ON a.evm_chain_id = b.evm_chain_id + AND a.address = b.address + AND a.topics[:topic_index] = b.topics[:topic_index] + AND a.event_sig = :sigA + AND b.event_sig = :sigB + AND b.block_number BETWEEN :start_block AND :end_block + AND `), "b", confs) + + ` ORDER BY block_number, log_index` var logs []Log query, sqlArgs, err := o.ds.BindNamed(query, args) @@ -989,19 +1055,11 @@ func (o *DSORM) FilteredLogs(ctx context.Context, filter []query.Expression, lim return logs, nil } -func nestedBlockNumberQuery(confs evmtypes.Confirmations) string { - if confs == evmtypes.Finalized { - return ` - (SELECT finalized_block_number - FROM evm.log_poller_blocks - WHERE evm_chain_id = :evm_chain_id - ORDER BY block_number DESC LIMIT 1) ` - } - // Intentionally wrap with greatest() function and don't return negative block numbers when :confs > :block_number - // It doesn't impact logic of the outer query, because block numbers are never less or equal to 0 (guarded by log_poller_blocks_block_number_check) - return ` - (SELECT greatest(block_number - :confs, 0) - FROM evm.log_poller_blocks - WHERE evm_chain_id = :evm_chain_id - ORDER BY block_number DESC LIMIT 1) ` +// DeleteLogsByRowID accepts a list of log row id's to delete +func (o *DSORM) DeleteLogsByRowID(ctx context.Context, rowIDs []uint64) (int64, error) { + result, err := o.ds.ExecContext(ctx, `DELETE FROM evm.logs WHERE id = ANY($1)`, rowIDs) + if err != nil { + return 0, err + } + return result.RowsAffected() } diff --git a/core/chains/evm/logpoller/orm_test.go b/core/chains/evm/logpoller/orm_test.go index 14f6bbeb6aa..7ebc97bb835 100644 --- a/core/chains/evm/logpoller/orm_test.go +++ b/core/chains/evm/logpoller/orm_test.go @@ -321,10 +321,22 @@ func TestORM(t *testing.T) { Data: []byte("hello short retention"), BlockTimestamp: time.Now(), }, + { + EvmChainId: ubig.New(th.ChainID), + LogIndex: 7, + BlockHash: common.HexToHash("0x1239"), + BlockNumber: int64(17), + EventSig: topic, + Topics: [][]byte{topic[:]}, + Address: common.HexToAddress("0x1236"), + TxHash: common.HexToHash("0x1888"), + Data: []byte("hello2 short retention"), + BlockTimestamp: time.Now(), + }, { EvmChainId: ubig.New(th.ChainID), LogIndex: 8, - BlockHash: common.HexToHash("0x1238"), + BlockHash: common.HexToHash("0x1239"), BlockNumber: int64(17), EventSig: topic2, Topics: [][]byte{topic2[:]}, @@ -365,10 +377,9 @@ func TestORM(t *testing.T) { }, })) - t.Log(latest.BlockNumber) logs, err := o1.SelectLogsByBlockRange(ctx, 1, 17) require.NoError(t, err) - require.Len(t, logs, 8) + require.Len(t, logs, 9) logs, err = o1.SelectLogsByBlockRange(ctx, 10, 10) require.NoError(t, err) @@ -483,18 +494,33 @@ func TestORM(t *testing.T) { require.Equal(t, int64(17), latest.BlockNumber) logs, err = o1.SelectLogsByBlockRange(ctx, 1, latest.BlockNumber) require.NoError(t, err) - require.Len(t, logs, 8) + require.Len(t, logs, 9) // Delete expired logs with page limit time.Sleep(2 * time.Millisecond) // just in case we haven't reached the end of the 1ms retention period - deleted, err := o1.DeleteExpiredLogs(ctx, 2) + deleted, err := o1.DeleteExpiredLogs(ctx, 1) require.NoError(t, err) - assert.Equal(t, int64(2), deleted) + assert.Equal(t, int64(1), deleted) // Delete expired logs without page limit deleted, err = o1.DeleteExpiredLogs(ctx, 0) require.NoError(t, err) - assert.Equal(t, int64(2), deleted) + assert.Equal(t, int64(1), deleted) + + // Select unmatched logs with page limit + ids, err := o1.SelectUnmatchedLogIDs(ctx, 2) + require.NoError(t, err) + assert.Len(t, ids, 2) + + // Select unmatched logs without page limit + ids, err = o1.SelectUnmatchedLogIDs(ctx, 0) + require.NoError(t, err) + assert.Len(t, ids, 3) + + // Delete logs by row id + deleted, err = o1.DeleteLogsByRowID(ctx, ids) + require.NoError(t, err) + assert.Equal(t, int64(3), deleted) // Ensure that both of the logs from the second chain are still there logs, err = o2.SelectLogs(ctx, 0, 100, common.HexToAddress("0x1236"), topic2) @@ -506,9 +532,9 @@ func TestORM(t *testing.T) { logs, err = o1.SelectLogsByBlockRange(ctx, 1, latest.BlockNumber) require.NoError(t, err) - // It should have retained the log matching filter0 (due to ret=0 meaning permanent retention) as well as all - // 3 logs matching filter12 (ret=1 hour). It should have deleted 3 logs not matching any filter, as well as 1 - // of the 2 logs matching filter1 (ret=1ms)--the one that doesn't also match filter12. + // The only log which should be deleted is the one which matches filter1 (ret=1ms) but not filter12 (ret=1 hour) + // Importantly, it shouldn't delete any logs matching only filter0 (ret=0 meaning permanent retention). Anything + // matching filter12 should be kept regardless of what other filters it matches. assert.Len(t, logs, 4) // Delete logs after should delete all logs. @@ -1600,7 +1626,7 @@ func TestSelectLatestBlockNumberEventSigsAddrsWithConfs(t *testing.T) { events: []common.Hash{event1, event2}, addrs: []common.Address{address1, address2}, confs: 0, - fromBlock: 3, + fromBlock: 4, expectedBlockNumber: 0, }, { diff --git a/core/chains/evm/logpoller/parser.go b/core/chains/evm/logpoller/parser.go index 19302c89192..9dfa00eaf3e 100644 --- a/core/chains/evm/logpoller/parser.go +++ b/core/chains/evm/logpoller/parser.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) @@ -26,6 +27,10 @@ const ( var ( ErrUnexpectedCursorFormat = errors.New("unexpected cursor format") + logsFields = [...]string{"evm_chain_id", "log_index", "block_hash", "block_number", + "address", "event_sig", "topics", "tx_hash", "data", "created_at", "block_timestamp"} + blocksFields = [...]string{"evm_chain_id", "block_hash", "block_number", "block_timestamp", + "finalized_block_number", "created_at"} ) // The parser builds SQL expressions piece by piece for each Accept function call and resets the error and expression @@ -220,7 +225,7 @@ func (v *pgDSLParser) buildQuery(chainID *big.Int, expressions []query.Expressio v.err = nil // build the query string - clauses := []string{"SELECT evm.logs.* FROM evm.logs"} + clauses := []string{logsQuery("")} where, err := v.whereClause(expressions, limiter) if err != nil { diff --git a/core/chains/evm/logpoller/parser_test.go b/core/chains/evm/logpoller/parser_test.go index f37497600bd..8446b3c93ef 100644 --- a/core/chains/evm/logpoller/parser_test.go +++ b/core/chains/evm/logpoller/parser_test.go @@ -10,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) @@ -34,7 +35,7 @@ func TestDSLParser(t *testing.T) { result, args, err := parser.buildQuery(chainID, expressions, limiter) require.NoError(t, err) - assert.Equal(t, "SELECT evm.logs.* FROM evm.logs WHERE evm_chain_id = :evm_chain_id ORDER BY "+defaultSort, result) + assert.Equal(t, logsQuery(" WHERE evm_chain_id = :evm_chain_id ORDER BY "+defaultSort), result) assertArgs(t, args, 1) }) @@ -52,15 +53,14 @@ func TestDSLParser(t *testing.T) { limiter := query.NewLimitAndSort(query.CursorLimit("10-5-0x42", query.CursorFollowing, 20)) result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND (address = :address_0 AND event_sig = :event_sig_0 " + - "AND block_number <= " + - "(SELECT finalized_block_number FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1)) " + - "AND (block_number > :cursor_block_number OR (block_number = :cursor_block_number AND log_index > :cursor_log_index)) " + - "ORDER BY block_number ASC, log_index ASC, tx_hash ASC " + - "LIMIT 20" + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND (address = :address_0 AND event_sig = :event_sig_0 " + + "AND block_number <= " + + "(SELECT finalized_block_number FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1)) " + + "AND (block_number > :cursor_block_number OR (block_number = :cursor_block_number AND log_index > :cursor_log_index)) " + + "ORDER BY block_number ASC, log_index ASC, tx_hash ASC " + + "LIMIT 20") require.NoError(t, err) assert.Equal(t, expected, result) @@ -80,12 +80,11 @@ func TestDSLParser(t *testing.T) { limiter := query.NewLimitAndSort(query.CountLimit(20)) result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND (address = :address_0 AND event_sig = :event_sig_0) " + - "ORDER BY " + defaultSort + " " + - "LIMIT 20" + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND (address = :address_0 AND event_sig = :event_sig_0) " + + "ORDER BY " + defaultSort + " " + + "LIMIT 20") require.NoError(t, err) assert.Equal(t, expected, result) @@ -102,10 +101,9 @@ func TestDSLParser(t *testing.T) { limiter := query.NewLimitAndSort(query.Limit{}, query.NewSortBySequence(query.Desc)) result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "ORDER BY block_number DESC, log_index DESC, tx_hash DESC" + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "ORDER BY block_number DESC, log_index DESC, tx_hash DESC") require.NoError(t, err) assert.Equal(t, expected, result) @@ -122,10 +120,9 @@ func TestDSLParser(t *testing.T) { limiter := query.NewLimitAndSort(query.Limit{}, query.NewSortByBlock(query.Asc), query.NewSortByTimestamp(query.Desc)) result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "ORDER BY block_number ASC, block_timestamp DESC" + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "ORDER BY block_number ASC, block_timestamp DESC") require.NoError(t, err) assert.Equal(t, expected, result) @@ -147,16 +144,15 @@ func TestDSLParser(t *testing.T) { limiter := query.NewLimitAndSort(query.CursorLimit("10-20-0x42", query.CursorPrevious, 20)) result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND (block_timestamp = :block_timestamp_0 " + - "AND tx_hash = :tx_hash_0 " + - "AND block_number != :block_number_0 " + - "AND block_number <= " + - "(SELECT finalized_block_number FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1)) " + - "AND (block_number < :cursor_block_number OR (block_number = :cursor_block_number AND log_index < :cursor_log_index)) " + - "ORDER BY block_number DESC, log_index DESC, tx_hash DESC LIMIT 20" + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND (block_timestamp = :block_timestamp_0 " + + "AND tx_hash = :tx_hash_0 " + + "AND block_number != :block_number_0 " + + "AND block_number <= " + + "(SELECT finalized_block_number FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1)) " + + "AND (block_number < :cursor_block_number OR (block_number = :cursor_block_number AND log_index < :cursor_log_index)) " + + "ORDER BY block_number DESC, log_index DESC, tx_hash DESC LIMIT 20") require.NoError(t, err) assert.Equal(t, expected, result) @@ -175,10 +171,9 @@ func TestDSLParser(t *testing.T) { limiter := query.LimitAndSort{} result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND block_number <= (SELECT finalized_block_number FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1) ORDER BY " + defaultSort + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND block_number <= (SELECT finalized_block_number FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1) ORDER BY " + defaultSort) require.NoError(t, err) assert.Equal(t, expected, result) @@ -194,10 +189,9 @@ func TestDSLParser(t *testing.T) { limiter := query.LimitAndSort{} result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND block_number <= (SELECT greatest(block_number - :confs_0, 0) FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1) ORDER BY " + defaultSort + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND block_number <= (SELECT greatest(block_number - :confs_0, 0) FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1) ORDER BY " + defaultSort) require.NoError(t, err) assert.Equal(t, expected, result) @@ -213,10 +207,9 @@ func TestDSLParser(t *testing.T) { limiter := query.LimitAndSort{} result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND block_number <= (SELECT greatest(block_number - :confs_0, 0) FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1) ORDER BY " + defaultSort + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND block_number <= (SELECT greatest(block_number - :confs_0, 0) FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1) ORDER BY " + defaultSort) require.NoError(t, err) assert.Equal(t, expected, result) @@ -243,10 +236,9 @@ func TestDSLParser(t *testing.T) { limiter := query.LimitAndSort{} result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND substring(data from 32*:word_index_0+1 for 32) > :word_value_0 ORDER BY " + defaultSort + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND substring(data from 32*:word_index_0+1 for 32) > :word_value_0 ORDER BY " + defaultSort) require.NoError(t, err) assert.Equal(t, expected, result) @@ -268,10 +260,9 @@ func TestDSLParser(t *testing.T) { limiter := query.LimitAndSort{} result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND topics[:topic_index_0] > :topic_value_0 AND topics[:topic_index_0] < :topic_value_1 ORDER BY " + defaultSort + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND topics[:topic_index_0] > :topic_value_0 AND topics[:topic_index_0] < :topic_value_1 ORDER BY " + defaultSort) require.NoError(t, err) assert.Equal(t, expected, result) @@ -304,12 +295,11 @@ func TestDSLParser(t *testing.T) { limiter := query.LimitAndSort{} result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND (block_timestamp >= :block_timestamp_0 " + - "AND (tx_hash = :tx_hash_0 " + - "OR block_number <= (SELECT greatest(block_number - :confs_0, 0) FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1))) ORDER BY " + defaultSort + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND (block_timestamp >= :block_timestamp_0 " + + "AND (tx_hash = :tx_hash_0 " + + "OR block_number <= (SELECT greatest(block_number - :confs_0, 0) FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1))) ORDER BY " + defaultSort) require.NoError(t, err) assert.Equal(t, expected, result) @@ -353,14 +343,13 @@ func TestDSLParser(t *testing.T) { limiter := query.LimitAndSort{} result, args, err := parser.buildQuery(chainID, expressions, limiter) - expected := "SELECT evm.logs.* " + - "FROM evm.logs " + - "WHERE evm_chain_id = :evm_chain_id " + - "AND (block_timestamp = :block_timestamp_0 " + - "AND (tx_hash = :tx_hash_0 " + - "OR (block_number <= (SELECT greatest(block_number - :confs_0, 0) FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1) " + - "AND substring(data from 32*:word_index_0+1 for 32) > :word_value_0 " + - "AND substring(data from 32*:word_index_0+1 for 32) <= :word_value_1))) ORDER BY " + defaultSort + expected := logsQuery( + " WHERE evm_chain_id = :evm_chain_id " + + "AND (block_timestamp = :block_timestamp_0 " + + "AND (tx_hash = :tx_hash_0 " + + "OR (block_number <= (SELECT greatest(block_number - :confs_0, 0) FROM evm.log_poller_blocks WHERE evm_chain_id = :evm_chain_id ORDER BY block_number DESC LIMIT 1) " + + "AND substring(data from 32*:word_index_0+1 for 32) > :word_value_0 " + + "AND substring(data from 32*:word_index_0+1 for 32) <= :word_value_1))) ORDER BY " + defaultSort) require.NoError(t, err) assert.Equal(t, expected, result) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index b32ae445022..73a8605e008 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -1,6 +1,6 @@ module github.com/smartcontractkit/chainlink/core/scripts -go 1.22.5 +go 1.22.7 // Make sure we're working with the latest chainlink libs replace github.com/smartcontractkit/chainlink/v2 => ../../ diff --git a/core/store/migrate/migrations/0254_log_poller_primary_keys.sql b/core/store/migrate/migrations/0254_log_poller_primary_keys.sql new file mode 100644 index 00000000000..39bee02656c --- /dev/null +++ b/core/store/migrate/migrations/0254_log_poller_primary_keys.sql @@ -0,0 +1,42 @@ +-- +goose Up + +-- Replace (block_hash, log_index, evm_chain_id) primary key on evm.log_poller_blocks with new id column +ALTER TABLE evm.logs DROP CONSTRAINT logs_pkey; +ALTER TABLE evm.logs ADD COLUMN id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY; +-- Replaces previous primary key index, but also useful for accelerating DeleteLogsAndBlocksAfter +-- This also strengthens the uniqueness requirement, ensuring that we can't insert logs for two different blocks +-- with the same block number but different block hashes--something which would corrupt the db +CREATE UNIQUE INDEX idx_logs_chain_block_logindex ON evm.logs (evm_chain_id, block_number, log_index); + +-- Previously used for WHERE evm_chain_id = $1 AND address = $2 AND event_sig = $3 ... ORDER BY block_number, created_at +DROP INDEX IF EXISTS evm.idx_evm_logs_ordered_by_block_and_created_at; +-- Useful for the current form of those queries: WHERE evm_chain_id = $1 AND address = $2 AND event_sig = $3 ... ORDER BY block_number, log_index +-- log_index is not included in this index because it increases the index size by ~ 10% for a likely negligible performance benefit +CREATE INDEX idx_logs_chain_address_event_block_logindex ON evm.logs (evm_chain_id, address, event_sig, block_number); + +-- Replace (block_number, evm_chain_id) primary key on evm.log_poller_blocks with new id column +ALTER TABLE evm.log_poller_blocks DROP CONSTRAINT log_poller_blocks_pkey; +ALTER TABLE evm.log_poller_blocks ADD COLUMN id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY; + +-- Add UNIQUE keyword to (evm_chain_id, block_number DESC) index (and rename for consistency) +-- This index is useful for any query for logs which includies a "confs" param, and for DeleteBlocksBefore, DeleteLogsAndBlocksAfter, etc. +-- Prior to this we also had a separate UNIQUE INDEX for (block_number, evm_chain_id) generated by the primary key defintion. +-- Adding the UNIQUE keyword to this allows us to combine the two indices into a single index, saving on disk space--with the +-- added benefit of making insertions into this index slightly faster (since it can rely on the keys being unique) +DROP INDEX IF EXISTS evm.idx_evm_log_poller_blocks_order_by_block; +CREATE UNIQUE INDEX idx_log_poller_blocks_chain_block ON evm.log_poller_blocks (evm_chain_id, block_number DESC); + +-- +goose Down + +-- revert evm.log_poller_blocks +DROP INDEX IF EXISTS evm.idx_log_poller_blocks_chain_block; +CREATE INDEX idx_evm_log_poller_blocks_order_by_block ON evm.log_poller_blocks (evm_chain_id, block_number DESC); +ALTER TABLE evm.log_poller_blocks DROP COLUMN id; +ALTER TABLE evm.log_poller_blocks ADD PRIMARY KEY (block_number, evm_chain_id); + +-- revert evm.logs +DROP INDEX IF EXISTS evm.idx_logs_chain_address_event_block_logindex; +CREATE INDEX idx_evm_logs_ordered_by_block_and_created_at ON evm.logs (evm_chain_id, address, event_sig, block_number, created_at); +DROP INDEX IF EXISTS evm.idx_logs_chain_block_logindex; +ALTER TABLE evm.logs DROP COLUMN id; +ALTER TABLE evm.logs ADD PRIMARY KEY (block_hash, log_index, evm_chain_id); diff --git a/dashboard-lib/go.mod b/dashboard-lib/go.mod index 0ab2b696dbc..a26c6ab454f 100644 --- a/dashboard-lib/go.mod +++ b/dashboard-lib/go.mod @@ -1,6 +1,6 @@ module github.com/smartcontractkit/chainlink/dashboard-lib -go 1.22.5 +go 1.22.7 require ( github.com/K-Phoen/grabana v0.22.1 diff --git a/go.mod b/go.mod index 73c38b8c2db..f0640e9706f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/smartcontractkit/chainlink/v2 -go 1.22.5 +go 1.22.7 require ( github.com/Depado/ginprom v1.8.0 diff --git a/integration-tests/go.mod b/integration-tests/go.mod index b6dab3a01f9..b6aaaa5cbc0 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -1,6 +1,6 @@ module github.com/smartcontractkit/chainlink/integration-tests -go 1.22.5 +go 1.22.7 // Make sure we're working with the latest chainlink libs replace github.com/smartcontractkit/chainlink/v2 => ../ diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 975b1a13d35..1b2398a80f3 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -1,6 +1,6 @@ module github.com/smartcontractkit/chainlink/load-tests -go 1.22.5 +go 1.22.7 // Make sure we're working with the latest chainlink libs replace github.com/smartcontractkit/chainlink/v2 => ../../ From 611460068702fca58d7eac6fb4011327f6036500 Mon Sep 17 00:00:00 2001 From: Jordan Krage Date: Tue, 1 Oct 2024 05:01:12 -0500 Subject: [PATCH 09/28] fix linter issues (#14595) --- common/client/multi_node.go | 4 +- common/headtracker/head_tracker.go | 16 ++- .../capabilities/targets/write_target_test.go | 12 +- .../chains/evm/gas/block_history_estimator.go | 6 +- .../evm/gas/rollups/arbitrum_l1_oracle.go | 57 +------- core/chains/evm/gas/rollups/l1_oracle.go | 3 - core/chains/evm/gas/rollups/l1_oracle_test.go | 128 ------------------ .../chains/evm/gas/rollups/mocks/l1_oracle.go | 114 +--------------- core/chains/evm/gas/rollups/op_l1_oracle.go | 50 ------- .../evm/gas/rollups/zkSync_l1_oracle.go | 17 --- .../evm/headtracker/head_tracker_test.go | 12 +- core/cmd/prompter.go | 2 +- .../ocr2/plugins/llo/integration_test.go | 3 +- core/services/pipeline/getters.go | 2 +- 14 files changed, 35 insertions(+), 391 deletions(-) diff --git a/common/client/multi_node.go b/common/client/multi_node.go index c9250a1d620..00ec436d9ab 100644 --- a/common/client/multi_node.go +++ b/common/client/multi_node.go @@ -2,6 +2,7 @@ package client import ( "context" + "errors" "fmt" "math" "math/big" @@ -687,9 +688,8 @@ func aggregateTxResults(resultsByCode sendTxErrors) (txResult error, err error) // We assume that primary node would never report false positive txResult for a transaction. // Thus, if such case occurs it's probably due to misconfiguration or a bug and requires manual intervention. if hasSevereErrors { - const errMsg = "found contradictions in nodes replies on SendTransaction: got success and severe error" // return success, since at least 1 node has accepted our broadcasted Tx, and thus it can now be included onchain - return successResults[0], fmt.Errorf(errMsg) + return successResults[0], errors.New("found contradictions in nodes replies on SendTransaction: got success and severe error") } // other errors are temporary - we are safe to return success diff --git a/common/headtracker/head_tracker.go b/common/headtracker/head_tracker.go index 8546d856b67..d309ced0cda 100644 --- a/common/headtracker/head_tracker.go +++ b/common/headtracker/head_tracker.go @@ -434,10 +434,10 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) backfill(ctx context.Context, hea } if head.BlockHash() != latestFinalizedHead.BlockHash() { - const errMsg = "expected finalized block to be present in canonical chain" - ht.log.With("finalized_block_number", latestFinalizedHead.BlockNumber(), "finalized_hash", latestFinalizedHead.BlockHash(), - "canonical_chain_block_number", head.BlockNumber(), "canonical_chain_hash", head.BlockHash()).Criticalf(errMsg) - return fmt.Errorf(errMsg) + ht.log.Criticalw("Finalized block missing from conical chain", + "finalized_block_number", latestFinalizedHead.BlockNumber(), "finalized_hash", latestFinalizedHead.BlockHash(), + "canonical_chain_block_number", head.BlockNumber(), "canonical_chain_hash", head.BlockHash()) + return FinalizedMissingError[BLOCK_HASH]{latestFinalizedHead.BlockHash(), head.BlockHash()} } l = l.With("latest_finalized_block_hash", latestFinalizedHead.BlockHash(), @@ -454,6 +454,14 @@ func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) backfill(ctx context.Context, hea return } +type FinalizedMissingError[BLOCK_HASH types.Hashable] struct { + Finalized, Canonical BLOCK_HASH +} + +func (e FinalizedMissingError[BLOCK_HASH]) Error() string { + return fmt.Sprintf("finalized block %s missing from canonical chain %s", e.Finalized, e.Canonical) +} + func (ht *headTracker[HTH, S, ID, BLOCK_HASH]) fetchAndSaveHead(ctx context.Context, n int64, hash BLOCK_HASH) (HTH, error) { ht.log.Debugw("Fetching head", "blockHeight", n, "blockHash", hash) head, err := ht.client.HeadByHash(ctx, hash) diff --git a/core/capabilities/targets/write_target_test.go b/core/capabilities/targets/write_target_test.go index 96df31bd3cc..499f4f9b29b 100644 --- a/core/capabilities/targets/write_target_test.go +++ b/core/capabilities/targets/write_target_test.go @@ -120,11 +120,11 @@ func TestWriteTarget(t *testing.T) { }) t.Run("passes gas limit set on config to the chain writer", func(t *testing.T) { - configGasLimit, err := values.NewMap(map[string]any{ + configGasLimit, err2 := values.NewMap(map[string]any{ "Address": forwarderAddr, "GasLimit": 500000, }) - require.NoError(t, err) + require.NoError(t, err2) req := capabilities.CapabilityRequest{ Metadata: validMetadata, Config: configGasLimit, @@ -134,16 +134,16 @@ func TestWriteTarget(t *testing.T) { meta := types.TxMeta{WorkflowExecutionID: &req.Metadata.WorkflowExecutionID, GasLimit: big.NewInt(500000)} cw.On("SubmitTransaction", mock.Anything, "forwarder", "report", mock.Anything, mock.Anything, forwarderAddr, &meta, mock.Anything).Return(types.ErrSettingTransactionGasLimitNotSupported) - _, err2 := writeTarget.Execute(ctx, req) + _, err2 = writeTarget.Execute(ctx, req) require.Error(t, err2) }) t.Run("retries without gas limit when ChainWriter's SubmitTransaction returns error due to gas limit not supported", func(t *testing.T) { - configGasLimit, err := values.NewMap(map[string]any{ + configGasLimit, err2 := values.NewMap(map[string]any{ "Address": forwarderAddr, "GasLimit": 500000, }) - require.NoError(t, err) + require.NoError(t, err2) req := capabilities.CapabilityRequest{ Metadata: validMetadata, Config: configGasLimit, @@ -165,7 +165,7 @@ func TestWriteTarget(t *testing.T) { Inputs: validInputs, } - _, err2 := writeTarget.Execute(ctx, req) + _, err2 = writeTarget.Execute(ctx, req) require.Error(t, err2) }) diff --git a/core/chains/evm/gas/block_history_estimator.go b/core/chains/evm/gas/block_history_estimator.go index 0386d92a0d6..02aaa06cc14 100644 --- a/core/chains/evm/gas/block_history_estimator.go +++ b/core/chains/evm/gas/block_history_estimator.go @@ -340,9 +340,9 @@ func (b *BlockHistoryEstimator) haltBumping(attempts []EvmPriorAttempt) error { } // Return error to prevent bumping if gas price is nil or if EIP1559 is enabled and tip cap is nil if maxGasPrice == nil || (b.eConfig.EIP1559DynamicFees() && maxTipCap == nil) { - errorMsg := fmt.Sprintf("%d percentile price is not set. This is likely because there aren't any valid transactions to estimate from. Preventing bumping until valid price is available to compare", percentile) - b.logger.Debugf(errorMsg) - return errors.New(errorMsg) + err := fmt.Errorf("%d percentile price is not set. This is likely because there aren't any valid transactions to estimate from. Preventing bumping until valid price is available to compare", percentile) + b.logger.Debugw("Bumping halted", "err", err) + return err } // Get the latest CheckInclusionBlocks from block history for fee cap check below blockHistory := b.getBlocks() diff --git a/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go b/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go index d758dc711e9..e332d31125f 100644 --- a/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go +++ b/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go @@ -13,15 +13,11 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink/v2/common/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" ) type ArbL1GasOracle interface { @@ -35,7 +31,6 @@ type arbitrumL1Oracle struct { client l1OracleClient pollPeriod time.Duration logger logger.SugaredLogger - chainType chaintype.ChainType l1GasPriceAddress string gasPriceMethod string @@ -93,7 +88,6 @@ func NewArbitrumL1GasOracle(lggr logger.Logger, ethClient l1OracleClient) (*arbi client: ethClient, pollPeriod: PollPeriod, logger: logger.Sugared(logger.Named(lggr, "L1GasOracle(arbitrum)")), - chainType: chaintype.ChainArbitrum, l1GasPriceAddress: l1GasPriceAddress, gasPriceMethod: gasPriceMethod, @@ -112,10 +106,6 @@ func (o *arbitrumL1Oracle) Name() string { return o.logger.Name() } -func (o *arbitrumL1Oracle) ChainType(_ context.Context) chaintype.ChainType { - return o.chainType -} - func (o *arbitrumL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run() @@ -184,24 +174,18 @@ func (o *arbitrumL1Oracle) fetchL1GasPrice(ctx context.Context) (price *big.Int, precompile := common.HexToAddress(o.l1GasPriceAddress) callData, err = o.l1GasPriceMethodAbi.Pack(o.gasPriceMethod) if err != nil { - errMsg := "failed to pack calldata for arbitrum L1 gas price method" - o.logger.Errorf(errMsg) - return nil, fmt.Errorf("%s: %w", errMsg, err) + return nil, fmt.Errorf("failed to pack calldata for arbitrum L1 gas price method: %w", err) } b, err = o.client.CallContract(ctx, ethereum.CallMsg{ To: &precompile, Data: callData, }, nil) if err != nil { - errMsg := "gas oracle contract call failed" - o.logger.Errorf(errMsg) - return nil, fmt.Errorf("%s: %w", errMsg, err) + return nil, fmt.Errorf("gas oracle contract call failed: %w", err) } if len(b) != 32 { // returns uint256; - errMsg := fmt.Sprintf("return data length (%d) different than expected (%d)", len(b), 32) - o.logger.Criticalf(errMsg) - return nil, fmt.Errorf(errMsg) + return nil, fmt.Errorf("return data length (%d) different than expected (%d)", len(b), 32) } price = new(big.Int).SetBytes(b) return price, nil @@ -229,41 +213,6 @@ func (o *arbitrumL1Oracle) GasPrice(_ context.Context) (l1GasPrice *assets.Wei, return } -// Gets the L1 gas cost for the provided transaction at the specified block num -// If block num is not provided, the value on the latest block num is used -func (o *arbitrumL1Oracle) GetGasCost(ctx context.Context, tx *gethtypes.Transaction, blockNum *big.Int) (*assets.Wei, error) { - ctx, cancel := context.WithTimeout(ctx, client.QueryTimeout) - defer cancel() - var callData, b []byte - var err error - - if callData, err = o.l1GasCostMethodAbi.Pack(o.gasCostMethod, tx.To(), false, tx.Data()); err != nil { - return nil, fmt.Errorf("failed to pack calldata for %s L1 gas cost estimation method: %w", o.chainType, err) - } - - precompile := common.HexToAddress(o.l1GasCostAddress) - b, err = o.client.CallContract(ctx, ethereum.CallMsg{ - To: &precompile, - Data: callData, - }, blockNum) - if err != nil { - errorMsg := fmt.Sprintf("gas oracle contract call failed: %v", err) - o.logger.Errorf(errorMsg) - return nil, fmt.Errorf(errorMsg) - } - - var l1GasCost *big.Int - - if len(b) != 8+2*32 { // returns (uint64 gasEstimateForL1, uint256 baseFee, uint256 l1BaseFeeEstimate); - errorMsg := fmt.Sprintf("return data length (%d) different than expected (%d)", len(b), 8+2*32) - o.logger.Critical(errorMsg) - return nil, fmt.Errorf(errorMsg) - } - l1GasCost = new(big.Int).SetBytes(b[:8]) - - return assets.NewWei(l1GasCost), nil -} - // callGetPricesInArbGas calls ArbGasInfo.getPricesInArbGas() on the precompile contract ArbGasInfoAddress. // // @return (per L2 tx, per L1 calldata unit, per storage allocation) diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index 41951755986..772dc542ed1 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -8,7 +8,6 @@ import ( "time" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/smartcontractkit/chainlink-common/pkg/services" @@ -25,8 +24,6 @@ type L1Oracle interface { services.Service GasPrice(ctx context.Context) (*assets.Wei, error) - GetGasCost(ctx context.Context, tx *types.Transaction, blockNum *big.Int) (*assets.Wei, error) - ChainType(ctx context.Context) chaintype.ChainType } type l1OracleClient interface { diff --git a/core/chains/evm/gas/rollups/l1_oracle_test.go b/core/chains/evm/gas/rollups/l1_oracle_test.go index 7a28523d396..3432723c144 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/l1_oracle_test.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -21,7 +20,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ) func TestL1Oracle(t *testing.T) { @@ -195,129 +193,3 @@ func TestL1Oracle_GasPrice(t *testing.T) { assert.Equal(t, assets.NewWei(new(big.Int).Mul(gasPriceL2, gasPerPubByteL2)), gasPrice) }) } - -func TestL1Oracle_GetGasCost(t *testing.T) { - t.Parallel() - - t.Run("Calling GetGasCost on started Arbitrum L1Oracle returns Arbitrum getL1Fee", func(t *testing.T) { - l1GasCost := big.NewInt(100) - baseFee := utils.Uint256ToBytes32(big.NewInt(1000)) - l1BaseFeeEstimate := utils.Uint256ToBytes32(big.NewInt(500)) - blockNum := big.NewInt(1000) - toAddress := utils.RandomAddress() - callData := []byte{1, 2, 3, 4, 5, 6, 7} - l1GasCostMethodAbi, err := abi.JSON(strings.NewReader(GasEstimateL1ComponentAbiString)) - require.NoError(t, err) - - tx := types.NewTx(&types.LegacyTx{ - Nonce: 42, - To: &toAddress, - Data: callData, - }) - result := common.LeftPadBytes(l1GasCost.Bytes(), 8) - result = append(result, baseFee...) - result = append(result, l1BaseFeeEstimate...) - - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { - callMsg := args.Get(1).(ethereum.CallMsg) - blockNumber := args.Get(2).(*big.Int) - var payload []byte - payload, err = l1GasCostMethodAbi.Pack("gasEstimateL1Component", toAddress, false, callData) - require.NoError(t, err) - require.Equal(t, payload, callMsg.Data) - require.Equal(t, blockNum, blockNumber) - }).Return(result, nil) - - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum) - require.NoError(t, err) - - gasCost, err := oracle.GetGasCost(tests.Context(t), tx, blockNum) - require.NoError(t, err) - require.Equal(t, assets.NewWei(l1GasCost), gasCost) - }) - - t.Run("Calling GetGasCost on started Kroma L1Oracle returns error", func(t *testing.T) { - blockNum := big.NewInt(1000) - tx := types.NewTx(&types.LegacyTx{}) - - ethClient := mocks.NewL1OracleClient(t) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainKroma) - require.NoError(t, err) - - _, err = oracle.GetGasCost(tests.Context(t), tx, blockNum) - require.Error(t, err, "L1 gas cost not supported for this chain: kroma") - }) - - t.Run("Calling GetGasCost on started OPStack L1Oracle returns OPStack getL1Fee", func(t *testing.T) { - l1GasCost := big.NewInt(100) - blockNum := big.NewInt(1000) - toAddress := utils.RandomAddress() - callData := []byte{1, 2, 3} - l1GasCostMethodAbi, err := abi.JSON(strings.NewReader(GetL1FeeAbiString)) - require.NoError(t, err) - - tx := types.NewTx(&types.LegacyTx{ - Nonce: 42, - To: &toAddress, - Data: callData, - }) - - encodedTx, err := tx.MarshalBinary() - require.NoError(t, err) - - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { - callMsg := args.Get(1).(ethereum.CallMsg) - blockNumber := args.Get(2).(*big.Int) - var payload []byte - payload, err = l1GasCostMethodAbi.Pack("getL1Fee", encodedTx) - require.NoError(t, err) - require.Equal(t, payload, callMsg.Data) - require.Equal(t, blockNum, blockNumber) - }).Return(common.BigToHash(l1GasCost).Bytes(), nil) - - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock) - require.NoError(t, err) - - gasCost, err := oracle.GetGasCost(tests.Context(t), tx, blockNum) - require.NoError(t, err) - require.Equal(t, assets.NewWei(l1GasCost), gasCost) - }) - - t.Run("Calling GetGasCost on started Scroll L1Oracle returns Scroll getL1Fee", func(t *testing.T) { - l1GasCost := big.NewInt(100) - blockNum := big.NewInt(1000) - toAddress := utils.RandomAddress() - callData := []byte{1, 2, 3} - l1GasCostMethodAbi, err := abi.JSON(strings.NewReader(GetL1FeeAbiString)) - require.NoError(t, err) - - tx := types.NewTx(&types.LegacyTx{ - Nonce: 42, - To: &toAddress, - Data: callData, - }) - - encodedTx, err := tx.MarshalBinary() - require.NoError(t, err) - - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("CallContract", mock.Anything, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})).Run(func(args mock.Arguments) { - callMsg := args.Get(1).(ethereum.CallMsg) - blockNumber := args.Get(2).(*big.Int) - var payload []byte - payload, err = l1GasCostMethodAbi.Pack("getL1Fee", encodedTx) - require.NoError(t, err) - require.Equal(t, payload, callMsg.Data) - require.Equal(t, blockNum, blockNumber) - }).Return(common.BigToHash(l1GasCost).Bytes(), nil) - - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainScroll) - require.NoError(t, err) - - gasCost, err := oracle.GetGasCost(tests.Context(t), tx, blockNum) - require.NoError(t, err) - require.Equal(t, assets.NewWei(l1GasCost), gasCost) - }) -} diff --git a/core/chains/evm/gas/rollups/mocks/l1_oracle.go b/core/chains/evm/gas/rollups/mocks/l1_oracle.go index 25bb3a2db59..747df06e83a 100644 --- a/core/chains/evm/gas/rollups/mocks/l1_oracle.go +++ b/core/chains/evm/gas/rollups/mocks/l1_oracle.go @@ -3,17 +3,11 @@ package mocks import ( - big "math/big" + context "context" assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" - chaintype "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" - - context "context" - mock "github.com/stretchr/testify/mock" - - types "github.com/ethereum/go-ethereum/core/types" ) // L1Oracle is an autogenerated mock type for the L1Oracle type @@ -29,52 +23,6 @@ func (_m *L1Oracle) EXPECT() *L1Oracle_Expecter { return &L1Oracle_Expecter{mock: &_m.Mock} } -// ChainType provides a mock function with given fields: ctx -func (_m *L1Oracle) ChainType(ctx context.Context) chaintype.ChainType { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for ChainType") - } - - var r0 chaintype.ChainType - if rf, ok := ret.Get(0).(func(context.Context) chaintype.ChainType); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(chaintype.ChainType) - } - - return r0 -} - -// L1Oracle_ChainType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChainType' -type L1Oracle_ChainType_Call struct { - *mock.Call -} - -// ChainType is a helper method to define mock.On call -// - ctx context.Context -func (_e *L1Oracle_Expecter) ChainType(ctx interface{}) *L1Oracle_ChainType_Call { - return &L1Oracle_ChainType_Call{Call: _e.mock.On("ChainType", ctx)} -} - -func (_c *L1Oracle_ChainType_Call) Run(run func(ctx context.Context)) *L1Oracle_ChainType_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *L1Oracle_ChainType_Call) Return(_a0 chaintype.ChainType) *L1Oracle_ChainType_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *L1Oracle_ChainType_Call) RunAndReturn(run func(context.Context) chaintype.ChainType) *L1Oracle_ChainType_Call { - _c.Call.Return(run) - return _c -} - // Close provides a mock function with given fields: func (_m *L1Oracle) Close() error { ret := _m.Called() @@ -178,66 +126,6 @@ func (_c *L1Oracle_GasPrice_Call) RunAndReturn(run func(context.Context) (*asset return _c } -// GetGasCost provides a mock function with given fields: ctx, tx, blockNum -func (_m *L1Oracle) GetGasCost(ctx context.Context, tx *types.Transaction, blockNum *big.Int) (*assets.Wei, error) { - ret := _m.Called(ctx, tx, blockNum) - - if len(ret) == 0 { - panic("no return value specified for GetGasCost") - } - - var r0 *assets.Wei - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, *big.Int) (*assets.Wei, error)); ok { - return rf(ctx, tx, blockNum) - } - if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, *big.Int) *assets.Wei); ok { - r0 = rf(ctx, tx, blockNum) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*assets.Wei) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *types.Transaction, *big.Int) error); ok { - r1 = rf(ctx, tx, blockNum) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// L1Oracle_GetGasCost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetGasCost' -type L1Oracle_GetGasCost_Call struct { - *mock.Call -} - -// GetGasCost is a helper method to define mock.On call -// - ctx context.Context -// - tx *types.Transaction -// - blockNum *big.Int -func (_e *L1Oracle_Expecter) GetGasCost(ctx interface{}, tx interface{}, blockNum interface{}) *L1Oracle_GetGasCost_Call { - return &L1Oracle_GetGasCost_Call{Call: _e.mock.On("GetGasCost", ctx, tx, blockNum)} -} - -func (_c *L1Oracle_GetGasCost_Call) Run(run func(ctx context.Context, tx *types.Transaction, blockNum *big.Int)) *L1Oracle_GetGasCost_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*types.Transaction), args[2].(*big.Int)) - }) - return _c -} - -func (_c *L1Oracle_GetGasCost_Call) Return(_a0 *assets.Wei, _a1 error) *L1Oracle_GetGasCost_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *L1Oracle_GetGasCost_Call) RunAndReturn(run func(context.Context, *types.Transaction, *big.Int) (*assets.Wei, error)) *L1Oracle_GetGasCost_Call { - _c.Call.Return(run) - return _c -} - // HealthReport provides a mock function with given fields: func (_m *L1Oracle) HealthReport() map[string]error { ret := _m.Called() diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 11babc5ca5d..9aa20f4f880 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -14,12 +14,9 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink/v2/common/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" @@ -31,7 +28,6 @@ type optimismL1Oracle struct { client l1OracleClient pollPeriod time.Duration logger logger.SugaredLogger - chainType chaintype.ChainType l1OracleAddress string l1GasPriceMu sync.RWMutex @@ -192,7 +188,6 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy client: ethClient, pollPeriod: PollPeriod, logger: logger.Sugared(logger.Named(lggr, fmt.Sprintf("L1GasOracle(%s)", chainType))), - chainType: chainType, l1OracleAddress: precompileAddress, isEcotone: false, @@ -220,10 +215,6 @@ func (o *optimismL1Oracle) Name() string { return o.logger.Name() } -func (o *optimismL1Oracle) ChainType(_ context.Context) chaintype.ChainType { - return o.chainType -} - func (o *optimismL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run() @@ -309,47 +300,6 @@ func (o *optimismL1Oracle) GasPrice(_ context.Context) (l1GasPrice *assets.Wei, return } -// Gets the L1 gas cost for the provided transaction at the specified block num -// If block num is not provided, the value on the latest block num is used -func (o *optimismL1Oracle) GetGasCost(ctx context.Context, tx *gethtypes.Transaction, blockNum *big.Int) (*assets.Wei, error) { - ctx, cancel := context.WithTimeout(ctx, client.QueryTimeout) - defer cancel() - var callData, b []byte - var err error - if o.chainType == chaintype.ChainKroma { - return nil, fmt.Errorf("L1 gas cost not supported for this chain: %s", o.chainType) - } - // Append rlp-encoded tx - var encodedtx []byte - if encodedtx, err = tx.MarshalBinary(); err != nil { - return nil, fmt.Errorf("failed to marshal tx for gas cost estimation: %w", err) - } - if callData, err = o.getL1FeeMethodAbi.Pack(getL1FeeMethod, encodedtx); err != nil { - return nil, fmt.Errorf("failed to pack calldata for %s L1 gas cost estimation method: %w", o.chainType, err) - } - - precompile := common.HexToAddress(o.l1OracleAddress) - b, err = o.client.CallContract(ctx, ethereum.CallMsg{ - To: &precompile, - Data: callData, - }, blockNum) - if err != nil { - errorMsg := fmt.Sprintf("gas oracle contract call failed: %v", err) - o.logger.Errorf(errorMsg) - return nil, fmt.Errorf(errorMsg) - } - - var l1GasCost *big.Int - if len(b) != 32 { // returns uint256; - errorMsg := fmt.Sprintf("return data length (%d) different than expected (%d)", len(b), 32) - o.logger.Critical(errorMsg) - return nil, fmt.Errorf(errorMsg) - } - l1GasCost = new(big.Int).SetBytes(b) - - return assets.NewWei(l1GasCost), nil -} - func (o *optimismL1Oracle) GetDAGasPrice(ctx context.Context) (*big.Int, error) { err := o.checkForUpgrade(ctx) if err != nil { diff --git a/core/chains/evm/gas/rollups/zkSync_l1_oracle.go b/core/chains/evm/gas/rollups/zkSync_l1_oracle.go index c2941233545..94d2e05ac02 100644 --- a/core/chains/evm/gas/rollups/zkSync_l1_oracle.go +++ b/core/chains/evm/gas/rollups/zkSync_l1_oracle.go @@ -15,11 +15,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/utils" - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" ) // Reads L2-specific precompiles and caches the l1GasPrice set by the L2. @@ -28,7 +25,6 @@ type zkSyncL1Oracle struct { client l1OracleClient pollPeriod time.Duration logger logger.SugaredLogger - chainType chaintype.ChainType systemContextAddress string gasPerPubdataMethod string @@ -65,7 +61,6 @@ func NewZkSyncL1GasOracle(lggr logger.Logger, ethClient l1OracleClient) *zkSyncL client: ethClient, pollPeriod: PollPeriod, logger: logger.Sugared(logger.Named(lggr, "L1GasOracle(zkSync)")), - chainType: chaintype.ChainZkSync, systemContextAddress: SystemContextAddress, gasPerPubdataMethod: SystemContext_gasPerPubdataByteMethod, @@ -83,10 +78,6 @@ func (o *zkSyncL1Oracle) Name() string { return o.logger.Name() } -func (o *zkSyncL1Oracle) ChainType(_ context.Context) chaintype.ChainType { - return o.chainType -} - func (o *zkSyncL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run() @@ -185,14 +176,6 @@ func (o *zkSyncL1Oracle) GasPrice(_ context.Context) (l1GasPrice *assets.Wei, er return } -// Gets the L1 gas cost for the provided transaction at the specified block num -// If block num is not provided, the value on the latest block num is used -func (o *zkSyncL1Oracle) GetGasCost(ctx context.Context, tx *gethtypes.Transaction, blockNum *big.Int) (*assets.Wei, error) { - //Unused method, so not implemented - // And its not possible to know gas consumption of a transaction before its executed, since zkSync only posts the state difference - return nil, fmt.Errorf("unimplemented") -} - // GetL2GasPrice calls SystemContract.gasPrice() on the zksync system precompile contract. // // @return (The current gasPrice on L2: same as tx.gasPrice) diff --git a/core/chains/evm/headtracker/head_tracker_test.go b/core/chains/evm/headtracker/head_tracker_test.go index 8e846d3122c..5534a6aa10c 100644 --- a/core/chains/evm/headtracker/head_tracker_test.go +++ b/core/chains/evm/headtracker/head_tracker_test.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" "github.com/onsi/gomega" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -21,16 +22,13 @@ import ( "golang.org/x/exp/maps" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-common/pkg/services" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox/mailboxtest" - - "github.com/jmoiron/sqlx" - + "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" + "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox/mailboxtest" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + commonht "github.com/smartcontractkit/chainlink/v2/common/headtracker" htmocks "github.com/smartcontractkit/chainlink/v2/common/headtracker/mocks" commontypes "github.com/smartcontractkit/chainlink/v2/common/headtracker/types" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" @@ -925,7 +923,7 @@ func testHeadTrackerBackfill(t *testing.T, newORM func(t *testing.T) headtracker htu.ethClient.On("LatestFinalizedBlock", mock.Anything).Return(h14Orphaned, nil).Once() err := htu.headTracker.Backfill(ctx, h15) - require.EqualError(t, err, "expected finalized block to be present in canonical chain") + require.ErrorAs(t, err, &commonht.FinalizedMissingError[common.Hash]{}) }) t.Run("Marks all blocks in chain that are older than finalized", func(t *testing.T) { htu := newHeadTrackerUniverse(t, opts{Heads: heads, FinalityTagEnabled: true}) diff --git a/core/cmd/prompter.go b/core/cmd/prompter.go index 84f930c6243..a5e67c5692e 100644 --- a/core/cmd/prompter.go +++ b/core/cmd/prompter.go @@ -92,5 +92,5 @@ func withTerminalResetter(f func()) { } func clearLine() { - fmt.Printf("\r" + strings.Repeat(" ", 60) + "\r") + fmt.Print("\r" + strings.Repeat(" ", 60) + "\r") } diff --git a/core/services/ocr2/plugins/llo/integration_test.go b/core/services/ocr2/plugins/llo/integration_test.go index 2e49e989462..a6de2172346 100644 --- a/core/services/ocr2/plugins/llo/integration_test.go +++ b/core/services/ocr2/plugins/llo/integration_test.go @@ -191,8 +191,7 @@ func setConfig(t *testing.T, donID uint32, steve *bind.TransactOpts, backend *ba func TestIntegration_LLO(t *testing.T) { testStartTimeStamp := time.Now() donID := uint32(995544) - multiplier, err := decimal.NewFromString("1e18") - require.NoError(t, err) + multiplier := decimal.New(1, 18) expirationWindow := time.Hour / time.Second reqs := make(chan request) diff --git a/core/services/pipeline/getters.go b/core/services/pipeline/getters.go index bbeb0050d68..1dff20f5bf4 100644 --- a/core/services/pipeline/getters.go +++ b/core/services/pipeline/getters.go @@ -147,7 +147,7 @@ func JSONWithVarExprs(jsExpr string, vars Vars, allowErrors bool) GetterFunc { } keypath, is := maybeKeypath.(string) if !is { - return nil, errors.Wrapf(ErrBadInput, fmt.Sprintf("you cannot use %s in your JSON", chainlinkKeyPath)) + return nil, errors.Wrap(ErrBadInput, fmt.Sprintf("you cannot use %s in your JSON", chainlinkKeyPath)) } newVal, err := vars.Get(keypath) if err != nil { From 087490196f1f2ad94221e40786bd85803ce51590 Mon Sep 17 00:00:00 2001 From: Chris De Leon <147140544+chris-de-leon-cll@users.noreply.github.com> Date: Tue, 1 Oct 2024 05:07:12 -0700 Subject: [PATCH 10/28] update L2EP code owner to BIX build (#14229) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a0818dfc197..e1b2b845809 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -71,7 +71,7 @@ core/scripts/gateway @smartcontractkit/functions /contracts/src/v0.8/automation @smartcontractkit/keepers /contracts/src/v0.8/functions @smartcontractkit/functions # TODO: interfaces folder, folder should be removed and files moved to the correct folders -/contracts/src/v0.8/l2ep @chris-de-leon-cll +/contracts/src/v0.8/l2ep @smartcontractkit/bix-build /contracts/src/v0.8/llo-feeds @smartcontractkit/data-streams-engineers # TODO: mocks folder, folder should be removed and files moved to the correct folders /contracts/src/v0.8/operatorforwarder @smartcontractkit/data-feeds-engineers From 9a891a5af601392f1f55126b1fe2b6b586b5c1a2 Mon Sep 17 00:00:00 2001 From: Jordan Krage Date: Tue, 1 Oct 2024 07:53:53 -0500 Subject: [PATCH 11/28] integration-tests/load: sort go.mod (#14616) --- integration-tests/load/go.mod | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 1b2398a80f3..93fd9263355 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -27,13 +27,6 @@ require ( go.uber.org/ratelimit v0.3.0 ) -require ( - github.com/AlekSi/pointer v1.1.0 // indirect - github.com/smartcontractkit/chainlink-automation v1.0.4 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd // indirect - github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 // indirect -) - require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.5 // indirect cosmossdk.io/api v0.3.1 // indirect @@ -45,6 +38,7 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect + github.com/AlekSi/pointer v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect @@ -387,6 +381,8 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.23 // indirect + github.com/smartcontractkit/chainlink-automation v1.0.4 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect @@ -395,6 +391,7 @@ require ( github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect + github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/wsrpc v0.8.2 // indirect github.com/soheilhy/cmux v0.1.5 // indirect From 6515802a4f3335850e7f9f93a0701c9d7f33c4e1 Mon Sep 17 00:00:00 2001 From: Sri Kidambi <1702865+kidambisrinivas@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:07:14 +0100 Subject: [PATCH 12/28] Updated chainlink common (#14618) --- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 73a8605e008..290d8d3dba8 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -22,7 +22,7 @@ require ( github.com/prometheus/client_golang v1.20.0 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/spf13/cobra v1.8.1 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 6433035ca02..29ef1473ef0 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1083,8 +1083,8 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 h1:z+XnayyX7pyvVv9OuMQ7oik7RkguQeWHhxcOoVM4oKI= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= diff --git a/go.mod b/go.mod index f0640e9706f..7d6f82d28da 100644 --- a/go.mod +++ b/go.mod @@ -75,7 +75,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd - github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f diff --git a/go.sum b/go.sum index ea1df80842d..32f170ae2e6 100644 --- a/go.sum +++ b/go.sum @@ -1044,8 +1044,8 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 h1:z+XnayyX7pyvVv9OuMQ7oik7RkguQeWHhxcOoVM4oKI= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index b6aaaa5cbc0..fe55b20b51a 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -40,7 +40,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd - github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 7ab1481ca95..411228d2b2d 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1425,8 +1425,8 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 h1:z+XnayyX7pyvVv9OuMQ7oik7RkguQeWHhxcOoVM4oKI= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 93fd9263355..cf5257669e8 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -15,7 +15,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 github.com/slack-go/slack v0.12.2 - github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 + github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.1 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.0 diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 3d40c225116..e79a8455488 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1399,8 +1399,8 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670 h1:z+XnayyX7pyvVv9OuMQ7oik7RkguQeWHhxcOoVM4oKI= -github.com/smartcontractkit/chainlink-common v0.2.3-0.20240930142117-ef04dd443670/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= +github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7/go.mod h1:BMYE1vC/pGmdFSsOJdPrAA0/4gZ0Xo0SxTMdGspBtRo= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 h1:yRk4ektpx/UxwarqAfgxUXLrsYXlaNeP1NOwzHGrK2Q= From 585f7aaa7f550c58c60b02362b9952f51139e489 Mon Sep 17 00:00:00 2001 From: Silas Lenihan <32529249+silaslenihan@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:57:56 -0400 Subject: [PATCH 13/28] Removed SendStrategy from ChainWriter config (#14623) --- core/capabilities/ccip/configs/evm/chain_writer.go | 4 +--- .../ccip/ocrimpls/contract_transmitter_test.go | 5 +---- core/services/relay/evm/chain_writer.go | 8 +------- core/services/relay/evm/types/types.go | 7 ++----- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/core/capabilities/ccip/configs/evm/chain_writer.go b/core/capabilities/ccip/configs/evm/chain_writer.go index 6f8c4a1570b..9bc377ba23e 100644 --- a/core/capabilities/ccip/configs/evm/chain_writer.go +++ b/core/capabilities/ccip/configs/evm/chain_writer.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink-ccip/pkg/consts" - "github.com/smartcontractkit/chainlink/v2/common/txmgr" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -60,8 +59,7 @@ func ChainWriterConfigRaw( }, }, }, - SendStrategy: txmgr.NewSendEveryStrategy(), - MaxGasPrice: maxGasPrice, + MaxGasPrice: maxGasPrice, } } diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 274620926fa..21c9ad01995 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -25,8 +25,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" - txmgrcommon "github.com/smartcontractkit/chainlink/v2/common/txmgr" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" @@ -402,8 +400,7 @@ func chainWriterConfigRaw(fromAddress common.Address, maxGasPrice *assets.Wei) e }, }, }, - SendStrategy: txmgrcommon.NewSendEveryStrategy(), - MaxGasPrice: maxGasPrice, + MaxGasPrice: maxGasPrice, } } diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index d61c17b2ae2..be5575ca3d0 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -44,15 +44,10 @@ func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm ev ge: estimator, maxGasPrice: config.MaxGasPrice, - sendStrategy: txmgr.NewSendEveryStrategy(), contracts: config.Contracts, parsedContracts: &codec.ParsedTypes{EncoderDefs: map[string]types.CodecEntry{}, DecoderDefs: map[string]types.CodecEntry{}}, } - if config.SendStrategy != nil { - w.sendStrategy = config.SendStrategy - } - if err := w.parseContracts(); err != nil { return nil, fmt.Errorf("%w: failed to parse contracts", err) } @@ -74,7 +69,6 @@ type chainWriter struct { ge gas.EvmFeeEstimator maxGasPrice *assets.Wei - sendStrategy txmgrtypes.TxStrategy contracts map[string]*types.ContractConfig parsedContracts *codec.ParsedTypes @@ -135,7 +129,7 @@ func (w *chainWriter) SubmitTransaction(ctx context.Context, contract, method st FeeLimit: gasLimit, Meta: txMeta, IdempotencyKey: &transactionID, - Strategy: w.sendStrategy, + Strategy: txmgr.NewSendEveryStrategy(), Checker: checker, Value: *v, } diff --git a/core/services/relay/evm/types/types.go b/core/services/relay/evm/types/types.go index c1f814c5965..1a824f3d162 100644 --- a/core/services/relay/evm/types/types.go +++ b/core/services/relay/evm/types/types.go @@ -13,8 +13,6 @@ import ( ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" - "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -26,9 +24,8 @@ import ( ) type ChainWriterConfig struct { - Contracts map[string]*ContractConfig - SendStrategy txmgrtypes.TxStrategy - MaxGasPrice *assets.Wei + Contracts map[string]*ContractConfig + MaxGasPrice *assets.Wei } type ContractConfig struct { From eec0ff946986dfbeeb46ed3d311ff9b8d0e21735 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 1 Oct 2024 14:16:42 -0400 Subject: [PATCH 14/28] ccip: chain reader router discovery function. (#14607) * Add new ccip chain reader configs. * Add changeset. * Fix lint * chainlink-ccip main branch * Fix config and add a test. * Update core/capabilities/ccip/configs/evm/contract_reader.go Co-authored-by: Makram * tidy --------- Co-authored-by: Makram --- .changeset/lemon-apples-explain.md | 5 +++ .../ccip/configs/evm/contract_reader.go | 38 ++++++++++++++++--- .../ccip/configs/evm/init_test.go | 12 ++++++ core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 +- go.mod | 2 +- go.sum | 4 +- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 +- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 +- 11 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 .changeset/lemon-apples-explain.md create mode 100644 core/capabilities/ccip/configs/evm/init_test.go diff --git a/.changeset/lemon-apples-explain.md b/.changeset/lemon-apples-explain.md new file mode 100644 index 00000000000..75f2b874fc4 --- /dev/null +++ b/.changeset/lemon-apples-explain.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#internal update ccip chainreader config. diff --git a/core/capabilities/ccip/configs/evm/contract_reader.go b/core/capabilities/ccip/configs/evm/contract_reader.go index fbfbf260b8a..a158e5bd5d4 100644 --- a/core/capabilities/ccip/configs/evm/contract_reader.go +++ b/core/capabilities/ccip/configs/evm/contract_reader.go @@ -93,11 +93,11 @@ var DestReaderConfig = evmrelaytypes.ChainReaderConfig{ ChainSpecificName: mustGetMethodName("getLatestPriceSequenceNumber", offrampABI), ReadType: evmrelaytypes.Method, }, - consts.MethodNameOfframpGetStaticConfig: { + consts.MethodNameOffRampGetStaticConfig: { ChainSpecificName: mustGetMethodName("getStaticConfig", offrampABI), ReadType: evmrelaytypes.Method, }, - consts.MethodNameOfframpGetDynamicConfig: { + consts.MethodNameOffRampGetDynamicConfig: { ChainSpecificName: mustGetMethodName("getDynamicConfig", offrampABI), ReadType: evmrelaytypes.Method, }, @@ -113,6 +113,16 @@ var DestReaderConfig = evmrelaytypes.ChainReaderConfig{ ChainSpecificName: mustGetEventName(consts.EventNameExecutionStateChanged, offrampABI), ReadType: evmrelaytypes.Event, }, + //nolint:staticcheck // TODO: remove deprecated config. + consts.MethodNameOfframpGetStaticConfig: { + ChainSpecificName: mustGetMethodName("getStaticConfig", offrampABI), + ReadType: evmrelaytypes.Method, + }, + //nolint:staticcheck // TODO: remove deprecated config. + consts.MethodNameOfframpGetDynamicConfig: { + ChainSpecificName: mustGetMethodName("getDynamicConfig", offrampABI), + ReadType: evmrelaytypes.Method, + }, }, }, consts.ContractNameNonceManager: { @@ -198,18 +208,34 @@ var SourceReaderConfig = evmrelaytypes.ChainReaderConfig{ ChainSpecificName: mustGetMethodName("getExpectedNextSequenceNumber", onrampABI), ReadType: evmrelaytypes.Method, }, + consts.EventNameCCIPMessageSent: { + ChainSpecificName: mustGetEventName("CCIPMessageSent", onrampABI), + ReadType: evmrelaytypes.Event, + }, + consts.MethodNameOnRampGetStaticConfig: { + ChainSpecificName: mustGetMethodName("getStaticConfig", onrampABI), + ReadType: evmrelaytypes.Method, + }, + consts.MethodNameOnRampGetDynamicConfig: { + ChainSpecificName: mustGetMethodName("getDynamicConfig", onrampABI), + ReadType: evmrelaytypes.Method, + }, + // TODO: swap with const. + "OnRampGetDestChainConfig": { + //consts.MethodNameOnRampGetDestChainConfig: { + ChainSpecificName: mustGetMethodName("getDestChainConfig", onrampABI), + ReadType: evmrelaytypes.Method, + }, + //nolint:staticcheck // TODO: remove deprecated config. consts.MethodNameOnrampGetStaticConfig: { ChainSpecificName: mustGetMethodName("getStaticConfig", onrampABI), ReadType: evmrelaytypes.Method, }, + //nolint:staticcheck // TODO: remove deprecated config. consts.MethodNameOnrampGetDynamicConfig: { ChainSpecificName: mustGetMethodName("getDynamicConfig", onrampABI), ReadType: evmrelaytypes.Method, }, - consts.EventNameCCIPMessageSent: { - ChainSpecificName: mustGetEventName("CCIPMessageSent", onrampABI), - ReadType: evmrelaytypes.Event, - }, }, }, }, diff --git a/core/capabilities/ccip/configs/evm/init_test.go b/core/capabilities/ccip/configs/evm/init_test.go new file mode 100644 index 00000000000..010592b6dc9 --- /dev/null +++ b/core/capabilities/ccip/configs/evm/init_test.go @@ -0,0 +1,12 @@ +package evm + +import ( + "testing" +) + +func TestConfig(t *testing.T) { + // Config is created during initialization, the following functions may panic: + // MustGetABI + // mustGetMethodName + // mustGetEventName +} diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 290d8d3dba8..4d9e4fa5ebe 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -271,7 +271,7 @@ require ( github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.23 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 29ef1473ef0..b3244647a49 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1081,8 +1081,8 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 h1:X07jKFIakpR7UyG/BnQ8wKZz395LsEcowqyQgDKHcT8= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= diff --git a/go.mod b/go.mod index 7d6f82d28da..bc65ed6296f 100644 --- a/go.mod +++ b/go.mod @@ -74,7 +74,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 diff --git a/go.sum b/go.sum index 32f170ae2e6..c528f8999cd 100644 --- a/go.sum +++ b/go.sum @@ -1042,8 +1042,8 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 h1:X07jKFIakpR7UyG/BnQ8wKZz395LsEcowqyQgDKHcT8= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index fe55b20b51a..708cba54dd2 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -39,7 +39,7 @@ require ( github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 411228d2b2d..1476373ca65 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1423,8 +1423,8 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 h1:X07jKFIakpR7UyG/BnQ8wKZz395LsEcowqyQgDKHcT8= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index cf5257669e8..57f186971a9 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -382,7 +382,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.23 // indirect github.com/smartcontractkit/chainlink-automation v1.0.4 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index e79a8455488..8e0d4eb37cf 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1397,8 +1397,8 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd h1:16Hwnz4hdmWKOy5qVH9wHfyT1XXM0k31M3naexwzpVo= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930150148-1c731b9602dd/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 h1:X07jKFIakpR7UyG/BnQ8wKZz395LsEcowqyQgDKHcT8= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= From e724e5f082bf0c36f23291b97db6ca5a3f0b196c Mon Sep 17 00:00:00 2001 From: Sri Kidambi <1702865+kidambisrinivas@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:28:40 +0100 Subject: [PATCH 15/28] Log Event Trigger Capability Development: Part 2 (#14609) * Receive log as values.Value to preserve type info * Test cursor usage of LogEventTrigger capability * Added JSON schema for log event trigger capability * Remove old override and generate log event capability * Update JSON schema constraints * Use types from generated pkg to reduce code duplication * Generate SDK helper after JSON schema change --------- Co-authored-by: Ryan Tinianov --- .../logeventcap/event_trigger-schema.json | 75 ++++++++ .../logeventcap/event_trigger_generated.go | 160 ++++++++++++++++++ .../triggers/logevent/logeventcap/gen.go | 5 + .../logeventcaptest/trigger_mock_generated.go | 17 ++ .../logeventcap/trigger_builders_generated.go | 156 +++++++++++++++++ .../capabilities/triggers/logevent/service.go | 11 +- .../capabilities/triggers/logevent/trigger.go | 18 +- .../capabilities/log_event_trigger_test.go | 100 +++++++++-- .../capabilities/testutils/chain_reader.go | 6 +- go.mod | 15 +- go.sum | 15 ++ .../capabilities/log-event-trigger/main.go | 27 ++- 12 files changed, 553 insertions(+), 52 deletions(-) create mode 100644 core/capabilities/triggers/logevent/logeventcap/event_trigger-schema.json create mode 100644 core/capabilities/triggers/logevent/logeventcap/event_trigger_generated.go create mode 100644 core/capabilities/triggers/logevent/logeventcap/gen.go create mode 100644 core/capabilities/triggers/logevent/logeventcap/logeventcaptest/trigger_mock_generated.go create mode 100644 core/capabilities/triggers/logevent/logeventcap/trigger_builders_generated.go diff --git a/core/capabilities/triggers/logevent/logeventcap/event_trigger-schema.json b/core/capabilities/triggers/logevent/logeventcap/event_trigger-schema.json new file mode 100644 index 00000000000..a60d8823582 --- /dev/null +++ b/core/capabilities/triggers/logevent/logeventcap/event_trigger-schema.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent/logeventcap/log-event-trigger", + "$defs": { + "head": { + "type": "object", + "properties": { + "Height": { + "type": "string", + "minLength": 1 + }, + "Hash": { + "type": "string", + "minLength": 1 + }, + "Timestamp": { + "type": "integer", + "minimum": 0 + } + } + }, + "config": { + "type": "object", + "properties": { + "contractName": { + "type": "string", + "minLength": 1 + }, + "contractAddress": { + "type": "string", + "minLength": 1 + }, + "contractEventName": { + "type": "string", + "minLength": 1 + }, + "contractReaderConfig": { + "type": "object", + "properties": { + "contracts": { + "type": "object" + } + }, + "required": ["contracts"] + } + }, + "required": ["contractName", "contractAddress", "contractEventName", "contractReaderConfig"] + }, + "output": { + "type": "object", + "properties": { + "Cursor": { + "type": "string", + "minLength": 1 + }, + "Head": { + "$ref": "#/$defs/head" + }, + "Data": { + "type": "object" + } + }, + "required": ["Cursor", "Head", "Data"] + } + }, + "type": "object", + "properties": { + "Config": { + "$ref": "#/$defs/config" + }, + "Outputs": { + "$ref": "#/$defs/output" + } + } + } \ No newline at end of file diff --git a/core/capabilities/triggers/logevent/logeventcap/event_trigger_generated.go b/core/capabilities/triggers/logevent/logeventcap/event_trigger_generated.go new file mode 100644 index 00000000000..23376958309 --- /dev/null +++ b/core/capabilities/triggers/logevent/logeventcap/event_trigger_generated.go @@ -0,0 +1,160 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package logeventcap + +import ( + "encoding/json" + "fmt" +) + +type Config struct { + // ContractAddress corresponds to the JSON schema field "contractAddress". + ContractAddress string `json:"contractAddress" yaml:"contractAddress" mapstructure:"contractAddress"` + + // ContractEventName corresponds to the JSON schema field "contractEventName". + ContractEventName string `json:"contractEventName" yaml:"contractEventName" mapstructure:"contractEventName"` + + // ContractName corresponds to the JSON schema field "contractName". + ContractName string `json:"contractName" yaml:"contractName" mapstructure:"contractName"` + + // ContractReaderConfig corresponds to the JSON schema field + // "contractReaderConfig". + ContractReaderConfig ConfigContractReaderConfig `json:"contractReaderConfig" yaml:"contractReaderConfig" mapstructure:"contractReaderConfig"` +} + +type ConfigContractReaderConfig struct { + // Contracts corresponds to the JSON schema field "contracts". + Contracts ConfigContractReaderConfigContracts `json:"contracts" yaml:"contracts" mapstructure:"contracts"` +} + +type ConfigContractReaderConfigContracts map[string]interface{} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConfigContractReaderConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["contracts"]; raw != nil && !ok { + return fmt.Errorf("field contracts in ConfigContractReaderConfig: required") + } + type Plain ConfigContractReaderConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ConfigContractReaderConfig(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Config) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["contractAddress"]; raw != nil && !ok { + return fmt.Errorf("field contractAddress in Config: required") + } + if _, ok := raw["contractEventName"]; raw != nil && !ok { + return fmt.Errorf("field contractEventName in Config: required") + } + if _, ok := raw["contractName"]; raw != nil && !ok { + return fmt.Errorf("field contractName in Config: required") + } + if _, ok := raw["contractReaderConfig"]; raw != nil && !ok { + return fmt.Errorf("field contractReaderConfig in Config: required") + } + type Plain Config + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if len(plain.ContractAddress) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "contractAddress", 1) + } + if len(plain.ContractEventName) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "contractEventName", 1) + } + if len(plain.ContractName) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "contractName", 1) + } + *j = Config(plain) + return nil +} + +type Head struct { + // Hash corresponds to the JSON schema field "Hash". + Hash *string `json:"Hash,omitempty" yaml:"Hash,omitempty" mapstructure:"Hash,omitempty"` + + // Height corresponds to the JSON schema field "Height". + Height *string `json:"Height,omitempty" yaml:"Height,omitempty" mapstructure:"Height,omitempty"` + + // Timestamp corresponds to the JSON schema field "Timestamp". + Timestamp *uint64 `json:"Timestamp,omitempty" yaml:"Timestamp,omitempty" mapstructure:"Timestamp,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Head) UnmarshalJSON(b []byte) error { + type Plain Head + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if plain.Hash != nil && len(*plain.Hash) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "Hash", 1) + } + if plain.Height != nil && len(*plain.Height) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "Height", 1) + } + *j = Head(plain) + return nil +} + +type Output struct { + // Cursor corresponds to the JSON schema field "Cursor". + Cursor string `json:"Cursor" yaml:"Cursor" mapstructure:"Cursor"` + + // Data corresponds to the JSON schema field "Data". + Data OutputData `json:"Data" yaml:"Data" mapstructure:"Data"` + + // Head corresponds to the JSON schema field "Head". + Head Head `json:"Head" yaml:"Head" mapstructure:"Head"` +} + +type OutputData map[string]interface{} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Output) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["Cursor"]; raw != nil && !ok { + return fmt.Errorf("field Cursor in Output: required") + } + if _, ok := raw["Data"]; raw != nil && !ok { + return fmt.Errorf("field Data in Output: required") + } + if _, ok := raw["Head"]; raw != nil && !ok { + return fmt.Errorf("field Head in Output: required") + } + type Plain Output + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if len(plain.Cursor) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "Cursor", 1) + } + *j = Output(plain) + return nil +} + +type Trigger struct { + // Config corresponds to the JSON schema field "Config". + Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" mapstructure:"Config,omitempty"` + + // Outputs corresponds to the JSON schema field "Outputs". + Outputs *Output `json:"Outputs,omitempty" yaml:"Outputs,omitempty" mapstructure:"Outputs,omitempty"` +} diff --git a/core/capabilities/triggers/logevent/logeventcap/gen.go b/core/capabilities/triggers/logevent/logeventcap/gen.go new file mode 100644 index 00000000000..09655a9a113 --- /dev/null +++ b/core/capabilities/triggers/logevent/logeventcap/gen.go @@ -0,0 +1,5 @@ +package logeventcap + +import _ "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd" // Required so that the tool is available to be run in go generate below. + +//go:generate go run github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/generate-types --dir $GOFILE diff --git a/core/capabilities/triggers/logevent/logeventcap/logeventcaptest/trigger_mock_generated.go b/core/capabilities/triggers/logevent/logeventcap/logeventcaptest/trigger_mock_generated.go new file mode 100644 index 00000000000..f91dbc6e2b3 --- /dev/null +++ b/core/capabilities/triggers/logevent/logeventcap/logeventcaptest/trigger_mock_generated.go @@ -0,0 +1,17 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package logeventcaptest + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk/testutils" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent/logeventcap" +) + +// Trigger registers a new capability mock with the runner +func Trigger(runner *testutils.Runner, id string, fn func() (logeventcap.Output, error)) *testutils.TriggerMock[logeventcap.Output] { + mock := testutils.MockTrigger[logeventcap.Output](id, fn) + runner.MockCapability(id, nil, mock) + return mock +} diff --git a/core/capabilities/triggers/logevent/logeventcap/trigger_builders_generated.go b/core/capabilities/triggers/logevent/logeventcap/trigger_builders_generated.go new file mode 100644 index 00000000000..8788f005d63 --- /dev/null +++ b/core/capabilities/triggers/logevent/logeventcap/trigger_builders_generated.go @@ -0,0 +1,156 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package logeventcap + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk" +) + +func (cfg Config) New(w *sdk.WorkflowSpecFactory, id string) OutputCap { + ref := "trigger" + def := sdk.StepDefinition{ + ID: id, Ref: ref, + Inputs: sdk.StepInputs{}, + Config: map[string]any{ + "contractAddress": cfg.ContractAddress, + "contractEventName": cfg.ContractEventName, + "contractName": cfg.ContractName, + "contractReaderConfig": cfg.ContractReaderConfig, + }, + CapabilityType: capabilities.CapabilityTypeTrigger, + } + + step := sdk.Step[Output]{Definition: def} + return OutputCapFromStep(w, step) +} + +type HeadCap interface { + sdk.CapDefinition[Head] + Hash() sdk.CapDefinition[string] + Height() sdk.CapDefinition[string] + Timestamp() sdk.CapDefinition[uint64] + private() +} + +// HeadCapFromStep should only be called from generated code to assure type safety +func HeadCapFromStep(w *sdk.WorkflowSpecFactory, step sdk.Step[Head]) HeadCap { + raw := step.AddTo(w) + return &head{CapDefinition: raw} +} + +type head struct { + sdk.CapDefinition[Head] +} + +func (*head) private() {} +func (c *head) Hash() sdk.CapDefinition[string] { + return sdk.AccessField[Head, string](c.CapDefinition, "Hash") +} +func (c *head) Height() sdk.CapDefinition[string] { + return sdk.AccessField[Head, string](c.CapDefinition, "Height") +} +func (c *head) Timestamp() sdk.CapDefinition[uint64] { + return sdk.AccessField[Head, uint64](c.CapDefinition, "Timestamp") +} + +func NewHeadFromFields( + hash sdk.CapDefinition[string], + height sdk.CapDefinition[string], + timestamp sdk.CapDefinition[uint64]) HeadCap { + return &simpleHead{ + CapDefinition: sdk.ComponentCapDefinition[Head]{ + "Hash": hash.Ref(), + "Height": height.Ref(), + "Timestamp": timestamp.Ref(), + }, + hash: hash, + height: height, + timestamp: timestamp, + } +} + +type simpleHead struct { + sdk.CapDefinition[Head] + hash sdk.CapDefinition[string] + height sdk.CapDefinition[string] + timestamp sdk.CapDefinition[uint64] +} + +func (c *simpleHead) Hash() sdk.CapDefinition[string] { + return c.hash +} +func (c *simpleHead) Height() sdk.CapDefinition[string] { + return c.height +} +func (c *simpleHead) Timestamp() sdk.CapDefinition[uint64] { + return c.timestamp +} + +func (c *simpleHead) private() {} + +type OutputCap interface { + sdk.CapDefinition[Output] + Cursor() sdk.CapDefinition[string] + Data() OutputDataCap + Head() HeadCap + private() +} + +// OutputCapFromStep should only be called from generated code to assure type safety +func OutputCapFromStep(w *sdk.WorkflowSpecFactory, step sdk.Step[Output]) OutputCap { + raw := step.AddTo(w) + return &output{CapDefinition: raw} +} + +type output struct { + sdk.CapDefinition[Output] +} + +func (*output) private() {} +func (c *output) Cursor() sdk.CapDefinition[string] { + return sdk.AccessField[Output, string](c.CapDefinition, "Cursor") +} +func (c *output) Data() OutputDataCap { + return OutputDataCap(sdk.AccessField[Output, OutputData](c.CapDefinition, "Data")) +} +func (c *output) Head() HeadCap { + return &head{CapDefinition: sdk.AccessField[Output, Head](c.CapDefinition, "Head")} +} + +func NewOutputFromFields( + cursor sdk.CapDefinition[string], + data OutputDataCap, + head HeadCap) OutputCap { + return &simpleOutput{ + CapDefinition: sdk.ComponentCapDefinition[Output]{ + "Cursor": cursor.Ref(), + "Data": data.Ref(), + "Head": head.Ref(), + }, + cursor: cursor, + data: data, + head: head, + } +} + +type simpleOutput struct { + sdk.CapDefinition[Output] + cursor sdk.CapDefinition[string] + data OutputDataCap + head HeadCap +} + +func (c *simpleOutput) Cursor() sdk.CapDefinition[string] { + return c.cursor +} +func (c *simpleOutput) Data() OutputDataCap { + return c.data +} +func (c *simpleOutput) Head() HeadCap { + return c.head +} + +func (c *simpleOutput) private() {} + +type OutputDataCap sdk.CapDefinition[OutputData] diff --git a/core/capabilities/triggers/logevent/service.go b/core/capabilities/triggers/logevent/service.go index 7ed4855e097..52e56d991f9 100644 --- a/core/capabilities/triggers/logevent/service.go +++ b/core/capabilities/triggers/logevent/service.go @@ -9,6 +9,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types/core" + + "github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent/logeventcap" ) const ID = "log-event-trigger-%s-%s@1.0.0" @@ -24,7 +26,7 @@ type Input struct { type TriggerService struct { services.StateMachine capabilities.CapabilityInfo - capabilities.Validator[RequestConfig, Input, capabilities.TriggerResponse] + capabilities.Validator[logeventcap.Config, Input, capabilities.TriggerResponse] lggr logger.Logger triggers CapabilitiesStore[logEventTrigger, capabilities.TriggerResponse] relayer core.Relayer @@ -69,7 +71,7 @@ func NewTriggerService(ctx context.Context, if err != nil { return s, err } - s.Validator = capabilities.NewValidator[RequestConfig, Input, capabilities.TriggerResponse](capabilities.ValidatorArgs{Info: s.CapabilityInfo}) + s.Validator = capabilities.NewValidator[logeventcap.Config, Input, capabilities.TriggerResponse](capabilities.ValidatorArgs{Info: s.CapabilityInfo}) return s, nil } @@ -83,7 +85,8 @@ func (s *TriggerService) Info(ctx context.Context) (capabilities.CapabilityInfo, // Register a new trigger // Can register triggers before the service is actively scheduling -func (s *TriggerService) RegisterTrigger(ctx context.Context, req capabilities.TriggerRegistrationRequest) (<-chan capabilities.TriggerResponse, error) { +func (s *TriggerService) RegisterTrigger(ctx context.Context, + req capabilities.TriggerRegistrationRequest) (<-chan capabilities.TriggerResponse, error) { if req.Config == nil { return nil, errors.New("config is required to register a log event trigger") } @@ -104,7 +107,7 @@ func (s *TriggerService) RegisterTrigger(ctx context.Context, req capabilities.T }) }) if !ok { - return nil, fmt.Errorf("cannot create new trigger since LogEventTriggerService has been stopped") + return nil, fmt.Errorf("cannot create new trigger since LogEventTriggerCapabilityService has been stopped") } if err != nil { return nil, fmt.Errorf("create new trigger failed %w", err) diff --git a/core/capabilities/triggers/logevent/trigger.go b/core/capabilities/triggers/logevent/trigger.go index 9a0e1d036c7..379d9483c24 100644 --- a/core/capabilities/triggers/logevent/trigger.go +++ b/core/capabilities/triggers/logevent/trigger.go @@ -15,17 +15,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink-common/pkg/values" -) -// Log Event Trigger Capability Request Config Details -type RequestConfig struct { - ContractName string `json:"contractName"` - ContractAddress string `json:"contractAddress"` - ContractEventName string `json:"contractEventName"` - // Log Event Trigger capability takes in a []byte as ContractReaderConfig - // to not depend on evm ChainReaderConfig type and be chain agnostic - ContractReaderConfig map[string]any `json:"contractReaderConfig"` -} + "github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent/logeventcap" +) // LogEventTrigger struct to listen for Contract events using ContractReader gRPC client // in a loop with a periodic delay of pollPeriod milliseconds, which is specified in @@ -35,7 +27,7 @@ type logEventTrigger struct { lggr logger.Logger // Contract address and Event Signature to monitor for - reqConfig *RequestConfig + reqConfig *logeventcap.Config contractReader types.ContractReader relayer core.Relayer startBlockNum uint64 @@ -51,7 +43,7 @@ type logEventTrigger struct { func newLogEventTrigger(ctx context.Context, lggr logger.Logger, workflowID string, - reqConfig *RequestConfig, + reqConfig *logeventcap.Config, logEventConfig Config, relayer core.Relayer) (*logEventTrigger, chan capabilities.TriggerResponse, error) { jsonBytes, err := json.Marshal(reqConfig.ContractReaderConfig) @@ -124,7 +116,7 @@ func (l *logEventTrigger) listen() { // Listen for events from lookbackPeriod var logs []types.Sequence var err error - logData := make(map[string]any) + var logData values.Value cursor := "" limitAndSort := query.LimitAndSort{ SortBy: []query.SortBy{query.NewSortByTimestamp(query.Asc)}, diff --git a/core/services/relay/evm/capabilities/log_event_trigger_test.go b/core/services/relay/evm/capabilities/log_event_trigger_test.go index f2104529b7f..e196ae5bf80 100644 --- a/core/services/relay/evm/capabilities/log_event_trigger_test.go +++ b/core/services/relay/evm/capabilities/log_event_trigger_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" commonmocks "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" @@ -19,6 +20,7 @@ import ( // Test for Log Event Trigger Capability happy path for EVM func TestLogEventTriggerEVMHappyPath(t *testing.T) { + t.Parallel() th := testutils.NewContractReaderTH(t) logEventConfig := logevent.Config{ @@ -59,31 +61,97 @@ func TestLogEventTriggerEVMHappyPath(t *testing.T) { log1Ch, err := logEventTriggerService.RegisterTrigger(ctx, th.LogEmitterRegRequest) require.NoError(t, err) - expectedLogVal := int64(10) + emitLogTxnAndWaitForLog(t, th, log1Ch, []*big.Int{big.NewInt(10)}) +} + +// Test if Log Event Trigger Capability is able to receive only new logs +// by using cursor and does not receive duplicate logs +func TestLogEventTriggerCursorNewLogs(t *testing.T) { + t.Parallel() + th := testutils.NewContractReaderTH(t) + + logEventConfig := logevent.Config{ + ChainID: th.BackendTH.ChainID.String(), + Network: "evm", + LookbackBlocks: 1000, + PollPeriod: 1000, + } + + // Create a new contract reader to return from mock relayer + ctx := coretestutils.Context(t) + + // Fetch latest head from simulated backend to return from mock relayer + height, err := th.BackendTH.EVMClient.LatestBlockHeight(ctx) + require.NoError(t, err) + block, err := th.BackendTH.EVMClient.BlockByNumber(ctx, height) + require.NoError(t, err) + + // Mock relayer to return a New ContractReader instead of gRPC client of a ContractReader + relayer := commonmocks.NewRelayer(t) + relayer.On("NewContractReader", mock.Anything, th.LogEmitterContractReaderCfg).Return(th.LogEmitterContractReader, nil).Once() + relayer.On("LatestHead", mock.Anything).Return(commontypes.Head{ + Height: height.String(), + Hash: block.Hash().Bytes(), + Timestamp: block.Time(), + }, nil).Once() + + // Create Log Event Trigger Service and register trigger + logEventTriggerService, err := logevent.NewTriggerService(ctx, + th.BackendTH.Lggr, + relayer, + logEventConfig) + require.NoError(t, err) + + // Start the service + servicetest.Run(t, logEventTriggerService) + + log1Ch, err := logEventTriggerService.RegisterTrigger(ctx, th.LogEmitterRegRequest) + require.NoError(t, err) - // Send a blockchain transaction that emits logs + emitLogTxnAndWaitForLog(t, th, log1Ch, []*big.Int{big.NewInt(10)}) + + // This confirms that the cursor being tracked by log event trigger capability + // works correctly and does not send old logs again as duplicates to the + // callback channel log1Ch + emitLogTxnAndWaitForLog(t, th, log1Ch, []*big.Int{big.NewInt(11), big.NewInt(12)}) +} + +// Send a transaction to EmitLog contract to emit Log1 events with given +// input parameters and wait for those logs to be received from relayer +// and ContractReader's QueryKey APIs used by Log Event Trigger +func emitLogTxnAndWaitForLog(t *testing.T, + th *testutils.ContractReaderTH, + log1Ch <-chan capabilities.TriggerResponse, + expectedLogVals []*big.Int) { done := make(chan struct{}) - t.Cleanup(func() { <-done }) + var err error go func() { defer close(done) _, err = - th.LogEmitterContract.EmitLog1(th.BackendTH.ContractsOwner, []*big.Int{big.NewInt(expectedLogVal)}) + th.LogEmitterContract.EmitLog1(th.BackendTH.ContractsOwner, expectedLogVals) assert.NoError(t, err) th.BackendTH.Backend.Commit() th.BackendTH.Backend.Commit() th.BackendTH.Backend.Commit() }() - // Wait for logs with a timeout - _, output, err := testutils.WaitForLog(th.BackendTH.Lggr, log1Ch, 15*time.Second) - require.NoError(t, err) - th.BackendTH.Lggr.Infow("EmitLog", "output", output) - // Verify if valid cursor is returned - cursor, err := testutils.GetStrVal(output, "Cursor") - require.NoError(t, err) - require.True(t, len(cursor) > 60) - // Verify if Arg0 is correct - actualLogVal, err := testutils.GetBigIntValL2(output, "Data", "Arg0") - require.NoError(t, err) - require.Equal(t, expectedLogVal, actualLogVal.Int64()) + for _, expectedLogVal := range expectedLogVals { + // Wait for logs with a timeout + _, output, err := testutils.WaitForLog(th.BackendTH.Lggr, log1Ch, 15*time.Second) + require.NoError(t, err) + th.BackendTH.Lggr.Infow("EmitLog", "output", output) + + // Verify if valid cursor is returned + cursor, err := testutils.GetStrVal(output, "Cursor") + require.NoError(t, err) + require.True(t, len(cursor) > 60) + + // Verify if Arg0 is correct + actualLogVal, err := testutils.GetBigIntValL2(output, "Data", "Arg0") + require.NoError(t, err) + + require.Equal(t, expectedLogVal.Int64(), actualLogVal.Int64()) + } + + <-done } diff --git a/core/services/relay/evm/capabilities/testutils/chain_reader.go b/core/services/relay/evm/capabilities/testutils/chain_reader.go index 3f0bf82da81..57dc21c426d 100644 --- a/core/services/relay/evm/capabilities/testutils/chain_reader.go +++ b/core/services/relay/evm/capabilities/testutils/chain_reader.go @@ -13,7 +13,7 @@ import ( commoncaps "github.com/smartcontractkit/chainlink-common/pkg/capabilities" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" commonvalues "github.com/smartcontractkit/chainlink-common/pkg/values" - "github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/triggers/logevent/logeventcap" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/log_emitter" coretestutils "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -44,7 +44,7 @@ func NewContractReaderTH(t *testing.T) *ContractReaderTH { require.NoError(t, err) // Create new contract reader - reqConfig := logevent.RequestConfig{ + reqConfig := logeventcap.Config{ ContractName: "LogEmitter", ContractAddress: logEmitterAddress.Hex(), ContractEventName: "Log1", @@ -72,7 +72,7 @@ func NewContractReaderTH(t *testing.T) *ContractReaderTH { // and be chain agnostic contractReaderCfgBytes, err := json.Marshal(contractReaderCfg) require.NoError(t, err) - contractReaderCfgMap := make(map[string]any) + var contractReaderCfgMap logeventcap.ConfigContractReaderConfig err = json.Unmarshal(contractReaderCfgBytes, &contractReaderCfgMap) require.NoError(t, err) // Encode the config map as JSON to specify in the expected call in mocked object diff --git a/go.mod b/go.mod index bc65ed6296f..c192323e61d 100644 --- a/go.mod +++ b/go.mod @@ -144,6 +144,7 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect + github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -211,6 +212,7 @@ require ( github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.3 // indirect @@ -245,6 +247,7 @@ require ( github.com/huandu/skiplist v1.2.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/huin/goupnp v1.3.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect @@ -273,6 +276,7 @@ require ( github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect @@ -294,6 +298,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sanity-io/litter v1.5.5 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/sethvargo/go-retry v0.2.4 // indirect @@ -348,6 +353,7 @@ require ( go.uber.org/ratelimit v0.3.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.188.0 // indirect google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect @@ -361,12 +367,7 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace ( - // until merged upstream: https://github.com/omissis/go-jsonschema/pull/264 - github.com/atombender/go-jsonschema => github.com/nolag/go-jsonschema v0.16.0-rtinianov - - // replicating the replace directive on cosmos SDK - github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 -) +// replicating the replace directive on cosmos SDK +replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 diff --git a/go.sum b/go.sum index c528f8999cd..89a1b45d722 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c h1:cxQVoh6kY+c4b0HUchHjGWBI8288VhH50qxKG3hdEg0= +github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c/go.mod h1:3XzxudkrYVUvbduN/uI2fl4lSrMSzU0+3RCu2mpnfx8= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -277,6 +279,7 @@ github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuA github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e h1:5jVSh2l/ho6ajWhSPNN84eHEdq3dp0T7+f6r3Tc6hsk= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e/go.mod h1:IJgIiGUARc4aOr4bOQ85klmjsShkEEfiRc6q/yBSfo8= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -445,6 +448,8 @@ github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -664,6 +669,8 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -847,6 +854,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -938,6 +947,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1011,6 +1021,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= @@ -1105,6 +1117,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1581,6 +1594,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/plugins/cmd/capabilities/log-event-trigger/main.go b/plugins/cmd/capabilities/log-event-trigger/main.go index 8abecf54aeb..7cf66f9c847 100644 --- a/plugins/cmd/capabilities/log-event-trigger/main.go +++ b/plugins/cmd/capabilities/log-event-trigger/main.go @@ -20,9 +20,9 @@ const ( ) type LogEventTriggerGRPCService struct { - trigger capabilities.TriggerCapability - s *loop.Server - config logevent.Config + triggerService *logevent.TriggerService + s *loop.Server + config logevent.Config } func main() { @@ -53,6 +53,10 @@ func (cs *LogEventTriggerGRPCService) Start(ctx context.Context) error { } func (cs *LogEventTriggerGRPCService) Close() error { + err := cs.triggerService.Close() + if err != nil { + return fmt.Errorf("error closing trigger service for chainID %s: %w", cs.config.ChainID, err) + } return nil } @@ -69,7 +73,7 @@ func (cs *LogEventTriggerGRPCService) Name() string { } func (cs *LogEventTriggerGRPCService) Infos(ctx context.Context) ([]capabilities.CapabilityInfo, error) { - triggerInfo, err := cs.trigger.Info(ctx) + triggerInfo, err := cs.triggerService.Info(ctx) if err != nil { return nil, err } @@ -94,23 +98,28 @@ func (cs *LogEventTriggerGRPCService) Initialise( var logEventConfig logevent.Config err := json.Unmarshal([]byte(config), &logEventConfig) if err != nil { - return fmt.Errorf("error decoding log_event_trigger config: %v", err) + return fmt.Errorf("error decoding log_event_trigger config: %w", err) } relayID := types.NewRelayID(logEventConfig.Network, logEventConfig.ChainID) relayer, err := relayerSet.Get(ctx, relayID) if err != nil { - return fmt.Errorf("error fetching relayer for chainID %s from relayerSet: %v", logEventConfig.ChainID, err) + return fmt.Errorf("error fetching relayer for chainID %s from relayerSet: %w", logEventConfig.ChainID, err) } // Set relayer and trigger in LogEventTriggerGRPCService cs.config = logEventConfig - cs.trigger, err = logevent.NewTriggerService(ctx, cs.s.Logger, relayer, logEventConfig) + triggerService, err := logevent.NewTriggerService(ctx, cs.s.Logger, relayer, logEventConfig) + if err != nil { + return fmt.Errorf("error creating trigger service for chainID %s: %w", logEventConfig.ChainID, err) + } + err = triggerService.Start(ctx) if err != nil { - return fmt.Errorf("error creating new trigger for chainID %s: %v", logEventConfig.ChainID, err) + return fmt.Errorf("error starting trigger service for chainID %s: %w", logEventConfig.ChainID, err) } + cs.triggerService = triggerService - if err := capabilityRegistry.Add(ctx, cs.trigger); err != nil { + if err := capabilityRegistry.Add(ctx, cs.triggerService); err != nil { return fmt.Errorf("error when adding cron trigger to the registry: %w", err) } From be774f00a961d9a7361d9ae5b10c97996f7ab164 Mon Sep 17 00:00:00 2001 From: Mateusz Sekara Date: Wed, 2 Oct 2024 15:38:39 +0200 Subject: [PATCH 16/28] CCIP-3605 Registering CCTP/USDC events in ChainReader (#14624) * Using shared config for USDCReader * Merging USDC chain reader config * Changelog * Bumping to the main version * Bumping to the main version --- .changeset/metal-pots-lie.md | 5 ++++ .../usdcreader/usdcreader_test.go | 21 ++------------- .../ccip/configs/evm/contract_reader.go | 17 ++++++++++++ core/capabilities/ccip/configs/evm/usdc.go | 26 +++++++++++++++++++ .../capabilities/ccip/oraclecreator/plugin.go | 16 ++++++++++++ core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 +-- go.mod | 2 +- go.sum | 4 +-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 +-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 +-- 13 files changed, 78 insertions(+), 31 deletions(-) create mode 100644 .changeset/metal-pots-lie.md create mode 100644 core/capabilities/ccip/configs/evm/usdc.go diff --git a/.changeset/metal-pots-lie.md b/.changeset/metal-pots-lie.md new file mode 100644 index 00000000000..bfa498cd4ac --- /dev/null +++ b/.changeset/metal-pots-lie.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Registering USDC/CCTP events in the ChainReader during oracle creation #internal diff --git a/core/capabilities/ccip/ccip_integration_tests/usdcreader/usdcreader_test.go b/core/capabilities/ccip/ccip_integration_tests/usdcreader/usdcreader_test.go index 8d26d2b395a..007dcb3e2ae 100644 --- a/core/capabilities/ccip/ccip_integration_tests/usdcreader/usdcreader_test.go +++ b/core/capabilities/ccip/ccip_integration_tests/usdcreader/usdcreader_test.go @@ -20,13 +20,13 @@ import ( sel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-ccip/execute/exectypes" - "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/chainlink-ccip/pkg/contractreader" "github.com/smartcontractkit/chainlink-ccip/pkg/reader" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/chainlink-common/pkg/types" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" + evmconfig "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" @@ -48,24 +48,7 @@ func Test_USDCReader_MessageHashes(t *testing.T) { polygonChain := cciptypes.ChainSelector(sel.POLYGON_MAINNET.Selector) polygonDomainCCTP := reader.CCTPDestDomains[uint64(polygonChain)] - cfg := evmtypes.ChainReaderConfig{ - Contracts: map[string]evmtypes.ChainContractReader{ - consts.ContractNameCCTPMessageTransmitter: { - ContractPollingFilter: evmtypes.ContractPollingFilter{ - GenericEventNames: []string{consts.EventNameCCTPMessageSent}, - }, - ContractABI: usdc_reader_tester.USDCReaderTesterABI, - Configs: map[string]*evmtypes.ChainReaderDefinition{ - consts.EventNameCCTPMessageSent: { - ChainSpecificName: consts.EventNameCCTPMessageSent, - ReadType: evmtypes.Event, - }, - }, - }, - }, - } - - ts := testSetup(ctx, t, ethereumChain, cfg) + ts := testSetup(ctx, t, ethereumChain, evmconfig.USDCReaderConfig) usdcReader, err := reader.NewUSDCMessageReader( map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig{ diff --git a/core/capabilities/ccip/configs/evm/contract_reader.go b/core/capabilities/ccip/configs/evm/contract_reader.go index a158e5bd5d4..250b07bd1b1 100644 --- a/core/capabilities/ccip/configs/evm/contract_reader.go +++ b/core/capabilities/ccip/configs/evm/contract_reader.go @@ -257,6 +257,23 @@ var FeedReaderConfig = evmrelaytypes.ChainReaderConfig{ }, } +var USDCReaderConfig = evmrelaytypes.ChainReaderConfig{ + Contracts: map[string]evmrelaytypes.ChainContractReader{ + consts.ContractNameCCTPMessageTransmitter: { + ContractABI: MessageTransmitterABI, + ContractPollingFilter: evmrelaytypes.ContractPollingFilter{ + GenericEventNames: []string{consts.EventNameCCTPMessageSent}, + }, + Configs: map[string]*evmrelaytypes.ChainReaderDefinition{ + consts.EventNameCCTPMessageSent: { + ChainSpecificName: consts.EventNameCCTPMessageSent, + ReadType: evmrelaytypes.Event, + }, + }, + }, + }, +} + // HomeChainReaderConfigRaw returns a ChainReaderConfig that can be used to read from the home chain. var HomeChainReaderConfigRaw = evmrelaytypes.ChainReaderConfig{ Contracts: map[string]evmrelaytypes.ChainContractReader{ diff --git a/core/capabilities/ccip/configs/evm/usdc.go b/core/capabilities/ccip/configs/evm/usdc.go new file mode 100644 index 00000000000..47efcdc25f5 --- /dev/null +++ b/core/capabilities/ccip/configs/evm/usdc.go @@ -0,0 +1,26 @@ +package evm + +import ( + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +var ( + // We need only ABI part of the contract that describes the event structure + // https://github.com/circlefin/evm-cctp-contracts/blob/377c9bd813fb86a42d900ae4003599d82aef635a/src/MessageTransmitter.sol#L41 + MessageTransmitterABI = `[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "MessageSent", + "type": "event" + } + ]` + _ = evmtypes.MustGetABI(MessageTransmitterABI) +) diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index 07682ba60e9..455bcde83e7 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -383,12 +383,28 @@ func getChainReaderConfig( chainReaderConfig = evmconfig.MergeReaderConfigs(chainReaderConfig, evmconfig.FeedReaderConfig) } + if isUSDCEnabled(chainID, destChainID, ofc) { + chainReaderConfig = evmconfig.MergeReaderConfigs(chainReaderConfig, evmconfig.USDCReaderConfig) + } + if chainID == homeChainID { chainReaderConfig = evmconfig.MergeReaderConfigs(chainReaderConfig, evmconfig.HomeChainReaderConfigRaw) } return chainReaderConfig } +func isUSDCEnabled(chainID uint64, destChainID uint64, ofc offChainConfig) bool { + if chainID == destChainID { + return false + } + + if ofc.execEmpty() { + return false + } + + return ofc.exec().IsUSDCEnabled() +} + func createChainReader( lggr logger.Logger, chain legacyevm.Chain, diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 4d9e4fa5ebe..0c3453fcf8e 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -271,7 +271,7 @@ require ( github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.23 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index b3244647a49..d71289f8bdc 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1081,8 +1081,8 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 h1:X07jKFIakpR7UyG/BnQ8wKZz395LsEcowqyQgDKHcT8= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a h1:zHMknn5+VAOMzMk3LXT+GfCD17h252jTKreFyQwRaB0= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= diff --git a/go.mod b/go.mod index c192323e61d..e7f1b2c1761 100644 --- a/go.mod +++ b/go.mod @@ -74,7 +74,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 diff --git a/go.sum b/go.sum index 89a1b45d722..d1c4d715409 100644 --- a/go.sum +++ b/go.sum @@ -1054,8 +1054,8 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 h1:X07jKFIakpR7UyG/BnQ8wKZz395LsEcowqyQgDKHcT8= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a h1:zHMknn5+VAOMzMk3LXT+GfCD17h252jTKreFyQwRaB0= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 708cba54dd2..7c0dad360f7 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -39,7 +39,7 @@ require ( github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.9 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 1476373ca65..14fcfcdb26f 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1423,8 +1423,8 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 h1:X07jKFIakpR7UyG/BnQ8wKZz395LsEcowqyQgDKHcT8= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a h1:zHMknn5+VAOMzMk3LXT+GfCD17h252jTKreFyQwRaB0= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 57f186971a9..f0b8fccdcd6 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -382,7 +382,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.23 // indirect github.com/smartcontractkit/chainlink-automation v1.0.4 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240916152957-433914114bd2 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240910155501-42f20443189f // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 8e0d4eb37cf..d401b11553c 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1397,8 +1397,8 @@ github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnj github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1 h1:X07jKFIakpR7UyG/BnQ8wKZz395LsEcowqyQgDKHcT8= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20240930203817-424892240cd1/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a h1:zHMknn5+VAOMzMk3LXT+GfCD17h252jTKreFyQwRaB0= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241002064705-34d7f9b7e26a/go.mod h1:/nGkIe25kgtr+l6y30VH+aTVaxu0NjIEEEhtV1TDlaE= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec h1:zmLmKLCpoV73AaGU8YBc86YPLJBzoZFv9lPC677Eqcs= github.com/smartcontractkit/chainlink-common v0.2.3-0.20241001140426-35be2fad06ec/go.mod h1:F6WUS6N4mP5ScwpwyTyAJc9/vjR+GXbMCRUOVekQi1g= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240911175228-daf2600bb7b7 h1:lTGIOQYLk1Ufn++X/AvZnt6VOcuhste5yp+C157No/Q= From 65f1d9d8797171cd0e0ed1bafa8ac0275bc55fd9 Mon Sep 17 00:00:00 2001 From: HenryNguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Wed, 2 Oct 2024 07:42:10 -0700 Subject: [PATCH 17/28] RE 2997 error building chainlink binary with wasmtime go dependency (#14583) * Refactor .goreleaser.develop.yaml to use multi-platform native compilation * Make required changes to get platform native builds working * Programmatic goreleaser config generator, works with .goreleaser.develop.yaml * Remove broken goreleaser makefile cmds * Remove zig dep from shell.nix * Remove macos-sdk, goreleaser exec, zig refs from goreleaser action * Use no_unique_dist_dir config since we only build 1 target a time * Remove qemu support * Use ubuntu 24 for goreleaser base image * Test split builds w/o merge * Add sensible default for CHAINLINK_VERSION * Set chainlink version in github action * Merge ccip and regular builds together * Use nightlies over snapshots * Split and merge * Correctly set release type * Quote nullable var * Pass down release type * goreleaserpro -> goreleaser * Set nightly version correctly * Add fetch depth * Make name more accurate * Fix merge cmd * Disable changelogs + archives unless prod * Update develop config file * Sign nightly images * Handle prod image name prefix * prod -> production * Remove stale fixtures * Add production config generation * Correctly add ECR path to prod images * Merge production + ccip production together * Disable changelog on develop * Remove env var shadowing and redundant templating * Remove signing for develop builds * Fix nightly version template * Refactor build-sign-publish inputs * Fix skippush condition, remove cosign signing * Nuke cosign from goreleaser action in favor of gh artifact attestation * Apply split+merge refactor to prod pipeline * Run gomodtidy * Remove useless test * Update go.mod * Remove push on release/** trigger * Add fetch-depth 0 to image builds * Use a separate workflow for goreleaser * Update gomods * TEST: goreleaser prod * Add missing env * Fix yaml extension * Add missing fetch depth * Fix incorrect manifest naming * Configure skip_push for prod manifests * Refactor artifact path handling in build-publish-goreleaser.yml * Remove artifact attestation * Update go.mod * Remove test branch trigger --- .../goreleaser-build-sign-publish/README.md | 56 -- .../goreleaser-build-sign-publish/action.yml | 74 +-- .../action_utils | 82 --- .../goreleaser-build-sign-publish/release.js | 93 +++ .../workflows/build-publish-develop-pr.yml | 171 +++--- .../workflows/build-publish-goreleaser.yml | 150 +++++ .github/workflows/build-publish.yml | 139 +---- .goreleaser.ccip.develop.yaml | 229 -------- .goreleaser.ccip.production.yaml | 229 -------- .goreleaser.develop.yaml | 510 ++++++++++------- .goreleaser.production.yaml | 530 +++++++++++------- .tool-versions | 1 - GNUmakefile | 10 - core/chainlink.goreleaser.Dockerfile | 14 +- shell.nix | 3 - tools/bin/goreleaser_utils | 97 +--- tools/bin/goreleaser_wrapper | 41 -- tools/bin/ldd_fix | 27 - tools/goreleaser-config/gen_config.go | 363 ++++++++++++ tools/goreleaser-config/go.mod | 14 + tools/goreleaser-config/go.sum | 19 + tools/goreleaser-config/main.go | 24 + 22 files changed, 1409 insertions(+), 1467 deletions(-) delete mode 100755 .github/actions/goreleaser-build-sign-publish/action_utils create mode 100755 .github/actions/goreleaser-build-sign-publish/release.js create mode 100644 .github/workflows/build-publish-goreleaser.yml delete mode 100644 .goreleaser.ccip.develop.yaml delete mode 100644 .goreleaser.ccip.production.yaml delete mode 100755 tools/bin/goreleaser_wrapper delete mode 100755 tools/bin/ldd_fix create mode 100644 tools/goreleaser-config/gen_config.go create mode 100644 tools/goreleaser-config/go.mod create mode 100644 tools/goreleaser-config/go.sum create mode 100644 tools/goreleaser-config/main.go diff --git a/.github/actions/goreleaser-build-sign-publish/README.md b/.github/actions/goreleaser-build-sign-publish/README.md index 189578391f5..07bb644c001 100644 --- a/.github/actions/goreleaser-build-sign-publish/README.md +++ b/.github/actions/goreleaser-build-sign-publish/README.md @@ -21,8 +21,6 @@ jobs: permissions: id-token: write contents: read - env: - MACOS_SDK_VERSION: 12.3 steps: - name: Checkout repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -32,28 +30,11 @@ jobs: role-to-assume: ${{ secrets.aws-role-arn }} role-duration-seconds: ${{ secrets.aws-role-dur-sec }} aws-region: ${{ secrets.aws-region }} - - name: Cache macos sdk - id: sdk-cache - uses: actions/cache@v3 - with: - path: ${{ format('MacOSX{0}.sdk', env.MAC_SDK_VERSION) }} - key: ${{ runner.OS }}-${{ env.MAC_SDK_VERSION }}-macos-sdk-cache-${{ hashFiles('**/SDKSettings.json') }} - restore-keys: | - ${{ runner.OS }}-${{ env.MAC_SDK_VERSION }}-macos-sdk-cache- - - name: Get macos sdk - if: steps.sdk-cache.outputs.cache-hit != 'true' - run: | - curl -L https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_SDK_VERSION}/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz > MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz - tar -xf MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz - name: Build, sign, and publish uses: ./.github/actions/goreleaser-build-sign-publish with: - enable-docker-publish: "true" - enable-goreleaser-snapshot: "false" docker-registry: ${{ secrets.aws-ecr-registry }} - goreleaser-exec: goreleaser goreleaser-config: .goreleaser.yaml - macos-sdk-dir: ${{ format('MacOSX{0}.sdk', env.MAC_SDK_VERSION) }} env: GITHUB_TOKEN: ${{ secrets.gh-token }} ``` @@ -64,29 +45,8 @@ jobs: - name: Build, sign, and publish image uses: ./.github/actions/goreleaser-build-sign-publish with: - enable-docker-publish: "true" - enable-goreleaser-snapshot: "true" - docker-registry: ${{ secrets.aws-ecr-registry }} - goreleaser-exec: goreleaser - goreleaser-config: .goreleaser.yaml -``` - -### image signing - -```yaml -- name: Build, sign, and publish - uses: ./.github/actions/goreleaser-build-sign-publish - with: - enable-docker-publish: "true" - enable-goreleaser-snapshot: "false" - enable-cosign: "true" docker-registry: ${{ secrets.aws-ecr-registry }} - goreleaser-exec: goreleaser goreleaser-config: .goreleaser.yaml - cosign-password: ${{ secrets.cosign-password }} - cosign-public-key: ${{ secrets.cosign-public-key }} - cosign-private-key: ${{ secrets.cosign-private-key }} - macos-sdk-dir: MacOSX12.3.sdk ``` ## customizing @@ -98,22 +58,9 @@ Following inputs can be used as `step.with` keys | Name | Type | Default | Description | | ---------------------------- | ------ | ------------------ | ----------------------------------------------------------------------- | | `goreleaser-version` | String | `~> v2` | `goreleaser` version | -| `zig-version` | String | `0.10.1` | `zig` version | -| `cosign-version` | String | `v2.2.2` | `cosign` version | -| `macos-sdk-dir` | String | `MacOSX12.3.sdk` | MacOSX sdk directory | -| `enable-docker-publish` | Bool | `true` | Enable publishing of Docker images / manifests | | `docker-registry` | String | `localhost:5001` | Docker registry | -| `docker-image-name` | String | `chainlink` | Docker image name | | `docker-image-tag` | String | `develop` | Docker image tag | -| `enable-goreleaser-snapshot` | Bool | `false` | Enable goreleaser build / release snapshot | -| `enable-goreleaser-split` | Bool | `false` | Enable goreleaser build using split and merge | -| `goreleaser-split-arch` | String | `""` | The arch to build the image with - amd64, arm64 | -| `goreleaser-exec` | String | `goreleaser` | The goreleaser executable, can invoke wrapper script | | `goreleaser-config` | String | `.goreleaser.yaml` | The goreleaser configuration yaml | -| `enable-cosign` | Bool | `false` | Enable signing of Docker images | -| `cosign-public-key` | String | `""` | The public key to be used with cosign for verification | -| `cosign-private-key` | String | `""` | The private key to be used with cosign to sign the image | -| `cosign-password-key` | String | `""` | The password to decrypt the cosign private key needed to sign the image | ## testing @@ -126,10 +73,7 @@ docker run -d --restart=always -p "127.0.0.1:5001:5000" --name registry registry - run snapshot release, publish to local docker registry ```sh -GORELEASER_EXEC=" set -_publish_snapshot_images() { - local full_sha=$(git rev-parse HEAD) - local images=$(docker images --filter "label=org.opencontainers.image.revision=$full_sha" --format "{{.Repository}}:{{.Tag}}") - for image in $images; do - docker push "$image" - done -} - -# publish snapshot docker manifest lists -# must have label=org.opencontainers.image.revision= set -_publish_snapshot_manifests() { - local docker_manifest_extra_args=$DOCKER_MANIFEST_EXTRA_ARGS - local full_sha=$(git rev-parse HEAD) - local images=$(docker images --filter "label=org.opencontainers.image.revision=$full_sha" --format "{{.Repository}}:{{.Tag}}" | sort) - local raw_manifest_lists="" - if [[ $ENABLE_GORELEASER_SPLIT == "true" ]]; then - local arches=(${GOARCH:-""}) - else - local arches=(amd64 arm64) - fi - for image in $images; do - for arch in "${arches[@]}"; do - image=${image%"-$arch"} - done - raw_manifest_lists+="$image"$'\n' - done - local manifest_lists=$(echo "$raw_manifest_lists" | sort | uniq) - for manifest_list in $manifest_lists; do - manifests="" - for arch in "${arches[@]}"; do - archExists=$(echo "$images" | grep -c "$manifest_lists-$arch") - if [[ $archExists -ne 0 ]]; then - manifests+="$manifest_list-$arch " - fi - done - docker manifest create $manifest_list $manifests $docker_manifest_extra_args - docker manifest push "$manifest_list" - done -} - -# wrapper function to invoke goreleaser release -goreleaser_release() { - goreleaser_flags=() - - # set goreleaser flags - if [[ $ENABLE_GORELEASER_SNAPSHOT == "true" ]]; then - goreleaser_flags+=("--snapshot") - goreleaser_flags+=("--clean") - fi - if [[ $ENABLE_GORELEASER_SPLIT == "true" ]]; then - goreleaser_flags+=("--split") - fi - flags=$(printf "%s " "${goreleaser_flags[@]}") - flags=$(echo "$flags" | sed 's/ *$//') - - if [[ -n $MACOS_SDK_DIR ]]; then - MACOS_SDK_DIR=$(echo "$(cd "$(dirname "$MACOS_SDK_DIR")" || exit; pwd)/$(basename "$MACOS_SDK_DIR")") - fi - - $GORELEASER_EXEC release ${flags} --config "$GORELEASER_CONFIG" "$@" - - if [[ $ENABLE_DOCKER_PUBLISH == "true" ]] && [[ $ENABLE_GORELEASER_SNAPSHOT == "true" ]]; then - _publish_snapshot_images - _publish_snapshot_manifests - fi -} - -"$@" diff --git a/.github/actions/goreleaser-build-sign-publish/release.js b/.github/actions/goreleaser-build-sign-publish/release.js new file mode 100755 index 00000000000..565b03ee7f4 --- /dev/null +++ b/.github/actions/goreleaser-build-sign-publish/release.js @@ -0,0 +1,93 @@ +#!/usr/bin/env node +const { execSync } = require("child_process"); + +function main() { + const goreleaserConfig = mustGetEnv("GORELEASER_CONFIG"); + const releaseType = mustGetEnv("RELEASE_TYPE"); + const command = constructGoreleaserCommand(releaseType, goreleaserConfig); + + if (process.env.DRY_RUN) { + console.log(`Generated command: ${command}`); + console.log("Dry run enabled. Exiting without executing the command."); + return; + } else { + console.log(`Executing command: ${command}`); + execSync(command, { stdio: "inherit" }); + } +} + +main(); + +function constructGoreleaserCommand(releaseType, goreleaserConfig) { + const version = getVersion(); + const flags = []; + + checkReleaseType(releaseType); + + let subCmd = "release"; + const splitArgs = ["--split", "--clean"]; + + switch (releaseType) { + case "release": + flags.push(...splitArgs); + break; + case "nightly": + flags.push("--nightly", ...splitArgs); + break; + case "snapshot": + flags.push("--snapshot", ...splitArgs); + break; + case "merge": + flags.push("--merge"); + subCmd = "continue"; + break; + } + + const flagsStr = flags.join(" "); + if (releaseType === "merge") { + return `CHAINLINK_VERSION=${version} goreleaser ${subCmd} ${flagsStr}`; + } else { + return `CHAINLINK_VERSION=${version} goreleaser ${subCmd} --config ${goreleaserConfig} ${flagsStr}`; + } +} + +function checkReleaseType(releaseType) { + const VALID_RELEASE_TYPES = ["nightly", "merge", "snapshot", "release"]; + + if (!VALID_RELEASE_TYPES.includes(releaseType)) { + const validReleaseTypesStr = VALID_RELEASE_TYPES.join(", "); + console.error( + `Error: Invalid release type: ${releaseType}. Must be one of: ${validReleaseTypesStr}` + ); + } +} + +function mustGetEnv(key) { + const val = process.env[key]; + if (!val || val.trim() === "") { + console.error(`Error: Environment variable ${key} is not set or empty.`); + process.exit(1); + } + + return val.trim(); +} + +function getVersion() { + try { + const pkgPath = process.cwd() + "/package.json"; + console.log("Looking for chainlink version in package.json at: ", pkgPath); + const packageJson = require(pkgPath); + if (!packageJson.version) { + console.error( + 'Error: "version" field is missing or empty in package.json.' + ); + process.exit(1); + } + console.log("Resolved version: ", packageJson.version); + + return packageJson.version; + } catch (err) { + console.error(`Error reading package.json: ${err.message}`); + process.exit(1); + } +} diff --git a/.github/workflows/build-publish-develop-pr.yml b/.github/workflows/build-publish-develop-pr.yml index 2868616ace0..aacda25a187 100644 --- a/.github/workflows/build-publish-develop-pr.yml +++ b/.github/workflows/build-publish-develop-pr.yml @@ -1,11 +1,10 @@ -name: "Build and Publish Chainlink" +name: "Build and Publish GoReleaser" on: pull_request: push: branches: - develop - - "release/**" workflow_dispatch: inputs: git_ref: @@ -20,17 +19,10 @@ env: GIT_REF: ${{ github.event.inputs.git_ref || github.ref }} jobs: - goreleaser-build-publish-chainlink: - name: "goreleaser-build-publish-${{ matrix.image-name }}" - strategy: - fail-fast: false - matrix: - include: - - image-name: chainlink - goreleaser-config: .goreleaser.develop.yaml - - image-name: ccip - goreleaser-config: .goreleaser.ccip.develop.yaml - runs-on: ubuntu-20.04 + merge: + runs-on: ubuntu-latest + needs: [split, image-tag] + if: ${{ needs.image-tag.outputs.release-type == 'nightly' }} permissions: id-token: write contents: read @@ -40,67 +32,83 @@ jobs: with: ref: ${{ env.GIT_REF }} - # This gets the image tag and whether to publish the image based on the event type - # PR builds: pr-- (if label 'build-publish' is present publishes the image) - # develop builds: develop- and develop (only amd64) - # release builds: release- - # manual builds: (if build-publish is true publishes the image) - - name: Get image tag - id: get-image-tag - run: | - short_sha=$(git rev-parse --short HEAD) - echo "build-publish=false" | tee -a $GITHUB_OUTPUT - if [[ ${{ github.event_name }} == 'push' ]]; then - if [[ ${{ github.ref_name }} == 'release/'* ]]; then - echo "image-tag=release-${short_sha}" | tee -a $GITHUB_OUTPUT - echo "build-publish=true" | tee -a $GITHUB_OUTPUT - else - echo "image-tag=develop" | tee -a $GITHUB_OUTPUT - echo "build-publish=true" | tee -a $GITHUB_OUTPUT - fi - elif [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then - echo "image-tag=${short_sha}" | tee -a $GITHUB_OUTPUT - echo "build-publish=${{ github.event.inputs.build-publish }}" | tee -a $GITHUB_OUTPUT - else - if [[ ${{ github.event_name }} == "pull_request" ]]; then - echo "image-tag=pr-${{ github.event.number }}-${short_sha}" | tee -a $GITHUB_OUTPUT - if [[ ${{ contains(github.event.pull_request.labels.*.name, 'build-publish') }} == "true" ]]; then - echo "build-publish=true" | tee -a $GITHUB_OUTPUT - fi - fi - fi - - name: Configure aws credentials - if: steps.get-image-tag.outputs.build-publish == 'true' uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 with: role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_BUILD_PUBLISH_DEVELOP_PR }} aws-region: ${{ secrets.AWS_REGION }} mask-aws-account-id: true - role-session-name: goreleaser-build-publish-${{ matrix.image-name }} + role-session-name: "merge" + + - uses: actions/cache/restore@v4 + with: + path: dist/linux_amd64_v1 + key: chainlink-amd64-${{ github.sha }} + fail-on-cache-miss: true + + - uses: actions/cache/restore@v4 + with: + path: dist/linux_arm64 + key: chainlink-arm64-${{ github.sha }} + fail-on-cache-miss: true - - name: Build and publish images + - name: Merge images for both architectures uses: ./.github/actions/goreleaser-build-sign-publish with: - enable-docker-publish: ${{ steps.get-image-tag.outputs.build-publish }} docker-registry: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }} - docker-image-name: ${{ matrix.image-name }} - docker-image-tag: ${{ steps.get-image-tag.outputs.image-tag }} - enable-goreleaser-snapshot: "true" - goreleaser-exec: ./tools/bin/goreleaser_wrapper - goreleaser-config: ${{ matrix.goreleaser-config }} + docker-image-tag: ${{ needs.image-tag.outputs.image-tag }} + goreleaser-release-type: "merge" + goreleaser-config: .goreleaser.develop.yaml goreleaser-key: ${{ secrets.GORELEASER_KEY }} - zig-version: 0.11.0 - - name: Output image name and digest - if: steps.get-image-tag.outputs.build-publish == 'true' - shell: bash - run: | - echo "### Docker Images" | tee -a "$GITHUB_STEP_SUMMARY" - jq -r '.[] | select(.type == "Docker Image") | "\(.name)"' ${artifact_path} >> output.txt - while read -r line; do - echo "$line" | tee -a "$GITHUB_STEP_SUMMARY" - done < output.txt + split: + name: "split-${{ matrix.goarch }}" + needs: image-tag + runs-on: ${{ matrix.runner }} + permissions: + id-token: write + contents: read + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-latest + goarch: amd64 + dist_name: linux_amd64_v1 + + - runner: ubuntu-24.04-4cores-16GB-ARM + goarch: arm64 + dist_name: linux_arm64 + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + ref: ${{ env.GIT_REF }} + fetch-depth: 0 + + - name: Configure aws credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_BUILD_PUBLISH_DEVELOP_PR }} + aws-region: ${{ secrets.AWS_REGION }} + mask-aws-account-id: true + role-session-name: "split-${{ matrix.goarch }}" + + - id: cache + uses: actions/cache@v4 + with: + path: dist/${{ matrix.dist_name }} + key: chainlink-${{ matrix.goarch }}-${{ github.sha }} + + - name: Build images for ${{ matrix.goarch }} + uses: ./.github/actions/goreleaser-build-sign-publish + if: steps.cache.outputs.cache-hit != 'true' + with: + docker-registry: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }} + docker-image-tag: ${{ needs.image-tag.outputs.image-tag }} + goreleaser-release-type: ${{ needs.image-tag.outputs.release-type }} + goreleaser-config: .goreleaser.develop.yaml + goreleaser-key: ${{ secrets.GORELEASER_KEY }} - name: Collect Metrics if: always() @@ -111,5 +119,40 @@ jobs: org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: goreleaser-build-publish-${{ matrix.image-name }} - continue-on-error: true \ No newline at end of file + this-job-name: "split-${{ matrix.goarch }}" + continue-on-error: true + + image-tag: + runs-on: ubuntu-latest + outputs: + image-tag: ${{ steps.get-image-tag.outputs.image-tag }} + release-type: ${{ steps.get-image-tag.outputs.release-type }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + + - name: Get image tag + id: get-image-tag + run: | + short_sha=$(git rev-parse --short HEAD) + echo "release-type=snapshot" | tee -a $GITHUB_OUTPUT + if [[ ${{ github.event_name }} == 'push' ]]; then + echo "image-tag=develop" | tee -a $GITHUB_OUTPUT + echo "release-type=nightly" | tee -a $GITHUB_OUTPUT + elif [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then + echo "image-tag=${short_sha}" | tee -a $GITHUB_OUTPUT + if [[ "${{ inputs.build-publish }}" == 'false' ]]; then + echo "release-type=snapshot" | tee -a $GITHUB_OUTPUT + else + echo "release-type=nightly" | tee -a $GITHUB_OUTPUT + fi + else + if [[ ${{ github.event_name }} == "pull_request" ]]; then + echo "image-tag=pr-${{ github.event.number }}-${short_sha}" | tee -a $GITHUB_OUTPUT + if [[ ${{ contains(github.event.pull_request.labels.*.name, 'build-publish') }} == "true" ]]; then + echo "release-type=nightly" | tee -a $GITHUB_OUTPUT + fi + fi + fi diff --git a/.github/workflows/build-publish-goreleaser.yml b/.github/workflows/build-publish-goreleaser.yml new file mode 100644 index 00000000000..f19df8cb0bf --- /dev/null +++ b/.github/workflows/build-publish-goreleaser.yml @@ -0,0 +1,150 @@ +name: "Goreleaser Chainlink" + +on: + push: + tags: + - "goreleaser-v*" + +env: + ECR_HOSTNAME: public.ecr.aws + +jobs: + checks: + name: "Checks" + runs-on: ubuntu-20.04 + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - name: Check for VERSION file bump on tags + # Avoids checking VERSION file bump on forks. + if: ${{ github.repository == 'smartcontractkit/chainlink' }} + uses: ./.github/actions/version-file-bump + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + # The main differences between this workflow and the develop one are: + # - Goreleaser pipeline only runs on tags + # - We only build ccip OR chainlink, not both + goreleaser-merge: + needs: [goreleaser-split] + name: merge + runs-on: ubuntu-latest + environment: build-publish + permissions: + id-token: write + contents: read + attestations: write + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + fetch-depth: 0 + + - name: Configure aws credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN }} + role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} + aws-region: ${{ secrets.AWS_REGION }} + mask-aws-account-id: true + role-session-name: goreleaser-build-sign-publish-chainlink + + - uses: actions/cache/restore@v4 + with: + path: dist/linux_amd64_v1 + # We use ref_name here and not in develop b/c develop builds both ccip and chainlink + # whereas here we only build one or the other + key: chainlink-amd64-${{ github.sha }}-${{ github.ref_name }} + fail-on-cache-miss: true + + - uses: actions/cache/restore@v4 + with: + path: dist/linux_arm64 + key: chainlink-arm64-${{ github.sha }}-${{ github.ref_name }} + fail-on-cache-miss: true + + - name: Merge images for both architectures + id: goreleaser-build-sign-publish + uses: ./.github/actions/goreleaser-build-sign-publish + with: + docker-registry: ${{ env.ECR_HOSTNAME }} + docker-image-tag: ${{ github.ref_name }} + goreleaser-config: .goreleaser.production.yaml + goreleaser-release-type: merge + goreleaser-key: ${{ secrets.GORELEASER_KEY }} + + - name: Collect Metrics + if: always() + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 + with: + id: goreleaser-build-chainlink-publish + org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} + this-job-name: merge + continue-on-error: true + + goreleaser-split: + name: "split-${{ matrix.goarch }}" + needs: [checks] + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-latest + goarch: amd64 + dist_name: linux_amd64_v1 + + - runner: ubuntu-24.04-4cores-16GB-ARM + goarch: arm64 + dist_name: linux_arm64 + environment: build-publish + permissions: + id-token: write + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + fetch-depth: 0 + + - name: Configure aws credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN }} + role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} + aws-region: ${{ secrets.AWS_REGION }} + mask-aws-account-id: true + role-session-name: goreleaser-build-sign-publish-chainlink + + - id: cache + uses: actions/cache@v4 + with: + path: dist/${{ matrix.dist_name }} + # We use ref_name here and not in develop b/c develop builds both ccip and chainlink + # whereas here we only build one or the other + key: chainlink-${{ matrix.goarch }}-${{ github.sha }}-${{ github.ref_name }} + + - name: Build images for ${{ matrix.goarch }} + if: steps.cache.outputs.cache-hit != 'true' + uses: ./.github/actions/goreleaser-build-sign-publish + with: + docker-registry: ${{ env.ECR_HOSTNAME }} + docker-image-tag: ${{ github.ref_name }} + goreleaser-release-type: release + goreleaser-config: .goreleaser.production.yaml + goreleaser-key: ${{ secrets.GORELEASER_KEY }} + + - name: Collect Metrics + if: always() + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 + with: + id: goreleaser-build-chainlink-publish + org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} + this-job-name: split-${{ matrix.goarch }} + continue-on-error: true diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index ca6a2d5275a..0b899310de1 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -1,12 +1,9 @@ name: "Build, Sign and Publish Chainlink" on: - # Mimics old circleci behaviour push: tags: - "v*" - branches: - - "release/**" env: ECR_HOSTNAME: public.ecr.aws @@ -21,14 +18,13 @@ jobs: uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Check for VERSION file bump on tags # Avoids checking VERSION file bump on forks. - if: ${{ github.repository == 'smartcontractkit/chainlink' && startsWith(github.ref, 'refs/tags/v') }} + if: ${{ github.repository == 'smartcontractkit/chainlink' }} uses: ./.github/actions/version-file-bump with: github-token: ${{ secrets.GITHUB_TOKEN }} build-sign-publish-chainlink: needs: [checks] - if: ${{ ! startsWith(github.ref_name, 'release/') }} runs-on: ubuntu-20.04 environment: build-publish permissions: @@ -76,139 +72,6 @@ jobs: this-job-name: build-sign-publish-chainlink continue-on-error: true - goreleaser-build-sign-publish-chainlink: - needs: [checks] - if: ${{ ! startsWith(github.ref_name, 'release/') }} - runs-on: ubuntu-20.04 - environment: build-publish - permissions: - id-token: write - contents: write - attestations: write - steps: - - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - - name: Configure aws credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 - with: - role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN }} - role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} - aws-region: ${{ secrets.AWS_REGION }} - mask-aws-account-id: true - role-session-name: goreleaser-build-sign-publish-chainlink - - - name: Set build configs - shell: bash - id: set-build-configs - run: | - if [[ ${{ github.ref_name }} =~ "-ccip" ]]; then - echo "ECR_IMAGE_NAME=chainlink/ccip" | tee -a $GITHUB_OUTPUT - echo "GORELEASER_CONFIG=.goreleaser.ccip.production.yaml" | tee -a $GITHUB_OUTPUT - else - echo "ECR_IMAGE_NAME=chainlink/chainlink" | tee -a $GITHUB_OUTPUT - echo "GORELEASER_CONFIG=.goreleaser.production.yaml" | tee -a $GITHUB_OUTPUT - fi - - - name: Build, sign, and publish image - id: goreleaser-build-sign-publish - uses: ./.github/actions/goreleaser-build-sign-publish - with: - docker-registry: ${{ env.ECR_HOSTNAME}} - docker-image-name: ${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} - docker-image-tag: ${{ github.ref_name }} - goreleaser-exec: ./tools/bin/goreleaser_wrapper - goreleaser-config: ${{ steps.set-build-configs.outputs.GORELEASER_CONFIG }} - goreleaser-key: ${{ secrets.GORELEASER_KEY }} - zig-version: 0.11.0 - enable-cosign: true - cosign-version: "v2.4.0" - - - name: Output image name and digest - id: get-image-name-digest - shell: bash - run: | - artifact_path="dist/artifacts.json" - jq -r '.[] | select(.type == "Docker Image") | "\(.name)"' ${artifact_path} >> output.txt - - echo "### Docker Images" | tee -a "$GITHUB_STEP_SUMMARY" - while read -r line; do - echo "$line" | tee -a "$GITHUB_STEP_SUMMARY" - done < output.txt - - core_amd64_name="${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }}:${{ github.ref_name }}-amd64" - plugins_amd64_name="${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }}:${{ github.ref_name }}-plugins-amd64" - core_arm64_name="${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }}:${{ github.ref_name }}-arm64" - plugins_arm64_name="${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }}:${{ github.ref_name }}-plugins-arm64" - - echo "core_amd64_digest=$(jq -r --arg name "$core_amd64_name" '.[]|select(.type=="Published Docker Image" and .name==$name)|.extra.Digest' ${artifact_path})" | tee -a "$GITHUB_OUTPUT" "$GITHUB_STEP_SUMMARY" - echo "plugins_amd64_digest=$(jq -r --arg name "$plugins_amd64_name" '.[]|select(.type=="Published Docker Image" and .name==$name)|.extra.Digest' ${artifact_path})" | tee -a "$GITHUB_OUTPUT" "$GITHUB_STEP_SUMMARY" - echo "core_arm64_digest=$(jq -r --arg name "$core_amd64_name" '.[]|select(.type=="Published Docker Image" and .name==$name)|.extra.Digest' ${artifact_path})" | tee -a "$GITHUB_OUTPUT" "$GITHUB_STEP_SUMMARY" - echo "plugins_arm64_digest=$(jq -r --arg name "$plugins_amd64_name" '.[]|select(.type=="Published Docker Image" and .name==$name)|.extra.Digest' ${artifact_path})" | tee -a "$GITHUB_OUTPUT" "$GITHUB_STEP_SUMMARY" - - - name: Attest tarballs - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 - with: - subject-path: "dist/*.tar.gz" - - - name: Attest Docker image (core-amd64) - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 - with: - subject-digest: ${{ steps.get-image-name-digest.outputs.core_amd64_digest }} - subject-name: ${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} - push-to-registry: true - - - name: Attest Docker image (plugins-amd64) - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 - with: - subject-digest: ${{ steps.get-image-name-digest.outputs.plugins_amd64_digest }} - subject-name: ${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} - push-to-registry: true - - - name: Attest Docker image (core-arm64) - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 - with: - subject-digest: ${{ steps.get-image-name-digest.outputs.core_arm64_digest }} - subject-name: ${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} - push-to-registry: true - - - name: Attest Docker image (plugins-arm64) - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 - with: - subject-digest: ${{ steps.get-image-name-digest.outputs.plugins_arm64_digest }} - subject-name: ${{ env.ECR_HOSTNAME }}/${{ steps.set-build-configs.outputs.ECR_IMAGE_NAME }} - push-to-registry: true - - - name: Upload SBOMs - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 - with: - name: goreleaser-sboms - path: dist/*.sbom.json - - - name: Print SBOM artifact to job summary - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - ARTIFACTS=$(gh api -X GET repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts) - ARTIFACT_ID=$(echo "$ARTIFACTS" | jq '.artifacts[] | select(.name=="goreleaser-sboms") | .id') - echo "Artifact ID: $ARTIFACT_ID" - echo "### SBOM Artifact" | tee -a "$GITHUB_STEP_SUMMARY" - artifact_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID" - echo "[Artifact URL]($artifact_url)" | tee -a $GITHUB_STEP_SUMMARY - - - name: Collect Metrics - if: always() - id: collect-gha-metrics - uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 - with: - id: goreleaser-build-chainlink-publish - org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} - basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} - hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: goreleaser-build-sign-publish-chainlink - continue-on-error: true - # Notify Slack channel for new git tags. slack-notify: if: github.ref_type == 'tag' diff --git a/.goreleaser.ccip.develop.yaml b/.goreleaser.ccip.develop.yaml deleted file mode 100644 index 595fe14c27b..00000000000 --- a/.goreleaser.ccip.develop.yaml +++ /dev/null @@ -1,229 +0,0 @@ -project_name: chainlink - -version: 2 - -env: - - ZIG_EXEC={{ if index .Env "ZIG_EXEC" }}{{ .Env.ZIG_EXEC }}{{ else }}zig{{ end }} - - IMAGE_PREFIX={{ if index .Env "IMAGE_PREFIX" }}{{ .Env.IMAGE_PREFIX }}{{ else }}localhost:5001{{ end }} - - IMAGE_NAME={{ if index .Env "IMAGE_NAME" }}{{ .Env.IMAGE_NAME }}{{ else }}chainlink{{ end }} - - IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} - - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" - - IMAGE_LABEL_LICENSES="MIT" - - IMAGE_LABEL_SOURCE="https://github.com/smartcontractkit/{{ .ProjectName }}" - -before: - hooks: - - go mod tidy - - ./tools/bin/goreleaser_utils before_hook - -# See https://goreleaser.com/customization/build/ -builds: - - binary: chainlink - id: linux-arm64 - goos: - - linux - goarch: - - arm64 - hooks: - post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} - env: - - CGO_ENABLED=1 - - CC=$ZIG_EXEC cc -target aarch64-linux-gnu - - CCX=$ZIG_EXEC c++ -target aarch64-linux-gnu - flags: - - -trimpath - - -buildmode=pie - ldflags: - - -s -w -r=$ORIGIN/libs - - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} - - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} - - binary: chainlink - id: linux-amd64 - goos: - - linux - goarch: - - amd64 - hooks: - post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} - env: - - CGO_ENABLED=1 - - CC=$ZIG_EXEC cc -target x86_64-linux-gnu - - CCX=$ZIG_EXEC c++ -target x86_64-linux-gnu - flags: - - -trimpath - - -buildmode=pie - ldflags: - - -s -w -r=$ORIGIN/libs - - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} - - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} - -# See https://goreleaser.com/customization/docker/ -dockers: - - id: linux-amd64 - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: amd64 - extra_files: - - tmp/linux_amd64/libs - - tools/bin/ldd_fix - - ccip/config - build_flag_templates: - - "--platform=linux/amd64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" - - id: linux-arm64 - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: arm64 - extra_files: - - tmp/linux_arm64/libs - - tools/bin/ldd_fix - - ccip/config - build_flag_templates: - - "--platform=linux/arm64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" - - id: linux-amd64-plugins - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: amd64 - extra_files: - - tmp/linux_amd64/libs - - tmp/linux_amd64/plugins - - tools/bin/ldd_fix - - ccip/config - build_flag_templates: - - "--platform=linux/amd64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" - - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" - - "--build-arg=CL_SOLANA_CMD=chainlink-solana" - - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" - - id: linux-arm64-plugins - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: arm64 - extra_files: - - tmp/linux_arm64/libs - - tmp/linux_arm64/plugins - - tools/bin/ldd_fix - - ccip/config - build_flag_templates: - - "--platform=linux/arm64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" - - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" - - "--build-arg=CL_SOLANA_CMD=chainlink-solana" - - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" - -# See https://goreleaser.com/customization/docker_manifest/ -docker_manifests: - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" - -# See https://goreleaser.com/customization/docker_sign/ -docker_signs: - - artifacts: all - args: - - "sign" - - "${artifact}" - - "--yes" - -checksum: - name_template: "checksums.txt" - -snapshot: - version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" - -partial: - by: target - -# See https://goreleaser.com/customization/release/ -release: - disable: true - -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" -# modelines, feel free to remove those if you don't want/use them: -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/.goreleaser.ccip.production.yaml b/.goreleaser.ccip.production.yaml deleted file mode 100644 index 1247be143ca..00000000000 --- a/.goreleaser.ccip.production.yaml +++ /dev/null @@ -1,229 +0,0 @@ -project_name: chainlink - -version: 2 - -env: - - ZIG_EXEC={{ if index .Env "ZIG_EXEC" }}{{ .Env.ZIG_EXEC }}{{ else }}zig{{ end }} - - IMAGE_PREFIX={{ if index .Env "IMAGE_PREFIX" }}{{ .Env.IMAGE_PREFIX }}{{ else }}localhost:5001{{ end }} - - IMAGE_NAME={{ if index .Env "IMAGE_NAME" }}{{ .Env.IMAGE_NAME }}{{ else }}chainlink{{ end }} - - IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} - - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" - - IMAGE_LABEL_LICENSES="MIT" - - IMAGE_LABEL_SOURCE="https://github.com/smartcontractkit/{{ .ProjectName }}" - -before: - hooks: - - go mod tidy - - ./tools/bin/goreleaser_utils before_hook - -# See https://goreleaser.com/customization/build/ -builds: - - binary: chainlink - id: linux-arm64 - goos: - - linux - goarch: - - arm64 - hooks: - post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} - env: - - CGO_ENABLED=1 - - CC=$ZIG_EXEC cc -target aarch64-linux-gnu - - CCX=$ZIG_EXEC c++ -target aarch64-linux-gnu - flags: - - -trimpath - - -buildmode=pie - ldflags: - - -s -w -r=$ORIGIN/libs - - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} - - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} - - binary: chainlink - id: linux-amd64 - goos: - - linux - goarch: - - amd64 - hooks: - post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} - env: - - CGO_ENABLED=1 - - CC=$ZIG_EXEC cc -target x86_64-linux-gnu - - CCX=$ZIG_EXEC c++ -target x86_64-linux-gnu - flags: - - -trimpath - - -buildmode=pie - ldflags: - - -s -w -r=$ORIGIN/libs - - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} - - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} - -# See https://goreleaser.com/customization/docker/ -dockers: - - id: linux-amd64 - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: amd64 - extra_files: - - tmp/linux_amd64/libs - - tools/bin/ldd_fix - - ccip/config - build_flag_templates: - - "--platform=linux/amd64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" - - id: linux-arm64 - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: arm64 - extra_files: - - tmp/linux_arm64/libs - - tools/bin/ldd_fix - - ccip/config - build_flag_templates: - - "--platform=linux/arm64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" - - id: linux-amd64-plugins - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: amd64 - extra_files: - - tmp/linux_amd64/libs - - tmp/linux_amd64/plugins - - tools/bin/ldd_fix - - ccip/config - build_flag_templates: - - "--platform=linux/amd64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" - - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" - - "--build-arg=CL_SOLANA_CMD=chainlink-solana" - - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" - - id: linux-arm64-plugins - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: arm64 - extra_files: - - tmp/linux_arm64/libs - - tmp/linux_arm64/plugins - - tools/bin/ldd_fix - - ccip/config - build_flag_templates: - - "--platform=linux/arm64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" - - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" - - "--build-arg=CL_SOLANA_CMD=chainlink-solana" - - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - - "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" - -# See https://goreleaser.com/customization/docker_manifest/ -docker_manifests: - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" - -# See https://goreleaser.com/customization/docker_sign/ -docker_signs: - - artifacts: all - args: - - "sign" - - "${artifact}" - - "--yes" - -checksum: - name_template: "checksums.txt" - -# See https://goreleaser.com/customization/sbom -sboms: - - artifacts: archive - -snapshot: - version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" - -partial: - by: target - -# See https://goreleaser.com/customization/release/ -release: - disable: true - -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" -# modelines, feel free to remove those if you don't want/use them: -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/.goreleaser.develop.yaml b/.goreleaser.develop.yaml index f8757676f83..1e054df7c45 100644 --- a/.goreleaser.develop.yaml +++ b/.goreleaser.develop.yaml @@ -1,221 +1,301 @@ -project_name: chainlink - version: 2 - +project_name: chainlink env: - - ZIG_EXEC={{ if index .Env "ZIG_EXEC" }}{{ .Env.ZIG_EXEC }}{{ else }}zig{{ end }} - - IMAGE_PREFIX={{ if index .Env "IMAGE_PREFIX" }}{{ .Env.IMAGE_PREFIX }}{{ else }}localhost:5001{{ end }} - - IMAGE_NAME={{ if index .Env "IMAGE_NAME" }}{{ .Env.IMAGE_NAME }}{{ else }}chainlink{{ end }} - - IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} - - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" - - IMAGE_LABEL_LICENSES="MIT" - - IMAGE_LABEL_SOURCE="https://github.com/smartcontractkit/{{ .ProjectName }}" - -before: - hooks: - - go mod tidy - - ./tools/bin/goreleaser_utils before_hook - -# See https://goreleaser.com/customization/build/ + - IMG_PRE={{ if index .Env "IMAGE_PREFIX" }}{{ .Env.IMAGE_PREFIX }}{{ else }}localhost:5001{{ end }} + - IMG_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} + - VERSION={{ if index .Env "CHAINLINK_VERSION" }}{{ .Env.CHAINLINK_VERSION }}{{ else }}v0.0.0-local{{ end }} +release: + disable: "true" builds: - - binary: chainlink - id: linux-arm64 - goos: - - linux - goarch: - - arm64 - hooks: - post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} - env: - - CGO_ENABLED=1 - - CC=$ZIG_EXEC cc -target aarch64-linux-gnu - - CCX=$ZIG_EXEC c++ -target aarch64-linux-gnu - flags: - - -trimpath - - -buildmode=pie - ldflags: - - -s -w -r=$ORIGIN/libs - - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} - - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} - - binary: chainlink - id: linux-amd64 - goos: - - linux - goarch: - - amd64 - hooks: - post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} - env: - - CGO_ENABLED=1 - - CC=$ZIG_EXEC cc -target x86_64-linux-gnu - - CCX=$ZIG_EXEC c++ -target x86_64-linux-gnu - flags: - - -trimpath - - -buildmode=pie - ldflags: - - -s -w -r=$ORIGIN/libs - - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} - - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} - -# See https://goreleaser.com/customization/docker/ + - targets: + - go_first_class + binary: chainlink + hooks: + post: + - cmd: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} + no_unique_dist_dir: "true" + ldflags: + - -s -w -r=$ORIGIN/libs + - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.VERSION }} + - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} + flags: + - -trimpath + - -buildmode=pie +archives: + - format: binary +snapshot: + version_template: '{{ .Env.VERSION }}-{{ .ShortCommit }}' +checksum: + name_template: checksums.txt dockers: - - id: linux-amd64 - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: amd64 - extra_files: - - tmp/linux_amd64/libs - - tools/bin/ldd_fix - build_flag_templates: - - "--platform=linux/amd64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" - - id: linux-arm64 - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: arm64 - extra_files: - - tmp/linux_arm64/libs - - tools/bin/ldd_fix - build_flag_templates: - - "--platform=linux/arm64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" - - id: linux-amd64-plugins - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: amd64 - extra_files: - - tmp/linux_amd64/libs - - tmp/linux_amd64/plugins - - tools/bin/ldd_fix - build_flag_templates: - - "--platform=linux/amd64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" - - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" - - "--build-arg=CL_SOLANA_CMD=chainlink-solana" - - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" - - id: linux-arm64-plugins - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: arm64 - extra_files: - - tmp/linux_arm64/libs - - tmp/linux_arm64/plugins - - tools/bin/ldd_fix - build_flag_templates: - - "--platform=linux/arm64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" - - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" - - "--build-arg=CL_SOLANA_CMD=chainlink-solana" - - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" - -# See https://goreleaser.com/customization/docker_manifest/ + - id: linux-amd64-chainlink + goos: linux + goarch: amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink:{{ .Env.IMG_TAG }}' + - '{{ .Env.IMG_PRE }}/chainlink:{{ .Env.IMG_TAG }}-amd64' + - '{{ .Env.IMG_PRE }}/chainlink:sha-{{ .ShortCommit }}-amd64' + extra_files: + - tmp/libs + build_flag_templates: + - --platform=linux/amd64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-amd64-chainlink-plugins + goos: linux + goarch: amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink:{{ .Env.IMG_TAG }}-plugins' + - '{{ .Env.IMG_PRE }}/chainlink:{{ .Env.IMG_TAG }}-plugins-amd64' + - '{{ .Env.IMG_PRE }}/chainlink:sha-{{ .ShortCommit }}-plugins-amd64' + extra_files: + - tmp/libs + - tmp/plugins + build_flag_templates: + - --platform=linux/amd64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_MEDIAN_CMD=chainlink-feeds + - --build-arg=CL_MERCURY_CMD=chainlink-mercury + - --build-arg=CL_SOLANA_CMD=chainlink-solana + - --build-arg=CL_STARKNET_CMD=chainlink-starknet + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-arm64-chainlink + goos: linux + goarch: arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink:{{ .Env.IMG_TAG }}-arm64' + - '{{ .Env.IMG_PRE }}/chainlink:sha-{{ .ShortCommit }}-arm64' + extra_files: + - tmp/libs + build_flag_templates: + - --platform=linux/arm64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-arm64-chainlink-plugins + goos: linux + goarch: arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink:{{ .Env.IMG_TAG }}-plugins-arm64' + - '{{ .Env.IMG_PRE }}/chainlink:sha-{{ .ShortCommit }}-plugins-arm64' + extra_files: + - tmp/libs + - tmp/plugins + build_flag_templates: + - --platform=linux/arm64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_MEDIAN_CMD=chainlink-feeds + - --build-arg=CL_MERCURY_CMD=chainlink-mercury + - --build-arg=CL_SOLANA_CMD=chainlink-solana + - --build-arg=CL_STARKNET_CMD=chainlink-starknet + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-amd64-ccip + goos: linux + goarch: amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/ccip:{{ .Env.IMG_TAG }}' + - '{{ .Env.IMG_PRE }}/ccip:{{ .Env.IMG_TAG }}-amd64' + - '{{ .Env.IMG_PRE }}/ccip:sha-{{ .ShortCommit }}-amd64' + extra_files: + - tmp/libs + - ccip/config + build_flag_templates: + - --platform=linux/amd64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-amd64-ccip-plugins + goos: linux + goarch: amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/ccip:{{ .Env.IMG_TAG }}-plugins' + - '{{ .Env.IMG_PRE }}/ccip:{{ .Env.IMG_TAG }}-plugins-amd64' + - '{{ .Env.IMG_PRE }}/ccip:sha-{{ .ShortCommit }}-plugins-amd64' + extra_files: + - tmp/libs + - tmp/plugins + - ccip/config + build_flag_templates: + - --platform=linux/amd64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config + - --build-arg=CL_MEDIAN_CMD=chainlink-feeds + - --build-arg=CL_MERCURY_CMD=chainlink-mercury + - --build-arg=CL_SOLANA_CMD=chainlink-solana + - --build-arg=CL_STARKNET_CMD=chainlink-starknet + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-arm64-ccip + goos: linux + goarch: arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/ccip:{{ .Env.IMG_TAG }}-arm64' + - '{{ .Env.IMG_PRE }}/ccip:sha-{{ .ShortCommit }}-arm64' + extra_files: + - tmp/libs + - ccip/config + build_flag_templates: + - --platform=linux/arm64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-arm64-ccip-plugins + goos: linux + goarch: arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/ccip:{{ .Env.IMG_TAG }}-plugins-arm64' + - '{{ .Env.IMG_PRE }}/ccip:sha-{{ .ShortCommit }}-plugins-arm64' + extra_files: + - tmp/libs + - tmp/plugins + - ccip/config + build_flag_templates: + - --platform=linux/arm64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config + - --build-arg=CL_MEDIAN_CMD=chainlink-feeds + - --build-arg=CL_MERCURY_CMD=chainlink-mercury + - --build-arg=CL_SOLANA_CMD=chainlink-solana + - --build-arg=CL_STARKNET_CMD=chainlink-starknet + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx docker_manifests: - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" - -# See https://goreleaser.com/customization/docker_sign/ -docker_signs: - - artifacts: all - args: - - "sign" - - "${artifact}" - - "--yes" - -checksum: - name_template: "checksums.txt" - -snapshot: - version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" - -partial: - by: target - -# See https://goreleaser.com/customization/release/ -release: - disable: true - + - id: tagged-chainlink + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink:{{ .Env.IMG_TAG }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink:{{ .Env.IMG_TAG }}' + - '{{ .Env.IMAGE_PREFIX }}/chainlink:{{ .Env.IMG_TAG }}-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink:{{ .Env.IMG_TAG }}-arm64' + - id: sha-chainlink + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink:sha-{{ .ShortCommit }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink:sha-{{ .ShortCommit }}-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink:sha-{{ .ShortCommit }}-arm64' + - id: tagged-plugins-chainlink + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink:{{ .Env.IMG_TAG }}-plugins' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink:{{ .Env.IMG_TAG }}-plugins' + - '{{ .Env.IMAGE_PREFIX }}/chainlink:{{ .Env.IMG_TAG }}-plugins-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink:{{ .Env.IMG_TAG }}-plugins-arm64' + - id: sha-plugins-chainlink + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink:sha-{{ .ShortCommit }}-plugins' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink:sha-{{ .ShortCommit }}-plugins-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink:sha-{{ .ShortCommit }}-plugins-arm64' + - id: tagged-ccip + name_template: '{{ .Env.IMAGE_PREFIX }}/ccip:{{ .Env.IMG_TAG }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/ccip:{{ .Env.IMG_TAG }}' + - '{{ .Env.IMAGE_PREFIX }}/ccip:{{ .Env.IMG_TAG }}-amd64' + - '{{ .Env.IMAGE_PREFIX }}/ccip:{{ .Env.IMG_TAG }}-arm64' + - id: sha-ccip + name_template: '{{ .Env.IMAGE_PREFIX }}/ccip:sha-{{ .ShortCommit }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/ccip:sha-{{ .ShortCommit }}-amd64' + - '{{ .Env.IMAGE_PREFIX }}/ccip:sha-{{ .ShortCommit }}-arm64' + - id: tagged-plugins-ccip + name_template: '{{ .Env.IMAGE_PREFIX }}/ccip:{{ .Env.IMG_TAG }}-plugins' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/ccip:{{ .Env.IMG_TAG }}-plugins' + - '{{ .Env.IMAGE_PREFIX }}/ccip:{{ .Env.IMG_TAG }}-plugins-amd64' + - '{{ .Env.IMAGE_PREFIX }}/ccip:{{ .Env.IMG_TAG }}-plugins-arm64' + - id: sha-plugins-ccip + name_template: '{{ .Env.IMAGE_PREFIX }}/ccip:sha-{{ .ShortCommit }}-plugins' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/ccip:sha-{{ .ShortCommit }}-plugins-amd64' + - '{{ .Env.IMAGE_PREFIX }}/ccip:sha-{{ .ShortCommit }}-plugins-arm64' changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" -# modelines, feel free to remove those if you don't want/use them: -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj + disable: "true" +before: + hooks: + - cmd: go mod tidy + - cmd: ./tools/bin/goreleaser_utils before_hook +partial: + by: target +nightly: + version_template: '{{ .Env.VERSION }}-{{ .Env.IMG_TAG }}' diff --git a/.goreleaser.production.yaml b/.goreleaser.production.yaml index 0274f1322b8..ada9b847e74 100644 --- a/.goreleaser.production.yaml +++ b/.goreleaser.production.yaml @@ -1,221 +1,323 @@ -project_name: chainlink - version: 2 - +project_name: chainlink env: - - ZIG_EXEC={{ if index .Env "ZIG_EXEC" }}{{ .Env.ZIG_EXEC }}{{ else }}zig{{ end }} - - IMAGE_PREFIX={{ if index .Env "IMAGE_PREFIX" }}{{ .Env.IMAGE_PREFIX }}{{ else }}localhost:5001{{ end }} - - IMAGE_NAME={{ if index .Env "IMAGE_NAME" }}{{ .Env.IMAGE_NAME }}{{ else }}chainlink{{ end }} - - IMAGE_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} - - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" - - IMAGE_LABEL_LICENSES="MIT" - - IMAGE_LABEL_SOURCE="https://github.com/smartcontractkit/{{ .ProjectName }}" - -before: - hooks: - - go mod tidy - - ./tools/bin/goreleaser_utils before_hook - -# See https://goreleaser.com/customization/build/ + - IMG_PRE={{ if index .Env "IMAGE_PREFIX" }}{{ .Env.IMAGE_PREFIX }}{{ else }}localhost:5001{{ end }} + - IMG_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }} + - VERSION={{ if index .Env "CHAINLINK_VERSION" }}{{ .Env.CHAINLINK_VERSION }}{{ else }}v0.0.0-local{{ end }} +release: + disable: "true" builds: - - binary: chainlink - id: linux-arm64 - goos: - - linux - goarch: - - arm64 - hooks: - post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} - env: - - CGO_ENABLED=1 - - CC=$ZIG_EXEC cc -target aarch64-linux-gnu - - CCX=$ZIG_EXEC c++ -target aarch64-linux-gnu - flags: - - -trimpath - - -buildmode=pie - ldflags: - - -s -w -r=$ORIGIN/libs - - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} - - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} - - binary: chainlink - id: linux-amd64 - goos: - - linux - goarch: - - amd64 - hooks: - post: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} {{ .Os }} {{ .Arch }} - env: - - CGO_ENABLED=1 - - CC=$ZIG_EXEC cc -target x86_64-linux-gnu - - CCX=$ZIG_EXEC c++ -target x86_64-linux-gnu - flags: - - -trimpath - - -buildmode=pie - ldflags: - - -s -w -r=$ORIGIN/libs - - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.CHAINLINK_VERSION }} - - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} - -# See https://goreleaser.com/customization/docker/ + - targets: + - go_first_class + binary: chainlink + hooks: + post: + - cmd: ./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }} + no_unique_dist_dir: "true" + ldflags: + - -s -w -r=$ORIGIN/libs + - -X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.VERSION }} + - -X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }} + flags: + - -trimpath + - -buildmode=pie +archives: + - format: tar.gz +snapshot: + version_template: '{{ .Env.VERSION }}-{{ .ShortCommit }}' +checksum: + name_template: checksums.txt dockers: - - id: linux-amd64 - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: amd64 - extra_files: - - tmp/linux_amd64/libs - - tools/bin/ldd_fix - build_flag_templates: - - "--platform=linux/amd64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" - - id: linux-arm64 - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: arm64 - extra_files: - - tmp/linux_arm64/libs - - tools/bin/ldd_fix - build_flag_templates: - - "--platform=linux/arm64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" - - id: linux-amd64-plugins - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: amd64 - extra_files: - - tmp/linux_amd64/libs - - tmp/linux_amd64/plugins - - tools/bin/ldd_fix - build_flag_templates: - - "--platform=linux/amd64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" - - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" - - "--build-arg=CL_SOLANA_CMD=chainlink-solana" - - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" - - id: linux-arm64-plugins - dockerfile: core/chainlink.goreleaser.Dockerfile - use: buildx - goos: linux - goarch: arm64 - extra_files: - - tmp/linux_arm64/libs - - tmp/linux_arm64/plugins - - tools/bin/ldd_fix - build_flag_templates: - - "--platform=linux/arm64" - - "--pull" - - "--build-arg=CHAINLINK_USER=chainlink" - - "--build-arg=COMMIT_SHA={{ .FullCommit }}" - - "--build-arg=CL_MEDIAN_CMD=chainlink-feeds" - - "--build-arg=CL_MERCURY_CMD=chainlink-mercury" - - "--build-arg=CL_SOLANA_CMD=chainlink-solana" - - "--build-arg=CL_STARKNET_CMD=chainlink-starknet" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.description={{ .Env.IMAGE_LABEL_DESCRIPTION }}" - - "--label=org.opencontainers.image.licenses={{ .Env.IMAGE_LABEL_LICENSES }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - - "--label=org.opencontainers.image.source={{ .Env.IMAGE_LABEL_SOURCE }}" - - "--label=org.opencontainers.image.title={{ .ProjectName }}" - - "--label=org.opencontainers.image.version={{ .Env.CHAINLINK_VERSION }}" - - "--label=org.opencontainers.image.url={{ .Env.IMAGE_LABEL_SOURCE }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" - -# See https://goreleaser.com/customization/docker_manifest/ + - id: linux-amd64-chainlink + goos: linux + goarch: amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-amd64' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-amd64' + skip_push: '{{ contains .Tag "-ccip" }}' + extra_files: + - tmp/libs + build_flag_templates: + - --platform=linux/amd64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-amd64-chainlink-plugins + goos: linux + goarch: amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins-amd64' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins-amd64' + skip_push: '{{ contains .Tag "-ccip" }}' + extra_files: + - tmp/libs + - tmp/plugins + build_flag_templates: + - --platform=linux/amd64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_MEDIAN_CMD=chainlink-feeds + - --build-arg=CL_MERCURY_CMD=chainlink-mercury + - --build-arg=CL_SOLANA_CMD=chainlink-solana + - --build-arg=CL_STARKNET_CMD=chainlink-starknet + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-arm64-chainlink + goos: linux + goarch: arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-arm64' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-arm64' + skip_push: '{{ contains .Tag "-ccip" }}' + extra_files: + - tmp/libs + build_flag_templates: + - --platform=linux/arm64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-arm64-chainlink-plugins + goos: linux + goarch: arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins-arm64' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins-arm64' + skip_push: '{{ contains .Tag "-ccip" }}' + extra_files: + - tmp/libs + - tmp/plugins + build_flag_templates: + - --platform=linux/arm64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_MEDIAN_CMD=chainlink-feeds + - --build-arg=CL_MERCURY_CMD=chainlink-mercury + - --build-arg=CL_SOLANA_CMD=chainlink-solana + - --build-arg=CL_STARKNET_CMD=chainlink-starknet + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-amd64-ccip + goos: linux + goarch: amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-amd64' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-amd64' + skip_push: '{{ not (contains .Tag "-ccip") }}' + extra_files: + - tmp/libs + - ccip/config + build_flag_templates: + - --platform=linux/amd64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-amd64-ccip-plugins + goos: linux + goarch: amd64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins-amd64' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins-amd64' + skip_push: '{{ not (contains .Tag "-ccip") }}' + extra_files: + - tmp/libs + - tmp/plugins + - ccip/config + build_flag_templates: + - --platform=linux/amd64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config + - --build-arg=CL_MEDIAN_CMD=chainlink-feeds + - --build-arg=CL_MERCURY_CMD=chainlink-mercury + - --build-arg=CL_SOLANA_CMD=chainlink-solana + - --build-arg=CL_STARKNET_CMD=chainlink-starknet + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-arm64-ccip + goos: linux + goarch: arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-arm64' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-arm64' + skip_push: '{{ not (contains .Tag "-ccip") }}' + extra_files: + - tmp/libs + - ccip/config + build_flag_templates: + - --platform=linux/arm64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx + - id: linux-arm64-ccip-plugins + goos: linux + goarch: arm64 + dockerfile: core/chainlink.goreleaser.Dockerfile + image_templates: + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins-arm64' + - '{{ .Env.IMG_PRE }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins-arm64' + skip_push: '{{ not (contains .Tag "-ccip") }}' + extra_files: + - tmp/libs + - tmp/plugins + - ccip/config + build_flag_templates: + - --platform=linux/arm64 + - --pull + - --build-arg=CHAINLINK_USER=chainlink + - --build-arg=COMMIT_SHA={{ .FullCommit }} + - --build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config + - --build-arg=CL_MEDIAN_CMD=chainlink-feeds + - --build-arg=CL_MERCURY_CMD=chainlink-mercury + - --build-arg=CL_SOLANA_CMD=chainlink-solana + - --build-arg=CL_STARKNET_CMD=chainlink-starknet + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation" + - --label=org.opencontainers.image.licenses=MIT + - --label=org.opencontainers.image.revision={{ .FullCommit }} + - --label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink + - --label=org.opencontainers.image.title=chainlink + - --label=org.opencontainers.image.version={{ .Env.VERSION }} + - --label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink + use: buildx docker_manifests: - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:{{ .Env.IMAGE_TAG }}-plugins-arm64" - - name_template: "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins" - image_templates: - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-amd64" - - "{{ .Env.IMAGE_PREFIX }}/{{ .Env.IMAGE_NAME }}:sha-{{ .ShortCommit }}-plugins-arm64" - -# See https://goreleaser.com/customization/docker_sign/ -docker_signs: - - artifacts: all - args: - - "sign" - - "${artifact}" - - "--yes" - -checksum: - name_template: "checksums.txt" - -# See https://goreleaser.com/customization/sbom + - id: tagged-chainlink-chainlink-experimental-goreleaser + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}' + skip_push: '{{ contains .Tag "-ccip" }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-arm64' + - id: sha-chainlink-chainlink-experimental-goreleaser + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}' + skip_push: '{{ contains .Tag "-ccip" }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-arm64' + - id: tagged-plugins-chainlink-chainlink-experimental-goreleaser + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins' + skip_push: '{{ contains .Tag "-ccip" }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins-arm64' + - id: sha-plugins-chainlink-chainlink-experimental-goreleaser + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins' + skip_push: '{{ contains .Tag "-ccip" }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins-arm64' + - id: tagged-chainlink-chainlink-ccip-experimental-goreleaser + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}' + skip_push: '{{ not (contains .Tag "-ccip") }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-arm64' + - id: sha-chainlink-chainlink-ccip-experimental-goreleaser + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}' + skip_push: '{{ not (contains .Tag "-ccip") }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-arm64' + - id: tagged-plugins-chainlink-chainlink-ccip-experimental-goreleaser + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins' + skip_push: '{{ not (contains .Tag "-ccip") }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:{{ .Env.IMG_TAG }}-plugins-arm64' + - id: sha-plugins-chainlink-chainlink-ccip-experimental-goreleaser + name_template: '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins' + skip_push: '{{ not (contains .Tag "-ccip") }}' + image_templates: + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins-amd64' + - '{{ .Env.IMAGE_PREFIX }}/chainlink/chainlink-ccip-experimental-goreleaser:sha-{{ .ShortCommit }}-plugins-arm64' +changelog: + filters: + exclude: + - '^docs:' + - '^test:' + sort: asc +before: + hooks: + - cmd: go mod tidy + - cmd: ./tools/bin/goreleaser_utils before_hook sboms: - - artifacts: archive - -snapshot: - version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" - + - artifacts: archive partial: - by: target - -# See https://goreleaser.com/customization/release/ -release: - disable: true - -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" -# modelines, feel free to remove those if you don't want/use them: -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj + by: target +nightly: + version_template: '{{ .Env.VERSION }}-{{ .Env.IMG_TAG }}' diff --git a/.tool-versions b/.tool-versions index 345e10e2afa..f4817b39d08 100644 --- a/.tool-versions +++ b/.tool-versions @@ -4,6 +4,5 @@ nodejs 20.13.1 pnpm 9.4.0 postgres 15.1 helm 3.10.3 -zig 0.11.0 golangci-lint 1.60.3 protoc 25.1 diff --git a/GNUmakefile b/GNUmakefile index e2a3508e199..4080f87b734 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -172,16 +172,6 @@ golangci-lint: ## Run golangci-lint for all issues. [ -d "./golangci-lint" ] || mkdir ./golangci-lint && \ docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v1.59.1 golangci-lint run --max-issues-per-linter 0 --max-same-issues 0 | tee ./golangci-lint/$(shell date +%Y-%m-%d_%H:%M:%S).txt -GORELEASER_CONFIG ?= .goreleaser.yaml - -.PHONY: goreleaser-dev-build -goreleaser-dev-build: ## Run goreleaser snapshot build - ./tools/bin/goreleaser_wrapper build --snapshot --rm-dist --config ${GORELEASER_CONFIG} - -.PHONY: goreleaser-dev-release -goreleaser-dev-release: ## run goreleaser snapshot release - ./tools/bin/goreleaser_wrapper release --snapshot --rm-dist --config ${GORELEASER_CONFIG} - .PHONY: modgraph modgraph: ./tools/bin/modgraph > go.md diff --git a/core/chainlink.goreleaser.Dockerfile b/core/chainlink.goreleaser.Dockerfile index c229ad488c3..eb359376006 100644 --- a/core/chainlink.goreleaser.Dockerfile +++ b/core/chainlink.goreleaser.Dockerfile @@ -1,12 +1,12 @@ # This will replace chainlink.Dockerfile once all builds are migrated to goreleaser # Final image: ubuntu with chainlink binary -FROM ubuntu:20.04 +FROM ubuntu:24.04 ARG CHAINLINK_USER=root ARG TARGETARCH ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update && apt-get install -y ca-certificates gnupg lsb-release curl patchelf +RUN apt-get update && apt-get install -y ca-certificates gnupg lsb-release curl # Install Postgres for CLI tools, needed specifically for DB backups RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ @@ -18,11 +18,12 @@ RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ COPY ./chainlink /usr/local/bin/ # Copy native libs if cgo is enabled -COPY ./tmp/linux_${TARGETARCH}/libs /usr/local/bin/libs +COPY ./tmp/libs /usr/local/bin/libs # Copy plugins if exist and enable them # https://stackoverflow.com/questions/70096208/dockerfile-copy-folder-if-it-exists-conditional-copy/70096420#70096420 -COPY ./tmp/linux_${TARGETARCH}/plugin[s] /usr/local/bin/ +COPY ./tm[p]/plugin[s]/ /usr/local/bin/ + # Allow individual plugins to be enabled by supplying their path ARG CL_MEDIAN_CMD ARG CL_MERCURY_CMD @@ -32,11 +33,6 @@ ENV CL_MEDIAN_CMD=${CL_MEDIAN_CMD} \ CL_MERCURY_CMD=${CL_MERCURY_CMD} \ CL_SOLANA_CMD=${CL_SOLANA_CMD} \ CL_STARKNET_CMD=${CL_STARKNET_CMD} -# Temp fix to patch correctly link the libwasmvm.so -COPY ./tools/bin/ldd_fix /usr/local/bin/ldd_fix -RUN chmod +x /usr/local/bin/ldd_fix -RUN /usr/local/bin/ldd_fix -RUN apt-get remove -y patchelf # CCIP specific COPY ./cci[p]/confi[g] /chainlink/ccip-config diff --git a/shell.nix b/shell.nix index 3626cb11cf4..ba09ebc219d 100644 --- a/shell.nix +++ b/shell.nix @@ -44,9 +44,6 @@ in github-cli jq - # cross-compiling, used in CRIB - zig - # gofuzz ] ++ lib.optionals stdenv.isLinux [ diff --git a/tools/bin/goreleaser_utils b/tools/bin/goreleaser_utils index 979204d1e3a..a01c1654133 100755 --- a/tools/bin/goreleaser_utils +++ b/tools/bin/goreleaser_utils @@ -1,90 +1,24 @@ #!/usr/bin/env bash set -xe - -# get machine / kernel name -_get_platform() { - uname | tr '[:upper:]' '[:lower:]' -} - -# get machine architecture name -# See https://github.com/joschi/asdf-java/blob/aarch64-support/bin/functions#L33 -_get_arch() { - arch="$(uname -m)" - case "${arch}" in - x86_64 | amd64) echo "x86_64" ;; - aarch64 | arm64) echo "arm64" ;; - *) - echo "Unknown machine architecture: ${arch}" - exit 1 - ;; - esac -} - -# get lib wasmvm path -_get_wasmvm_lib_path() { - local -r platform="$1" - local -r arch="$2" - wasmvm_dir=$(go list -json -m github.com/CosmWasm/wasmvm | jq -r '.Dir') - shared_lib_dir="$wasmvm_dir/internal/api" - lib_name="libwasmvm" - if [ "$platform" == "darwin" ]; then - lib_extension="dylib" - elif [ "$platform" == "linux" ]; then - case "${arch}" in - x86_64 | amd64) lib_extension="x86_64.so" ;; - aarch64 | arm64) lib_extension="aarch64.so" ;; - *) echo "Unsupported arch $arch" && exit 1 ;; - esac - else - echo "Unsupported platform $platform" - exit 1 - fi - echo "$shared_lib_dir/${lib_name}.$lib_extension" -} - # global goreleaser before hook # moves native libraries to temp directories used by docker images / archives before_hook() { local -r lib_path=tmp - # MOVE NATIVE LIBRARIES HERE - local -r wasmvm_lib_path_linux_amd64=$(_get_wasmvm_lib_path "linux" "amd64") - local -r wasmvm_lib_path_linux_arm64=$(_get_wasmvm_lib_path "linux" "arm64") - local -r wasmvm_lib_path_darwin_amd64=$(_get_wasmvm_lib_path "darwin" "amd64") - local -r wasmvm_lib_path_darwin_arm64=$(_get_wasmvm_lib_path "darwin" "arm64") - mkdir -p "$lib_path/linux_amd64/libs" - cp -f "$wasmvm_lib_path_linux_amd64" "$lib_path/linux_amd64/libs" - mkdir -p "$lib_path/linux_arm64/libs" - cp -f "$wasmvm_lib_path_linux_arm64" "$lib_path/linux_arm64/libs" - mkdir -p "$lib_path/darwin_amd64/libs" - cp -f "$wasmvm_lib_path_darwin_amd64" "$lib_path/darwin_amd64/libs" - mkdir -p "$lib_path/darwin_arm64/libs" - cp -f "$wasmvm_lib_path_darwin_arm64" "$lib_path/darwin_arm64/libs" - - # MOVE PLUGINS HERE - gobin=$(go env GOPATH)/bin - install_local_plugins "linux" "amd64" "$gobin"/linux_amd64/ - install_remote_plugins "linux" "amd64" "$gobin"/linux_amd64/ - mkdir -p "$lib_path/linux_amd64/plugins" - cp "$gobin"/linux_amd64/chainlink* "$lib_path/linux_amd64/plugins" - cp "$gobin"/chainlink* "$lib_path/linux_amd64/plugins" + mkdir -p "$lib_path/libs" + # Copy over all platform versions of the wasmvm library + cp -f "$(go list -json -m github.com/CosmWasm/wasmvm | jq -r '.Dir')"/internal/api/libwasmvm.* "$lib_path/libs" - install_local_plugins "linux" "arm64" "$gobin"/linux_arm64/ - install_remote_plugins "linux" "arm64" "$gobin"/linux_arm64/ - mkdir -p "$lib_path/linux_arm64/plugins" - cp "$gobin"/linux_arm64/chainlink* "$lib_path/linux_arm64/plugins" - cp "$gobin"/chainlink* "$lib_path/linux_arm64/plugins" + install_local_plugins + install_remote_plugins + mkdir -p "$lib_path/plugins" + cp "$(go env GOPATH)"/bin/chainlink* "$lib_path/plugins" } install_local_plugins() { - local -r goos=$1 - local -r goarch=$2 - local -r gobin=$3 - ldf="$(./tools/bin/ldflags)" - ldflags=(-ldflags "$ldf") - GOARCH=$goarch GOOS=$goos go build -o $gobin "${ldflags[@]}" ./plugins/cmd/chainlink-medianpoc - GOARCH=$goarch GOOS=$goos go build -o $gobin "${ldflags[@]}" ./plugins/cmd/chainlink-ocr3-capability + make install-medianpoc + make install-ocr3-capability } get_remote_plugin_paths() { @@ -105,13 +39,10 @@ get_remote_plugin_paths() { } install_remote_plugins() { - local -r goos=$1 - local -r goarch=$2 - local -r gobin=$(go env GOPATH)/bin ldflags=(-ldflags "$(./tools/bin/ldflags)") for plugin in $(get_remote_plugin_paths); do - GOARCH=$goarch GOOS=$goos go build -o $gobin "${ldflags[@]}" "$plugin" + go install "${ldflags[@]}" "$plugin" done } @@ -120,16 +51,14 @@ install_remote_plugins() { # moves native libraries to binary libs directory build_post_hook() { local -r dist_path=$1 + local -r plugin_src_path=./tmp/plugins + local -r wasmvm_lib_path=./tmp/libs local -r lib_dest_path=$dist_path/libs - local -r platform=$2 - local -r arch=$3 - local -r plugin_src_path=./tmp/${platform}_${arch}/plugins local -r plugin_dest_path=$dist_path/plugins # COPY NATIVE LIBRARIES HERE - local -r wasmvm_lib_path=$(_get_wasmvm_lib_path "$platform" "$arch") mkdir -p "$lib_dest_path" - cp "$wasmvm_lib_path" "$lib_dest_path" + cp -r "$wasmvm_lib_path/." "$lib_dest_path" # COPY PLUGINS HERE mkdir -p "$plugin_dest_path" diff --git a/tools/bin/goreleaser_wrapper b/tools/bin/goreleaser_wrapper deleted file mode 100755 index d4b16d1c549..00000000000 --- a/tools/bin/goreleaser_wrapper +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -set -euo pipefail -set -x -# get machine / kernel name -_get_platform() { - uname | tr '[:upper:]' '[:lower:]' -} - -# get macos sdk directory -_get_macos_sdk_dir() { - if [[ -z "${MACOS_SDK_DIR-}" ]]; then - platform=$(_get_platform) - if [[ "$platform" = 'darwin' ]]; then - if [[ "$(command -v xcrun)" ]]; then - echo "$(xcrun --sdk macosx --show-sdk-path)" - else - echo "You need to have MacOS Command Line Tools installed, you can install it via '$ xcode-select --install'" - exit 1 - fi - else - echo "You must set the MACOS_SDK_DIR env var to where you have the MacOS SDK installed" - echo "If you do not have a MacOS SDK installed, see https://github.com/joseluisq/macosx-sdks/tree/12.3 to obtain one" - exit 1 - fi - else - echo "$MACOS_SDK_DIR" - fi -} - -macos_sdk_dir=$(_get_macos_sdk_dir) -framework_search_path="/System/Library/Frameworks" -include_search_path='/usr/include' - -ZIG_FLAGS_DARWIN="-isysroot$macos_sdk_dir \ - -F$macos_sdk_dir$framework_search_path \ - -iframeworkwithsysroot$framework_search_path \ - -iwithsysroot$include_search_path \ - -mmacosx-version-min=11.7.1" \ -ZIG_EXEC=$(which zig) \ -CHAINLINK_VERSION=$(jq -r '.version' package.json) \ -goreleaser "$@" diff --git a/tools/bin/ldd_fix b/tools/bin/ldd_fix deleted file mode 100755 index dd48e9c3b30..00000000000 --- a/tools/bin/ldd_fix +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# This script is used as a temp fix the ldd linking of cosm lib for binary -# Currently there is an issue with the go linker not working with zig -# https://github.com/ziglang/zig/issues/18922 - -chainlink_path="/usr/local/bin/chainlink" -libs_path="/usr/local/bin/libs" - -line=$(ldd ${chainlink_path} | grep "github.com/!cosm!wasm/wasmvm") - -if [ -z "$line" ]; then - echo "Error: Path containing 'github.com/!cosm!wasm/wasmvm' not found in the ldd output." - exit 1 -fi - -path=$(echo "$line" | awk '{print $1}') - -if [ -z "$path" ]; then - echo "Error: Failed to extract the path from the line." - exit 1 -fi - -trimmed_path=${path%.so*}.so -cosm_file=$(ls ${libs_path} | grep "\.so$" | head -n 1) - -patchelf --remove-needed "${trimmed_path}" "$chainlink_path" -patchelf --add-needed "$cosm_file" "$chainlink_path" diff --git a/tools/goreleaser-config/gen_config.go b/tools/goreleaser-config/gen_config.go new file mode 100644 index 00000000000..9108ec1bbd1 --- /dev/null +++ b/tools/goreleaser-config/gen_config.go @@ -0,0 +1,363 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/goreleaser/goreleaser-pro/v2/pkg/config" +) + +// Generate creates the goreleaser configuration based on the environment. +var validEnvironments = []string{"devspace", "develop", "production"} + +func Generate(environment string) config.Project { + checkEnvironments(environment) + + project := config.Project{ + ProjectName: "chainlink", + Version: 2, + Env: commonEnv(), + Before: config.Before{ + Hooks: []config.Hook{ + { + Cmd: "go mod tidy", + }, + { + Cmd: "./tools/bin/goreleaser_utils before_hook", + }, + }, + }, + Builds: builds(environment), + Dockers: dockers(environment), + DockerManifests: dockerManifests(environment), + Checksum: config.Checksum{ + NameTemplate: "checksums.txt", + }, + Snapshot: config.Snapshot{ + VersionTemplate: "{{ .Env.VERSION }}-{{ .ShortCommit }}", + }, + Nightly: config.Nightly{ + VersionTemplate: "{{ .Env.VERSION }}-{{ .Env.IMG_TAG }}", + }, + Partial: config.Partial{ + By: "target", + }, + Release: config.Release{ + Disable: "true", + }, + Archives: []config.Archive{ + { + Format: "binary", + }, + }, + Changelog: config.Changelog{ + Disable: "true", + }, + } + + // Add SBOMs if needed + if environment == "production" { + project.Changelog = config.Changelog{ + Sort: "asc", + Filters: config.Filters{ + Exclude: []string{ + "^docs:", + "^test:", + }, + }, + } + project.Archives = []config.Archive{ + { + Format: "tar.gz", + }, + } + project.SBOMs = []config.SBOM{ + { + Artifacts: "archive", + }, + } + } + + return project +} + +func checkEnvironments(environment string) { + valid := false + for _, env := range validEnvironments { + if environment == env { + valid = true + break + } + } + if !valid { + panic(fmt.Sprintf("invalid environment: %s, valid environments are %v", environment, validEnvironments)) + } +} + +// commonEnv returns the common environment variables used across environments. +func commonEnv() []string { + return []string{ + `IMG_PRE={{ if index .Env "IMAGE_PREFIX" }}{{ .Env.IMAGE_PREFIX }}{{ else }}localhost:5001{{ end }}`, + `IMG_TAG={{ if index .Env "IMAGE_TAG" }}{{ .Env.IMAGE_TAG }}{{ else }}develop{{ end }}`, + `VERSION={{ if index .Env "CHAINLINK_VERSION" }}{{ .Env.CHAINLINK_VERSION }}{{ else }}v0.0.0-local{{ end }}`, + } +} + +// builds returns the build configurations based on the environment. +func builds(environment string) []config.Build { + switch environment { + case "devspace": + return []config.Build{ + build(true), + } + case "develop", "production": + return []config.Build{ + build(false), + } + + default: + return nil + } +} + +// build creates a build configuration. +func build(isDevspace bool) config.Build { + ldflags := []string{ + "-s -w -r=$ORIGIN/libs", + "-X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Env.VERSION }}", + "-X github.com/smartcontractkit/chainlink/v2/core/static.Sha={{ .FullCommit }}", + } + if isDevspace { + ldflags[2] = "-X github.com/smartcontractkit/chainlink/v2/core/static.Version={{ .Version }}" + } + + return config.Build{ + Binary: "chainlink", + NoUniqueDistDir: "true", + Targets: []string{"go_first_class"}, + Hooks: config.BuildHookConfig{ + Post: []config.Hook{ + {Cmd: "./tools/bin/goreleaser_utils build_post_hook {{ dir .Path }}"}, + }, + }, + BuildDetails: config.BuildDetails{ + Flags: []string{"-trimpath", "-buildmode=pie"}, + Ldflags: ldflags, + }, + } +} + +// dockers returns the docker configurations based on the environment. +func dockers(environment string) []config.Docker { + var dockers []config.Docker + switch environment { + case "devspace": + dockers = []config.Docker{ + docker("linux-amd64", "linux", "amd64", environment, true), + } + + case "develop", "production": + architectures := []string{"amd64", "arm64"} + imageNames := []string{"chainlink", "ccip"} + + for _, imageName := range imageNames { + for _, arch := range architectures { + id := fmt.Sprintf("linux-%s-%s", arch, imageName) + pluginId := fmt.Sprintf("%s-plugins", id) + + dockers = append(dockers, docker(id, "linux", arch, environment, false)) + dockers = append(dockers, docker(pluginId, "linux", arch, environment, false)) + } + } + } + return dockers +} + +// docker creates a docker configuration. +func docker(id, goos, goarch, environment string, isDevspace bool) config.Docker { + isCCIP := strings.Contains(id, "ccip") + isPlugins := strings.Contains(id, "plugins") + extraFiles := []string{"tmp/libs"} + if isPlugins || isDevspace { + extraFiles = append(extraFiles, "tmp/plugins") + } + if isCCIP { + extraFiles = append(extraFiles, "ccip/config") + } + + buildFlagTemplates := []string{ + fmt.Sprintf("--platform=%s/%s", goos, goarch), + "--pull", + "--build-arg=CHAINLINK_USER=chainlink", + "--build-arg=COMMIT_SHA={{ .FullCommit }}", + } + + if strings.Contains(id, "ccip") { + buildFlagTemplates = append(buildFlagTemplates, + "--build-arg=CL_CHAIN_DEFAULTS=/chainlink/ccip-config") + } + + if strings.Contains(id, "plugins") || isDevspace { + buildFlagTemplates = append(buildFlagTemplates, + "--build-arg=CL_MEDIAN_CMD=chainlink-feeds", + "--build-arg=CL_MERCURY_CMD=chainlink-mercury", + "--build-arg=CL_SOLANA_CMD=chainlink-solana", + "--build-arg=CL_STARKNET_CMD=chainlink-starknet", + ) + } + + buildFlagTemplates = append(buildFlagTemplates, + `--label=org.opencontainers.image.created={{ .Date }}`, + `--label=org.opencontainers.image.description="node of the decentralized oracle network, bridging on and off-chain computation"`, + `--label=org.opencontainers.image.licenses=MIT`, + `--label=org.opencontainers.image.revision={{ .FullCommit }}`, + `--label=org.opencontainers.image.source=https://github.com/smartcontractkit/chainlink`, + `--label=org.opencontainers.image.title=chainlink`, + `--label=org.opencontainers.image.version={{ .Env.VERSION }}`, + `--label=org.opencontainers.image.url=https://github.com/smartcontractkit/chainlink`, + ) + + dockerConfig := config.Docker{ + ID: id, + Dockerfile: "core/chainlink.goreleaser.Dockerfile", + Use: "buildx", + Goos: goos, + Goarch: goarch, + Files: extraFiles, + BuildFlagTemplates: buildFlagTemplates, + } + + // We always want to build both versions as a test, but + // only push the relevant version based on the tag name + // + // We also expect the production config file to only be run during a tag push, + // enforced inside our github actions workflow, "build-publish" + if environment == "production" { + if isCCIP { + dockerConfig.SkipPush = "{{ not (contains .Tag \"-ccip\") }}" + } else { + dockerConfig.SkipPush = "{{ contains .Tag \"-ccip\" }}" + } + } + + // This section handles the image templates for the docker configuration + if environment == "devspace" { + dockerConfig.ImageTemplates = []string{"{{ .Env.IMAGE }}"} + } else { + base := "{{ .Env.IMG_PRE }}" + // On production envs, we have the ECR prefix for the image + if environment == "production" { + if isCCIP { + base = base + "/chainlink/chainlink-ccip-experimental-goreleaser" + } else { + base = base + "/chainlink/chainlink-experimental-goreleaser" + } + } else { + if isCCIP { + base = base + "/ccip" + } else { + base = base + "/chainlink" + } + } + + imageTemplates := []string{} + if strings.Contains(id, "plugins") { + taggedBase := fmt.Sprintf("%s:{{ .Env.IMG_TAG }}-plugins", base) + // We have a default, non-arch specific image for plugins that defaults to amd64 + if goarch == "amd64" { + imageTemplates = append(imageTemplates, taggedBase) + } + imageTemplates = append(imageTemplates, + fmt.Sprintf("%s-%s", taggedBase, archSuffix(id)), + fmt.Sprintf("%s:sha-{{ .ShortCommit }}-plugins-%s", base, archSuffix(id))) + } else { + taggedBase := fmt.Sprintf("%s:{{ .Env.IMG_TAG }}", base) + // We have a default, non-arch specific image for plugins that defaults to amd64 + if goarch == "amd64" { + imageTemplates = append(imageTemplates, taggedBase) + } + imageTemplates = append(imageTemplates, + fmt.Sprintf("%s-%s", taggedBase, archSuffix(id)), + fmt.Sprintf("%s:sha-{{ .ShortCommit }}-%s", base, archSuffix(id))) + } + + dockerConfig.ImageTemplates = imageTemplates + } + + return dockerConfig +} + +// archSuffix returns the architecture suffix for image tags. +func archSuffix(id string) string { + if strings.Contains(id, "arm64") { + return "arm64" + } + return "amd64" +} + +// dockerManifests returns the docker manifest configurations based on the environment. +func dockerManifests(environment string) []config.DockerManifest { + if environment == "devspace" { + return []config.DockerManifest{ + { + NameTemplate: "{{ .Env.IMAGE }}", + ImageTemplates: []string{"{{ .Env.IMAGE }}"}, + }, + } + } + + // Define the image names based on the environment + imageNames := []string{"chainlink", "ccip"} + + // FIXME: This is duplicated + if environment == "production" { + imageNames = []string{"chainlink/chainlink-experimental-goreleaser", "chainlink/chainlink-ccip-experimental-goreleaser"} + } + var manifests []config.DockerManifest + + for _, imageName := range imageNames { + fullImageName := fmt.Sprintf("{{ .Env.IMAGE_PREFIX }}/%s", imageName) + + manifestConfigs := []struct { + ID string + Suffix string + }{ + {ID: "tagged", Suffix: ":{{ .Env.IMG_TAG }}"}, + {ID: "sha", Suffix: ":sha-{{ .ShortCommit }}"}, + {ID: "tagged-plugins", Suffix: ":{{ .Env.IMG_TAG }}-plugins"}, + {ID: "sha-plugins", Suffix: ":sha-{{ .ShortCommit }}-plugins"}, + } + for _, cfg := range manifestConfigs { + nameTemplate := fmt.Sprintf("%s%s", fullImageName, cfg.Suffix) + manifest := config.DockerManifest{ + ID: strings.ReplaceAll(fmt.Sprintf("%s-%s", cfg.ID, imageName), "/", "-"), + NameTemplate: nameTemplate, + ImageTemplates: manifestImages(nameTemplate), + } + if environment == "production" { + if strings.Contains(nameTemplate, "ccip") { + manifest.SkipPush = "{{ not (contains .Tag \"-ccip\") }}" + } else { + manifest.SkipPush = "{{ contains .Tag \"-ccip\" }}" + } + } + manifests = append(manifests, manifest) + } + } + + return manifests +} + +// manifestImages generates image templates for docker manifests. +func manifestImages(imageName string) []string { + architectures := []string{"amd64", "arm64"} + var images []string + // Add the default image for tagged images + if !strings.Contains(imageName, "sha") { + images = append(images, imageName) + } + for _, arch := range architectures { + images = append(images, fmt.Sprintf("%s-%s", imageName, arch)) + } + return images +} diff --git a/tools/goreleaser-config/go.mod b/tools/goreleaser-config/go.mod new file mode 100644 index 00000000000..f46423b660d --- /dev/null +++ b/tools/goreleaser-config/go.mod @@ -0,0 +1,14 @@ +module github.com/smartcontractkit/chainlink/tools/goreleaser-config + +go 1.23.0 + +require ( + github.com/goreleaser/goreleaser-pro/v2 v2.3.2-pro + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/kr/pretty v0.3.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +) diff --git a/tools/goreleaser-config/go.sum b/tools/goreleaser-config/go.sum new file mode 100644 index 00000000000..e823cbd7de2 --- /dev/null +++ b/tools/goreleaser-config/go.sum @@ -0,0 +1,19 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/goreleaser/goreleaser-pro/v2 v2.3.2-pro h1:9zJQ9cxvLn0JJZsjomtjOnSB9W4nrpFazZ7KxDZmuoU= +github.com/goreleaser/goreleaser-pro/v2 v2.3.2-pro/go.mod h1:GA7Uzk7qKA3efeDmgfWwcMTrDJe+V7D6H5RMqXlFvuc= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/goreleaser-config/main.go b/tools/goreleaser-config/main.go new file mode 100644 index 00000000000..852b5b580ae --- /dev/null +++ b/tools/goreleaser-config/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "gopkg.in/yaml.v3" + "os" +) + +func main() { + environments := []string{"develop", "production"} + for _, e := range environments { + cfg := Generate(e) + data, err := yaml.Marshal(&cfg) + if err != nil { + panic(err) + } + filename := fmt.Sprintf("../../.goreleaser.%s.yaml", e) + err = os.WriteFile(filename, data, 0644) + if err != nil { + panic(err) + } + fmt.Printf("Generated %s\n", filename) + } +} From 659b75adf96f294bbd784747deb8cfda3a1b8295 Mon Sep 17 00:00:00 2001 From: Makram Date: Wed, 2 Oct 2024 19:30:21 +0400 Subject: [PATCH 18/28] [CCIP-2917] core/capabilities/ccip: use relayers instead of legacyevm (#14611) * core/capabilities/ccip: use relayers instead of legacyevm * fix lint * address CR comments * fix * goimports --- .../ccip/configs/evm/chain_writer.go | 36 ++-- .../ccip/configs/evm/chain_writer_test.go | 103 +++++++++ .../ccip/configs/evm/contract_reader.go | 36 ++-- core/capabilities/ccip/delegate.go | 64 +++--- .../capabilities/ccip/oraclecreator/plugin.go | 195 ++++++++++-------- core/services/chainlink/application.go | 2 +- 6 files changed, 278 insertions(+), 158 deletions(-) create mode 100644 core/capabilities/ccip/configs/evm/chain_writer_test.go diff --git a/core/capabilities/ccip/configs/evm/chain_writer.go b/core/capabilities/ccip/configs/evm/chain_writer.go index 9bc377ba23e..f88bfce937b 100644 --- a/core/capabilities/ccip/configs/evm/chain_writer.go +++ b/core/capabilities/ccip/configs/evm/chain_writer.go @@ -1,7 +1,6 @@ package evm import ( - "encoding/json" "fmt" "github.com/ethereum/go-ethereum/accounts/abi" @@ -19,28 +18,29 @@ var ( offrampABI = evmtypes.MustGetABI(offramp.OffRampABI) ) -func MustChainWriterConfig( - fromAddress common.Address, - maxGasPrice *assets.Wei, - commitGasLimit, - execBatchGasLimit uint64, -) []byte { - rawConfig := ChainWriterConfigRaw(fromAddress, maxGasPrice, commitGasLimit, execBatchGasLimit) - encoded, err := json.Marshal(rawConfig) - if err != nil { - panic(fmt.Errorf("failed to marshal ChainWriterConfig: %w", err)) - } - - return encoded -} - // ChainWriterConfigRaw returns a ChainWriterConfig that can be used to transmit commit and execute reports. func ChainWriterConfigRaw( fromAddress common.Address, maxGasPrice *assets.Wei, commitGasLimit, execBatchGasLimit uint64, -) evmrelaytypes.ChainWriterConfig { +) (evmrelaytypes.ChainWriterConfig, error) { + if fromAddress == common.HexToAddress("0x0") { + return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("fromAddress cannot be zero") + } + if maxGasPrice == nil { + return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("maxGasPrice cannot be nil") + } + if maxGasPrice.Cmp(assets.NewWeiI(0)) <= 0 { + return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("maxGasPrice must be greater than zero") + } + if commitGasLimit == 0 { + return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("commitGasLimit must be greater than zero") + } + if execBatchGasLimit == 0 { + return evmrelaytypes.ChainWriterConfig{}, fmt.Errorf("execBatchGasLimit must be greater than zero") + } + return evmrelaytypes.ChainWriterConfig{ Contracts: map[string]*evmrelaytypes.ContractConfig{ consts.ContractNameOffRamp: { @@ -60,7 +60,7 @@ func ChainWriterConfigRaw( }, }, MaxGasPrice: maxGasPrice, - } + }, nil } // mustGetMethodName panics if the method name is not found in the provided ABI. diff --git a/core/capabilities/ccip/configs/evm/chain_writer_test.go b/core/capabilities/ccip/configs/evm/chain_writer_test.go new file mode 100644 index 00000000000..e24863866cc --- /dev/null +++ b/core/capabilities/ccip/configs/evm/chain_writer_test.go @@ -0,0 +1,103 @@ +package evm_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" +) + +func TestChainWriterConfigRaw(t *testing.T) { + tests := []struct { + name string + fromAddress common.Address + maxGasPrice *assets.Wei + commitGasLimit uint64 + execBatchGasLimit uint64 + expectedError string + }{ + { + name: "valid input", + fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + maxGasPrice: assets.NewWeiI(1000000000), + commitGasLimit: 21000, + execBatchGasLimit: 42000, + expectedError: "", + }, + { + name: "zero fromAddress", + fromAddress: common.HexToAddress("0x0"), + maxGasPrice: assets.NewWeiI(1000000000), + commitGasLimit: 21000, + execBatchGasLimit: 42000, + expectedError: "fromAddress cannot be zero", + }, + { + name: "nil maxGasPrice", + fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + maxGasPrice: nil, + commitGasLimit: 21000, + execBatchGasLimit: 42000, + expectedError: "maxGasPrice cannot be nil", + }, + { + name: "zero maxGasPrice", + fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + maxGasPrice: assets.NewWeiI(0), + commitGasLimit: 21000, + execBatchGasLimit: 42000, + expectedError: "maxGasPrice must be greater than zero", + }, + { + name: "negative maxGasPrice", + fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + maxGasPrice: assets.NewWeiI(-1), + commitGasLimit: 21000, + execBatchGasLimit: 42000, + expectedError: "maxGasPrice must be greater than zero", + }, + { + name: "zero commitGasLimit", + fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + maxGasPrice: assets.NewWeiI(1000000000), + commitGasLimit: 0, + execBatchGasLimit: 42000, + expectedError: "commitGasLimit must be greater than zero", + }, + { + name: "zero execBatchGasLimit", + fromAddress: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + maxGasPrice: assets.NewWeiI(1000000000), + commitGasLimit: 21000, + execBatchGasLimit: 0, + expectedError: "execBatchGasLimit must be greater than zero", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := evm.ChainWriterConfigRaw(tt.fromAddress, tt.maxGasPrice, tt.commitGasLimit, tt.execBatchGasLimit) + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + } else { + assert.NoError(t, err) + assert.Equal(t, + tt.fromAddress, + config.Contracts[consts.ContractNameOffRamp].Configs[consts.MethodCommit].FromAddress) + assert.Equal(t, + tt.commitGasLimit, + config.Contracts[consts.ContractNameOffRamp].Configs[consts.MethodCommit].GasLimit) + assert.Equal(t, + tt.execBatchGasLimit, + config.Contracts[consts.ContractNameOffRamp].Configs[consts.MethodExecute].GasLimit) + assert.Equal(t, + tt.maxGasPrice, + config.MaxGasPrice) + } + }) + } +} diff --git a/core/capabilities/ccip/configs/evm/contract_reader.go b/core/capabilities/ccip/configs/evm/contract_reader.go index 250b07bd1b1..e9b4314d68f 100644 --- a/core/capabilities/ccip/configs/evm/contract_reader.go +++ b/core/capabilities/ccip/configs/evm/contract_reader.go @@ -34,30 +34,6 @@ var ( // TODO: replace with generated ABI when the contract will be defined var rmnHomeString = "[{\"inputs\":[],\"name\":\"getAllConfigs\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"num\",\"type\":\"uint256\"}],\"name\":\"store\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" -// MustSourceReaderConfig returns a ChainReaderConfig that can be used to read from the onramp. -// The configuration is marshaled into JSON so that it can be passed to the relayer NewContractReader() method. -func MustSourceReaderConfig() []byte { - rawConfig := SourceReaderConfig - encoded, err := json.Marshal(rawConfig) - if err != nil { - panic(fmt.Errorf("failed to marshal ChainReaderConfig into JSON: %w", err)) - } - - return encoded -} - -// MustDestReaderConfig returns a ChainReaderConfig that can be used to read from the offramp. -// The configuration is marshaled into JSON so that it can be passed to the relayer NewContractReader() method. -func MustDestReaderConfig() []byte { - rawConfig := DestReaderConfig - encoded, err := json.Marshal(rawConfig) - if err != nil { - panic(fmt.Errorf("failed to marshal ChainReaderConfig into JSON: %w", err)) - } - - return encoded -} - func MergeReaderConfigs(configs ...evmrelaytypes.ChainReaderConfig) evmrelaytypes.ChainReaderConfig { allContracts := make(map[string]evmrelaytypes.ChainContractReader) for _, c := range configs { @@ -241,6 +217,8 @@ var SourceReaderConfig = evmrelaytypes.ChainReaderConfig{ }, } +// FeedReaderConfig provides a ChainReaderConfig that can be used to read from a price feed +// that is deployed on-chain. var FeedReaderConfig = evmrelaytypes.ChainReaderConfig{ Contracts: map[string]evmrelaytypes.ChainContractReader{ consts.ContractNamePriceAggregator: { @@ -307,6 +285,16 @@ var HomeChainReaderConfigRaw = evmrelaytypes.ChainReaderConfig{ }, } +var HomeChainReaderConfig = mustMarshal(HomeChainReaderConfigRaw) + +func mustMarshal(v interface{}) []byte { + b, err := json.Marshal(v) + if err != nil { + panic(err) + } + return b +} + func mustGetEventName(event string, tabi abi.ABI) string { e, ok := tabi.Events[event] if !ok { diff --git a/core/capabilities/ccip/delegate.go b/core/capabilities/ccip/delegate.go index 30007a7b2cf..d9fa4f4d91d 100644 --- a/core/capabilities/ccip/delegate.go +++ b/core/capabilities/ccip/delegate.go @@ -3,15 +3,18 @@ package ccip import ( "context" "fmt" + "math/big" "strconv" "time" "github.com/smartcontractkit/chainlink-common/pkg/loop" + "golang.org/x/exp/maps" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common" configsevm "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/launcher" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/oraclecreator" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" @@ -29,7 +32,6 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/config" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -40,7 +42,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" "github.com/smartcontractkit/chainlink/v2/plugins" ) @@ -54,13 +55,13 @@ type Delegate struct { lggr logger.Logger registrarConfig plugins.RegistrarConfig pipelineRunner pipeline.Runner - chains legacyevm.LegacyChainContainer relayers RelayGetter keystore keystore.Master ds sqlutil.DataSource peerWrapper *ocrcommon.SingletonPeerWrapper monitoringEndpointGen telemetry.MonitoringEndpointGenerator capabilityConfig config.Capabilities + evmConfigs toml.EVMConfigs isNewlyCreatedJob bool } @@ -69,25 +70,25 @@ func NewDelegate( lggr logger.Logger, registrarConfig plugins.RegistrarConfig, pipelineRunner pipeline.Runner, - chains legacyevm.LegacyChainContainer, relayers RelayGetter, keystore keystore.Master, ds sqlutil.DataSource, peerWrapper *ocrcommon.SingletonPeerWrapper, monitoringEndpointGen telemetry.MonitoringEndpointGenerator, capabilityConfig config.Capabilities, + evmConfigs toml.EVMConfigs, ) *Delegate { return &Delegate{ lggr: lggr, registrarConfig: registrarConfig, pipelineRunner: pipelineRunner, - chains: chains, relayers: relayers, ds: ds, keystore: keystore, peerWrapper: peerWrapper, monitoringEndpointGen: monitoringEndpointGen, capabilityConfig: capabilityConfig, + evmConfigs: evmConfigs, } } @@ -115,7 +116,7 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services cfg := d.capabilityConfig rid := cfg.ExternalRegistry().RelayID() - relayer, err := d.relayers.Get(rid) + homeChainRelayer, err := d.relayers.Get(rid) if err != nil { return nil, fmt.Errorf("could not fetch relayer %s configured for capabilities registry: %w", rid, err) } @@ -124,12 +125,12 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services func() (p2ptypes.PeerID, error) { return p2ptypes.PeerID(p2pID.PeerID()), nil }, - relayer, + homeChainRelayer, cfg.ExternalRegistry().Address(), registrysyncer.NewORM(d.ds, d.lggr), ) if err != nil { - return nil, fmt.Errorf("could not configure syncer: %w", err) + return nil, fmt.Errorf("could not create registry syncer: %w", err) } ocrKeys, err := d.getOCRKeys(spec.CCIPSpec.OCRKeyBundleIDs) @@ -137,7 +138,11 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services return nil, err } - transmitterKeys, err := d.getTransmitterKeys(ctx, d.chains) + allRelayers, err := d.relayers.GetIDToRelayerMap() + if err != nil { + return nil, fmt.Errorf("could not fetch all relayers: %w", err) + } + transmitterKeys, err := d.getTransmitterKeys(ctx, maps.Keys(allRelayers)) if err != nil { return nil, err } @@ -153,7 +158,7 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services homeChainContractReader, ccipConfigBinding, err := d.getHomeChainContractReader( ctx, - d.chains, + homeChainRelayer, spec.CCIPSpec.CapabilityLabelledName, spec.CCIPSpec.CapabilityVersion) if err != nil { @@ -186,7 +191,7 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services oracleCreator = oraclecreator.NewPluginOracleCreator( ocrKeys, transmitterKeys, - d.chains, + allRelayers, d.peerWrapper, spec.ExternalJobID, spec.ID, @@ -198,6 +203,7 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services bootstrapperLocators, hcr, cciptypes.ChainSelector(homeChainChainSelector), + d.evmConfigs, ) } else { oracleCreator = oraclecreator.NewBootstrapOracleCreator( @@ -223,6 +229,7 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services registrySyncer.AddLauncher(capLauncher) return []job.ServiceCtx{ + homeChainContractReader, registrySyncer, hcr, capLauncher, @@ -260,13 +267,17 @@ func (d *Delegate) getOCRKeys(ocrKeyBundleIDs job.JSONConfig) (map[string]ocr2ke return ocrKeys, nil } -func (d *Delegate) getTransmitterKeys(ctx context.Context, chains legacyevm.LegacyChainContainer) (map[types.RelayID][]string, error) { +func (d *Delegate) getTransmitterKeys(ctx context.Context, relayIDs []types.RelayID) (map[types.RelayID][]string, error) { transmitterKeys := make(map[types.RelayID][]string) - for _, chain := range chains.Slice() { - relayID := types.NewRelayID(relay.NetworkEVM, chain.ID().String()) - ethKeys, err2 := d.keystore.Eth().EnabledAddressesForChain(ctx, chain.ID()) - if err2 != nil { - return nil, fmt.Errorf("error getting enabled addresses for chain: %s %w", chain.ID().String(), err2) + for _, relayID := range relayIDs { + chainID, ok := new(big.Int).SetString(relayID.ChainID, 10) + if !ok { + return nil, fmt.Errorf("error parsing chain ID, expected big int: %s", relayID.ChainID) + } + + ethKeys, err := d.keystore.Eth().EnabledAddressesForChain(ctx, chainID) + if err != nil { + return nil, fmt.Errorf("error getting enabled addresses for chain: %s %w", chainID.String(), err) } transmitterKeys[relayID] = func() (r []string) { @@ -281,26 +292,11 @@ func (d *Delegate) getTransmitterKeys(ctx context.Context, chains legacyevm.Lega func (d *Delegate) getHomeChainContractReader( ctx context.Context, - chains legacyevm.LegacyChainContainer, + homeChainRelayer loop.Relayer, capabilityLabelledName, capabilityVersion string, ) (types.ContractReader, types.BoundContract, error) { - // home chain is where the capability registry is deployed, - // which should be set correctly in toml config. - homeChainRelayID := d.capabilityConfig.ExternalRegistry().RelayID() - homeChain, err := chains.Get(homeChainRelayID.ChainID) - if err != nil { - return nil, types.BoundContract{}, fmt.Errorf("home chain relayer not found, chain id: %s, err: %w", homeChainRelayID.String(), err) - } - - reader, err := evm.NewChainReaderService( - context.Background(), - d.lggr, - homeChain.LogPoller(), - homeChain.HeadTracker(), - homeChain.Client(), - configsevm.HomeChainReaderConfigRaw, - ) + reader, err := homeChainRelayer.NewContractReader(ctx, configsevm.HomeChainReaderConfig) if err != nil { return nil, types.BoundContract{}, fmt.Errorf("failed to create home chain contract reader: %w", err) } diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index 455bcde83e7..ec027610fcb 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -2,7 +2,9 @@ package oraclecreator import ( "context" + "encoding/json" "fmt" + "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -15,6 +17,8 @@ import ( evmconfig "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/configs/evm" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ocrimpls" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" chainsel "github.com/smartcontractkit/chain-selectors" @@ -23,6 +27,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/loop" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" commitocr3 "github.com/smartcontractkit/chainlink-ccip/commit" @@ -32,14 +37,11 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" evmrelaytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" @@ -49,6 +51,7 @@ var _ cctypes.OracleCreator = &pluginOracleCreator{} const ( defaultCommitGasLimit = 500_000 + defaultExecGasLimit = 6_500_000 ) // pluginOracleCreator creates oracles that reference plugins running @@ -56,7 +59,6 @@ const ( type pluginOracleCreator struct { ocrKeyBundles map[string]ocr2key.KeyBundle transmitters map[types.RelayID][]string - chains legacyevm.LegacyChainContainer peerWrapper *ocrcommon.SingletonPeerWrapper externalJobID uuid.UUID jobID int32 @@ -68,12 +70,14 @@ type pluginOracleCreator struct { bootstrapperLocators []commontypes.BootstrapperLocator homeChainReader ccipreaderpkg.HomeChain homeChainSelector cciptypes.ChainSelector + relayers map[types.RelayID]loop.Relayer + evmConfigs toml.EVMConfigs } func NewPluginOracleCreator( ocrKeyBundles map[string]ocr2key.KeyBundle, transmitters map[types.RelayID][]string, - chains legacyevm.LegacyChainContainer, + relayers map[types.RelayID]loop.Relayer, peerWrapper *ocrcommon.SingletonPeerWrapper, externalJobID uuid.UUID, jobID int32, @@ -85,11 +89,12 @@ func NewPluginOracleCreator( bootstrapperLocators []commontypes.BootstrapperLocator, homeChainReader ccipreaderpkg.HomeChain, homeChainSelector cciptypes.ChainSelector, + evmConfigs toml.EVMConfigs, ) cctypes.OracleCreator { return &pluginOracleCreator{ ocrKeyBundles: ocrKeyBundles, transmitters: transmitters, - chains: chains, + relayers: relayers, peerWrapper: peerWrapper, externalJobID: externalJobID, jobID: jobID, @@ -101,6 +106,7 @@ func NewPluginOracleCreator( bootstrapperLocators: bootstrapperLocators, homeChainReader: homeChainReader, homeChainSelector: homeChainSelector, + evmConfigs: evmConfigs, } } @@ -275,6 +281,10 @@ func (i *pluginOracleCreator) createReadersAndWriters( var execBatchGasLimit uint64 if !ofc.execEmpty() { execBatchGasLimit = ofc.exec().BatchGasLimit + } else { + // Set the default here so chain writer config validation doesn't fail. + // For commit, this won't be used, so its harmless. + execBatchGasLimit = defaultExecGasLimit } homeChainID, err := i.getChainID(i.homeChainSelector) @@ -284,33 +294,57 @@ func (i *pluginOracleCreator) createReadersAndWriters( contractReaders := make(map[cciptypes.ChainSelector]types.ContractReader) chainWriters := make(map[cciptypes.ChainSelector]types.ChainWriter) - for _, chain := range i.chains.Slice() { - chainSelector, err1 := i.getChainSelector(chain.ID().Uint64()) + for relayID, relayer := range i.relayers { + chainID, ok := new(big.Int).SetString(relayID.ChainID, 10) + if !ok { + return nil, nil, fmt.Errorf("error parsing chain ID, expected big int: %s", relayID.ChainID) + } + + chainSelector, err1 := i.getChainSelector(chainID.Uint64()) if err1 != nil { - return nil, nil, err1 + return nil, nil, fmt.Errorf("failed to get chain selector from chain ID %s: %w", chainID.String(), err1) + } + + chainReaderConfig, err1 := getChainReaderConfig(chainID.Uint64(), destChainID, homeChainID, ofc, chainSelector) + if err1 != nil { + return nil, nil, fmt.Errorf("failed to get chain reader config: %w", err1) } - chainReaderConfig := getChainReaderConfig(chain.ID().Uint64(), destChainID, homeChainID, ofc, chainSelector) - cr, err1 := createChainReader(i.lggr, chain, chainReaderConfig, pluginType) + // TODO: context. + cr, err1 := relayer.NewContractReader(context.Background(), chainReaderConfig) if err1 != nil { return nil, nil, err1 } - if err2 := bindContracts(chain, cr, config, destChainID); err2 != nil { - return nil, nil, err2 + if chainID.Uint64() == destChainID { + offrampAddressHex := common.BytesToAddress(config.Config.OfframpAddress).Hex() + err2 := cr.Bind(context.Background(), []types.BoundContract{ + { + Address: offrampAddressHex, + Name: consts.ContractNameOffRamp, + }, + }) + if err2 != nil { + return nil, nil, fmt.Errorf("failed to bind chain reader for dest chain %s's offramp at %s: %w", chainID.String(), offrampAddressHex, err) + } } - if err3 := cr.Start(context.Background()); err3 != nil { - return nil, nil, fmt.Errorf("failed to start contract reader for chain %s: %w", chain.ID(), err3) + if err2 := cr.Start(context.Background()); err2 != nil { + return nil, nil, fmt.Errorf("failed to start contract reader for chain %s: %w", chainID.String(), err2) } - cw, err1 := createChainWriter(i.lggr, chain, pluginType, i.transmitters, execBatchGasLimit) + cw, err1 := createChainWriter( + chainID, + i.evmConfigs, + relayer, + i.transmitters, + execBatchGasLimit) if err1 != nil { return nil, nil, err1 } if err4 := cw.Start(context.Background()); err4 != nil { - return nil, nil, fmt.Errorf("failed to start chain writer for chain %s: %w", chain.ID(), err4) + return nil, nil, fmt.Errorf("failed to start chain writer for chain %s: %w", chainID.String(), err4) } contractReaders[chainSelector] = cr @@ -371,7 +405,7 @@ func getChainReaderConfig( homeChainID uint64, ofc offChainConfig, chainSelector cciptypes.ChainSelector, -) evmrelaytypes.ChainReaderConfig { +) ([]byte, error) { var chainReaderConfig evmrelaytypes.ChainReaderConfig if chainID == destChainID { chainReaderConfig = evmconfig.DestReaderConfig @@ -390,7 +424,13 @@ func getChainReaderConfig( if chainID == homeChainID { chainReaderConfig = evmconfig.MergeReaderConfigs(chainReaderConfig, evmconfig.HomeChainReaderConfigRaw) } - return chainReaderConfig + + marshaledConfig, err := json.Marshal(chainReaderConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal chain reader config: %w", err) + } + + return marshaledConfig, nil } func isUSDCEnabled(chainID uint64, destChainID uint64, ofc offChainConfig) bool { @@ -405,83 +445,76 @@ func isUSDCEnabled(chainID uint64, destChainID uint64, ofc offChainConfig) bool return ofc.exec().IsUSDCEnabled() } -func createChainReader( - lggr logger.Logger, - chain legacyevm.Chain, - chainReaderConfig evmrelaytypes.ChainReaderConfig, - pluginType cctypes.PluginType, -) (types.ContractReader, error) { - cr, err := evm.NewChainReaderService( - context.Background(), - lggr. - Named("EVMChainReaderService"). - Named(chain.ID().String()). - Named(pluginType.String()), - chain.LogPoller(), - chain.HeadTracker(), - chain.Client(), - chainReaderConfig, - ) - if err != nil { - return nil, fmt.Errorf("failed to create contract reader for chain %s: %w", chain.ID(), err) - } - return cr, nil -} - -func bindContracts( - chain legacyevm.Chain, - cr types.ContractReader, - config cctypes.OCR3ConfigWithMeta, - destChainID uint64, -) error { - if chain.ID().Uint64() == destChainID { - offrampAddressHex := common.BytesToAddress(config.Config.OfframpAddress).Hex() - err := cr.Bind(context.Background(), []types.BoundContract{ - { - Address: offrampAddressHex, - Name: consts.ContractNameOffRamp, - }, - }) - if err != nil { - return fmt.Errorf("failed to bind chain reader for dest chain %s's offramp at %s: %w", chain.ID(), offrampAddressHex, err) - } - } - return nil -} - func createChainWriter( - lggr logger.Logger, - chain legacyevm.Chain, - pluginType cctypes.PluginType, + chainID *big.Int, + evmConfigs toml.EVMConfigs, + relayer loop.Relayer, transmitters map[types.RelayID][]string, execBatchGasLimit uint64, ) (types.ChainWriter, error) { var fromAddress common.Address - transmitter, ok := transmitters[types.NewRelayID(relay.NetworkEVM, chain.ID().String())] + transmitter, ok := transmitters[types.NewRelayID(relay.NetworkEVM, chainID.String())] if ok { // TODO: remove EVM-specific stuff fromAddress = common.HexToAddress(transmitter[0]) } - cw, err := evm.NewChainWriterService( - lggr.Named("EVMChainWriterService"). - Named(chain.ID().String()). - Named(pluginType.String()), - chain.Client(), - chain.TxManager(), - chain.GasEstimator(), - evmconfig.ChainWriterConfigRaw( - fromAddress, - chain.Config().EVM().GasEstimator().PriceMaxKey(fromAddress), - defaultCommitGasLimit, - execBatchGasLimit, - ), + + maxGasPrice := getKeySpecificMaxGasPrice(evmConfigs, chainID, fromAddress) + if maxGasPrice == nil { + return nil, fmt.Errorf("failed to find max gas price for chain %s", chainID.String()) + } + + chainWriterRawConfig, err := evmconfig.ChainWriterConfigRaw( + fromAddress, + maxGasPrice, + defaultCommitGasLimit, + execBatchGasLimit, ) if err != nil { - return nil, fmt.Errorf("failed to create chain writer for chain %s: %w", chain.ID(), err) + return nil, fmt.Errorf("failed to create chain writer config: %w", err) + } + + chainWriterConfig, err := json.Marshal(chainWriterRawConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal chain writer config: %w", err) + } + + // TODO: context. + cw, err := relayer.NewChainWriter(context.Background(), chainWriterConfig) + if err != nil { + return nil, fmt.Errorf("failed to create chain writer for chain %s: %w", chainID.String(), err) } + return cw, nil } +func getKeySpecificMaxGasPrice(evmConfigs toml.EVMConfigs, chainID *big.Int, fromAddress common.Address) *assets.Wei { + var maxGasPrice *assets.Wei + + // If a chain is enabled it should have some configuration in the TOML config + // of the chainlink node. + for _, config := range evmConfigs { + if config.ChainID.ToInt().Cmp(chainID) != 0 { + continue + } + + // find the key-specific max gas price for the given fromAddress. + for _, keySpecific := range config.KeySpecific { + if keySpecific.Key.Address() == fromAddress { + maxGasPrice = keySpecific.GasEstimator.PriceMax + } + } + + // if we didn't find a key-specific max gas price, use the one specified + // in the gas estimator config, which should have a default value. + if maxGasPrice == nil { + maxGasPrice = config.GasEstimator.PriceMax + } + } + + return maxGasPrice +} + type offChainConfig struct { commitOffchainConfig *pluginconfig.CommitOffchainConfig execOffchainConfig *pluginconfig.ExecuteOffchainConfig diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 873f5080c6a..c9e72244cf1 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -554,13 +554,13 @@ func NewApplication(opts ApplicationOpts) (Application, error) { globalLogger, loopRegistrarConfig, pipelineRunner, - opts.RelayerChainInteroperators.LegacyEVMChains(), relayerChainInterops, opts.KeyStore, opts.DS, peerWrapper, telemetryManager, cfg.Capabilities(), + cfg.EVMConfigs(), ) } else { globalLogger.Debug("Off-chain reporting v2 disabled") From 5d96be59a27f68f2f491a7d9f8cb0b2af4e0e835 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Thu, 3 Oct 2024 10:53:09 -0400 Subject: [PATCH 19/28] WS URL can be optional when LogBroadcaster is disabled (#14364) * WS URL can be optional * add changeset * change * make WSURL optional * fix test, and enforce SubscribeFilterLogs to fail when ws url not provided * add comments * update changeset * update dial logic and make ws optional not required * fix test * fix * fix lint * address comments * update comments * fix test * add check when both ws and http missing * add test and add restrictions * add comment * revert outdated change * remove extra line * fix test * revert changes from rpc client * unintended change * remove unused * update verification logic * add test fix * modify unit test to cover logbroadcaster enabled false * update doc * udpate changeset * address PR comments * address pr comments * update invalid toml config * fix test * ws required for primary nodes when logbroadcaster enabled * minor * Dmytro's comments * fix nil ptr, more fix to come * fix make * refactor function sig * fix test * fix * make ws pointer * fix * fix make * address comments * fix lint * fix make * fix make * fix make * fix rpc disconnect with optional ws url --------- Co-authored-by: Dmytro Haidashenko --- .changeset/silly-lies-boil.md | 8 ++ common/client/node.go | 17 +++-- common/client/node_test.go | 4 +- core/chains/evm/client/config_builder.go | 18 +++-- core/chains/evm/client/config_builder_test.go | 4 +- core/chains/evm/client/evm_client.go | 11 ++- core/chains/evm/client/helpers_test.go | 10 +-- core/chains/evm/client/rpc_client.go | 74 ++++++++++++------- core/chains/evm/client/rpc_client_test.go | 50 ++++++++----- core/chains/evm/config/toml/config.go | 36 ++++----- core/config/docs/chains-evm.toml | 2 +- core/services/chainlink/config_test.go | 18 ++--- docs/CONFIG.md | 2 +- 13 files changed, 157 insertions(+), 97 deletions(-) create mode 100644 .changeset/silly-lies-boil.md diff --git a/.changeset/silly-lies-boil.md b/.changeset/silly-lies-boil.md new file mode 100644 index 00000000000..b2a5084a36c --- /dev/null +++ b/.changeset/silly-lies-boil.md @@ -0,0 +1,8 @@ +--- +"chainlink": minor +--- + +Make websocket URL `WSURL` for `EVM.Nodes` optional, and apply logic so that: +* If WS URL was not provided, SubscribeFilterLogs should fail with an explicit error +* If WS URL was not provided LogBroadcaster should be disabled +#nops diff --git a/common/client/node.go b/common/client/node.go index 1f55e69cacc..7885fe76760 100644 --- a/common/client/node.go +++ b/common/client/node.go @@ -91,14 +91,14 @@ type node[ services.StateMachine lfcLog logger.Logger name string - id int32 + id int chainID CHAIN_ID nodePoolCfg NodeConfig chainCfg ChainConfig order int32 chainFamily string - ws url.URL + ws *url.URL http *url.URL rpc RPC @@ -121,10 +121,10 @@ func NewNode[ nodeCfg NodeConfig, chainCfg ChainConfig, lggr logger.Logger, - wsuri url.URL, + wsuri *url.URL, httpuri *url.URL, name string, - id int32, + id int, chainID CHAIN_ID, nodeOrder int32, rpc RPC, @@ -136,8 +136,10 @@ func NewNode[ n.chainID = chainID n.nodePoolCfg = nodeCfg n.chainCfg = chainCfg - n.ws = wsuri n.order = nodeOrder + if wsuri != nil { + n.ws = wsuri + } if httpuri != nil { n.http = httpuri } @@ -157,7 +159,10 @@ func NewNode[ } func (n *node[CHAIN_ID, HEAD, RPC]) String() string { - s := fmt.Sprintf("(%s)%s:%s", Primary.String(), n.name, n.ws.String()) + s := fmt.Sprintf("(%s)%s", Primary.String(), n.name) + if n.ws != nil { + s = s + fmt.Sprintf(":%s", n.ws.String()) + } if n.http != nil { s = s + fmt.Sprintf(":%s", n.http.String()) } diff --git a/common/client/node_test.go b/common/client/node_test.go index 66bb50fc94f..539964691c0 100644 --- a/common/client/node_test.go +++ b/common/client/node_test.go @@ -67,10 +67,10 @@ type testNodeOpts struct { config testNodeConfig chainConfig clientMocks.ChainConfig lggr logger.Logger - wsuri url.URL + wsuri *url.URL httpuri *url.URL name string - id int32 + id int chainID types.ID nodeOrder int32 rpc *mockNodeClient[types.ID, Head] diff --git a/core/chains/evm/client/config_builder.go b/core/chains/evm/client/config_builder.go index 9a31f9e4b40..66bdfc2614f 100644 --- a/core/chains/evm/client/config_builder.go +++ b/core/chains/evm/client/config_builder.go @@ -80,15 +80,21 @@ func NewClientConfigs( func parseNodeConfigs(nodeCfgs []NodeConfig) ([]*toml.Node, error) { nodes := make([]*toml.Node, len(nodeCfgs)) for i, nodeCfg := range nodeCfgs { - if nodeCfg.WSURL == nil || nodeCfg.HTTPURL == nil { - return nil, fmt.Errorf("node config [%d]: missing WS or HTTP URL", i) + var wsURL, httpURL *commonconfig.URL + // wsUrl requirement will be checked in EVMConfig validation + if nodeCfg.WSURL != nil { + wsURL = commonconfig.MustParseURL(*nodeCfg.WSURL) } - wsUrl := commonconfig.MustParseURL(*nodeCfg.WSURL) - httpUrl := commonconfig.MustParseURL(*nodeCfg.HTTPURL) + + if nodeCfg.HTTPURL == nil { + return nil, fmt.Errorf("node config [%d]: missing HTTP URL", i) + } + + httpURL = commonconfig.MustParseURL(*nodeCfg.HTTPURL) node := &toml.Node{ Name: nodeCfg.Name, - WSURL: wsUrl, - HTTPURL: httpUrl, + WSURL: wsURL, + HTTPURL: httpURL, SendOnly: nodeCfg.SendOnly, Order: nodeCfg.Order, } diff --git a/core/chains/evm/client/config_builder_test.go b/core/chains/evm/client/config_builder_test.go index 28620ac6ca9..22956fb0185 100644 --- a/core/chains/evm/client/config_builder_test.go +++ b/core/chains/evm/client/config_builder_test.go @@ -93,7 +93,7 @@ func TestNodeConfigs(t *testing.T) { require.Len(t, tomlNodes, len(nodeConfigs)) }) - t.Run("parsing missing ws url fails", func(t *testing.T) { + t.Run("ws can be optional", func(t *testing.T) { nodeConfigs := []client.NodeConfig{ { Name: ptr("foo1"), @@ -101,7 +101,7 @@ func TestNodeConfigs(t *testing.T) { }, } _, err := client.ParseTestNodeConfigs(nodeConfigs) - require.Error(t, err) + require.Nil(t, err) }) t.Run("parsing missing http url fails", func(t *testing.T) { diff --git a/core/chains/evm/client/evm_client.go b/core/chains/evm/client/evm_client.go index c26362d6351..c596bbc3a95 100644 --- a/core/chains/evm/client/evm_client.go +++ b/core/chains/evm/client/evm_client.go @@ -15,22 +15,25 @@ import ( ) func NewEvmClient(cfg evmconfig.NodePool, chainCfg commonclient.ChainConfig, clientErrors evmconfig.ClientErrors, lggr logger.Logger, chainID *big.Int, nodes []*toml.Node, chainType chaintype.ChainType) Client { - var empty url.URL var primaries []commonclient.Node[*big.Int, *evmtypes.Head, RPCClient] var sendonlys []commonclient.SendOnlyNode[*big.Int, RPCClient] largePayloadRPCTimeout, defaultRPCTimeout := getRPCTimeouts(chainType) for i, node := range nodes { + var ws *url.URL + if node.WSURL != nil { + ws = (*url.URL)(node.WSURL) + } if node.SendOnly != nil && *node.SendOnly { - rpc := NewRPCClient(lggr, empty, (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, + rpc := NewRPCClient(lggr, nil, (*url.URL)(node.HTTPURL), *node.Name, i, chainID, commonclient.Secondary, cfg.FinalizedBlockPollInterval(), cfg.NewHeadsPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType) sendonly := commonclient.NewSendOnlyNode(lggr, (url.URL)(*node.HTTPURL), *node.Name, chainID, rpc) sendonlys = append(sendonlys, sendonly) } else { - rpc := NewRPCClient(lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), + rpc := NewRPCClient(lggr, ws, (*url.URL)(node.HTTPURL), *node.Name, i, chainID, commonclient.Primary, cfg.FinalizedBlockPollInterval(), cfg.NewHeadsPollInterval(), largePayloadRPCTimeout, defaultRPCTimeout, chainType) primaryNode := commonclient.NewNode(cfg, chainCfg, - lggr, (url.URL)(*node.WSURL), (*url.URL)(node.HTTPURL), *node.Name, int32(i), chainID, *node.Order, + lggr, ws, (*url.URL)(node.HTTPURL), *node.Name, i, chainID, *node.Order, rpc, "EVM") primaries = append(primaries, primaryNode) } diff --git a/core/chains/evm/client/helpers_test.go b/core/chains/evm/client/helpers_test.go index 031f6481574..1a6090e4a01 100644 --- a/core/chains/evm/client/helpers_test.go +++ b/core/chains/evm/client/helpers_test.go @@ -135,7 +135,7 @@ func NewChainClientWithTestNode( rpcUrl string, rpcHTTPURL *url.URL, sendonlyRPCURLs []url.URL, - id int32, + id int, chainID *big.Int, ) (Client, error) { parsed, err := url.ParseRequestURI(rpcUrl) @@ -148,10 +148,10 @@ func NewChainClientWithTestNode( } lggr := logger.Test(t) - rpc := NewRPCClient(lggr, *parsed, rpcHTTPURL, "eth-primary-rpc-0", id, chainID, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := NewRPCClient(lggr, parsed, rpcHTTPURL, "eth-primary-rpc-0", id, chainID, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") n := commonclient.NewNode[*big.Int, *evmtypes.Head, RPCClient]( - nodeCfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, *parsed, rpcHTTPURL, "eth-primary-node-0", id, chainID, 1, rpc, "EVM") + nodeCfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, parsed, rpcHTTPURL, "eth-primary-node-0", id, chainID, 1, rpc, "EVM") primaries := []commonclient.Node[*big.Int, *evmtypes.Head, RPCClient]{n} var sendonlys []commonclient.SendOnlyNode[*big.Int, RPCClient] @@ -160,7 +160,7 @@ func NewChainClientWithTestNode( return nil, pkgerrors.Errorf("sendonly ethereum rpc url scheme must be http(s): %s", u.String()) } var empty url.URL - rpc := NewRPCClient(lggr, empty, &sendonlyRPCURLs[i], fmt.Sprintf("eth-sendonly-rpc-%d", i), id, chainID, commonclient.Secondary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := NewRPCClient(lggr, &empty, &sendonlyRPCURLs[i], fmt.Sprintf("eth-sendonly-rpc-%d", i), id, chainID, commonclient.Secondary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") s := commonclient.NewSendOnlyNode[*big.Int, RPCClient]( lggr, u, fmt.Sprintf("eth-sendonly-%d", i), chainID, rpc) sendonlys = append(sendonlys, s) @@ -206,7 +206,7 @@ func NewChainClientWithMockedRpc( parsed, _ := url.ParseRequestURI("ws://test") n := commonclient.NewNode[*big.Int, *evmtypes.Head, RPCClient]( - cfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, *parsed, nil, "eth-primary-node-0", 1, chainID, 1, rpc, "EVM") + cfg, clientMocks.ChainConfig{NoNewHeadsThresholdVal: noNewHeadsThreshold}, lggr, parsed, nil, "eth-primary-node-0", 1, chainID, 1, rpc, "EVM") primaries := []commonclient.Node[*big.Int, *evmtypes.Head, RPCClient]{n} clientErrors := NewTestClientErrors() c := NewChainClient(lggr, selectionMode, leaseDuration, noNewHeadsThreshold, primaries, nil, chainID, chainType, &clientErrors, 0) diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index a29ed5e118c..f55c35980df 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -117,7 +117,7 @@ type rawclient struct { type rpcClient struct { rpcLog logger.SugaredLogger name string - id int32 + id int chainID *big.Int tier commonclient.NodeTier largePayloadRpcTimeout time.Duration @@ -126,7 +126,7 @@ type rpcClient struct { newHeadsPollInterval time.Duration chainType chaintype.ChainType - ws rawclient + ws *rawclient http *rawclient stateMu sync.RWMutex // protects state* fields @@ -154,10 +154,10 @@ type rpcClient struct { // NewRPCCLient returns a new *rpcClient as commonclient.RPC func NewRPCClient( lggr logger.Logger, - wsuri url.URL, + wsuri *url.URL, httpuri *url.URL, name string, - id int32, + id int, chainID *big.Int, tier commonclient.NodeTier, finalizedBlockPollInterval time.Duration, @@ -175,9 +175,11 @@ func NewRPCClient( r.id = id r.chainID = chainID r.tier = tier - r.ws.uri = wsuri r.finalizedBlockPollInterval = finalizedBlockPollInterval r.newHeadsPollInterval = newHeadsPollInterval + if wsuri != nil { + r.ws = &rawclient{uri: *wsuri} + } if httpuri != nil { r.http = &rawclient{uri: *httpuri} } @@ -199,30 +201,33 @@ func (r *rpcClient) Dial(callerCtx context.Context) error { ctx, cancel := r.makeQueryCtx(callerCtx, r.rpcTimeout) defer cancel() - promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc() - lggr := r.rpcLog.With("wsuri", r.ws.uri.Redacted()) - if r.http != nil { - lggr = lggr.With("httpuri", r.http.uri.Redacted()) + if r.ws == nil && r.http == nil { + return errors.New("cannot dial rpc client when both ws and http info are missing") } - lggr.Debugw("RPC dial: evmclient.Client#dial") - wsrpc, err := rpc.DialWebsocket(ctx, r.ws.uri.String(), "") - if err != nil { - promEVMPoolRPCNodeDialsFailed.WithLabelValues(r.chainID.String(), r.name).Inc() - return r.wrapRPCClientError(pkgerrors.Wrapf(err, "error while dialing websocket: %v", r.ws.uri.Redacted())) - } + promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc() + lggr := r.rpcLog + if r.ws != nil { + lggr = lggr.With("wsuri", r.ws.uri.Redacted()) + wsrpc, err := rpc.DialWebsocket(ctx, r.ws.uri.String(), "") + if err != nil { + promEVMPoolRPCNodeDialsFailed.WithLabelValues(r.chainID.String(), r.name).Inc() + return r.wrapRPCClientError(pkgerrors.Wrapf(err, "error while dialing websocket: %v", r.ws.uri.Redacted())) + } - r.ws.rpc = wsrpc - r.ws.geth = ethclient.NewClient(wsrpc) + r.ws.rpc = wsrpc + r.ws.geth = ethclient.NewClient(wsrpc) + } if r.http != nil { + lggr = lggr.With("httpuri", r.http.uri.Redacted()) if err := r.DialHTTP(); err != nil { return err } } + lggr.Debugw("RPC dial: evmclient.Client#dial") promEVMPoolRPCNodeDialsSuccess.WithLabelValues(r.chainID.String(), r.name).Inc() - return nil } @@ -231,7 +236,7 @@ func (r *rpcClient) Dial(callerCtx context.Context) error { // It can only return error if the URL is malformed. func (r *rpcClient) DialHTTP() error { promEVMPoolRPCNodeDials.WithLabelValues(r.chainID.String(), r.name).Inc() - lggr := r.rpcLog.With("httpuri", r.ws.uri.Redacted()) + lggr := r.rpcLog.With("httpuri", r.http.uri.Redacted()) lggr.Debugw("RPC dial: evmclient.Client#dial") var httprpc *rpc.Client @@ -251,7 +256,7 @@ func (r *rpcClient) DialHTTP() error { func (r *rpcClient) Close() { defer func() { - if r.ws.rpc != nil { + if r.ws != nil && r.ws.rpc != nil { r.ws.rpc.Close() } }() @@ -270,7 +275,10 @@ func (r *rpcClient) cancelInflightRequests() { } func (r *rpcClient) String() string { - s := fmt.Sprintf("(%s)%s:%s", r.tier.String(), r.name, r.ws.uri.Redacted()) + s := fmt.Sprintf("(%s)%s", r.tier.String(), r.name) + if r.ws != nil { + s = s + fmt.Sprintf(":%s", r.ws.uri.Redacted()) + } if r.http != nil { s = s + fmt.Sprintf(":%s", r.http.uri.Redacted()) } @@ -336,7 +344,7 @@ func (r *rpcClient) registerSub(sub ethereum.Subscription, stopInFLightCh chan s // DisconnectAll disconnects all clients connected to the rpcClient func (r *rpcClient) DisconnectAll() { r.stateMu.Lock() - if r.ws.rpc != nil { + if r.ws != nil && r.ws.rpc != nil { r.ws.rpc.Close() } r.cancelInflightRequests() @@ -497,7 +505,6 @@ func (r *rpcClient) SubscribeNewHead(ctx context.Context, channel chan<- *evmtyp defer cancel() args := []interface{}{"newHeads"} lggr := r.newRqLggr().With("args", args) - if r.newHeadsPollInterval > 0 { interval := r.newHeadsPollInterval timeout := interval @@ -529,6 +536,10 @@ func (r *rpcClient) SubscribeNewHead(ctx context.Context, channel chan<- *evmtyp return &poller, nil } + if ws == nil { + return nil, errors.New("SubscribeNewHead is not allowed without ws url") + } + lggr.Debug("RPC call: evmclient.Client#EthSubscribe") start := time.Now() defer func() { @@ -557,7 +568,6 @@ func (r *rpcClient) SubscribeNewHead(ctx context.Context, channel chan<- *evmtyp func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.Head, sub commontypes.Subscription, err error) { ctx, cancel, chStopInFlight, ws, _ := r.acquireQueryCtx(ctx, r.rpcTimeout) defer cancel() - args := []interface{}{rpcSubscriptionMethodNewHeads} start := time.Now() lggr := r.newRqLggr().With("args", args) @@ -580,6 +590,10 @@ func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H return channel, &poller, nil } + if ws == nil { + return nil, nil, errors.New("SubscribeNewHead is not allowed without ws url") + } + lggr.Debug("RPC call: evmclient.Client#EthSubscribe") defer func() { duration := time.Since(start) @@ -1286,6 +1300,9 @@ 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) (_ ethereum.Subscription, err error) { ctx, cancel, chStopInFlight, ws, _ := r.acquireQueryCtx(ctx, r.rpcTimeout) defer cancel() + if ws == nil { + return nil, errors.New("SubscribeFilterLogs is not allowed without ws url") + } lggr := r.newRqLggr().With("q", q) lggr.Debug("RPC call: evmclient.Client#SubscribeFilterLogs") @@ -1390,18 +1407,21 @@ func (r *rpcClient) wrapHTTP(err error) error { } // makeLiveQueryCtxAndSafeGetClients wraps makeQueryCtx -func (r *rpcClient) makeLiveQueryCtxAndSafeGetClients(parentCtx context.Context, timeout time.Duration) (ctx context.Context, cancel context.CancelFunc, ws rawclient, http *rawclient) { +func (r *rpcClient) makeLiveQueryCtxAndSafeGetClients(parentCtx context.Context, timeout time.Duration) (ctx context.Context, cancel context.CancelFunc, ws *rawclient, http *rawclient) { ctx, cancel, _, ws, http = r.acquireQueryCtx(parentCtx, timeout) return } func (r *rpcClient) acquireQueryCtx(parentCtx context.Context, timeout time.Duration) (ctx context.Context, cancel context.CancelFunc, - chStopInFlight chan struct{}, ws rawclient, http *rawclient) { + chStopInFlight chan struct{}, ws *rawclient, http *rawclient) { // Need to wrap in mutex because state transition can cancel and replace the // context r.stateMu.RLock() chStopInFlight = r.chStopInFlight - ws = r.ws + if r.ws != nil { + cp := *r.ws + ws = &cp + } if r.http != nil { cp := *r.http http = &cp diff --git a/core/chains/evm/client/rpc_client_test.go b/core/chains/evm/client/rpc_client_test.go index d959f8d1115..662c757ffb3 100644 --- a/core/chains/evm/client/rpc_client_test.go +++ b/core/chains/evm/client/rpc_client_test.go @@ -78,11 +78,18 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { require.Equal(t, int32(0), rpcClient.SubscribersCount()) } + t.Run("WS and HTTP URL cannot be both empty", func(t *testing.T) { + // ws is optional when LogBroadcaster is disabled, however SubscribeFilterLogs will return error if ws is missing + observedLggr, _ := logger.TestObserved(t, zap.DebugLevel) + rpcClient := client.NewRPCClient(observedLggr, nil, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + require.Equal(t, errors.New("cannot dial rpc client when both ws and http info are missing"), rpcClient.Dial(ctx)) + }) + t.Run("Updates chain info on new blocks", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) // set to default values @@ -132,7 +139,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) ch := make(chan *evmtypes.Head) @@ -177,7 +184,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { } server := createRPCServer() - rpc := client.NewRPCClient(lggr, *server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, tests.TestInterval, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, tests.TestInterval, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) latest, highestUserObservations := rpc.GetInterceptedChainInfo() @@ -201,7 +208,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) var wg sync.WaitGroup @@ -225,7 +232,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { t.Run("Block's chain ID matched configured", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) ch := make(chan *evmtypes.Head) @@ -242,7 +249,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { }) wsURL := server.WSURL() observedLggr, observed := logger.TestObserved(t, zap.DebugLevel) - rpc := client.NewRPCClient(observedLggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(observedLggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") require.NoError(t, rpc.Dial(ctx)) server.Close() _, err := rpc.SubscribeNewHead(ctx, make(chan *evmtypes.Head)) @@ -253,7 +260,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) @@ -266,7 +273,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 1, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 1, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) @@ -279,7 +286,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) @@ -291,7 +298,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 1, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 1, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) @@ -303,7 +310,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 1, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 1, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) @@ -314,7 +321,7 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { t.Run("Subscription error is properly wrapper", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) sub, err := rpc.SubscribeNewHead(ctx, make(chan *evmtypes.Head)) @@ -336,13 +343,22 @@ func TestRPCClient_SubscribeFilterLogs(t *testing.T) { lggr := logger.Test(t) ctx, cancel := context.WithTimeout(tests.Context(t), tests.WaitTimeout(t)) defer cancel() + t.Run("Failed SubscribeFilterLogs when WSURL is empty", func(t *testing.T) { + // ws is optional when LogBroadcaster is disabled, however SubscribeFilterLogs will return error if ws is missing + observedLggr, _ := logger.TestObserved(t, zap.DebugLevel) + rpcClient := client.NewRPCClient(observedLggr, nil, &url.URL{}, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + require.Nil(t, rpcClient.Dial(ctx)) + + _, err := rpcClient.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log)) + require.Equal(t, errors.New("SubscribeFilterLogs is not allowed without ws url"), err) + }) t.Run("Failed SubscribeFilterLogs logs and returns proper error", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, func(reqMethod string, reqParams gjson.Result) (resp testutils.JSONRPCResponse) { return resp }) wsURL := server.WSURL() observedLggr, observed := logger.TestObserved(t, zap.DebugLevel) - rpc := client.NewRPCClient(observedLggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(observedLggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") require.NoError(t, rpc.Dial(ctx)) server.Close() _, err := rpc.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log)) @@ -359,7 +375,7 @@ func TestRPCClient_SubscribeFilterLogs(t *testing.T) { return resp }) wsURL := server.WSURL() - rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") defer rpc.Close() require.NoError(t, rpc.Dial(ctx)) sub, err := rpc.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, make(chan types.Log)) @@ -408,7 +424,7 @@ func TestRPCClient_LatestFinalizedBlock(t *testing.T) { } server := createRPCServer() - rpc := client.NewRPCClient(lggr, *server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + rpc := client.NewRPCClient(lggr, server.URL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") require.NoError(t, rpc.Dial(ctx)) defer rpc.Close() server.Head = &evmtypes.Head{Number: 128} @@ -518,7 +534,7 @@ func TestRpcClientLargePayloadTimeout(t *testing.T) { // use something unreasonably large for RPC timeout to ensure that we use largePayloadRPCTimeout const rpcTimeout = time.Hour const largePayloadRPCTimeout = tests.TestInterval - rpc := client.NewRPCClient(logger.Test(t), *rpcURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, largePayloadRPCTimeout, rpcTimeout, "") + rpc := client.NewRPCClient(logger.Test(t), rpcURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, largePayloadRPCTimeout, rpcTimeout, "") require.NoError(t, rpc.Dial(ctx)) defer rpc.Close() err := testCase.Fn(ctx, rpc) @@ -558,7 +574,7 @@ func TestAstarCustomFinality(t *testing.T) { const expectedFinalizedBlockNumber = int64(4) const expectedFinalizedBlockHash = "0x7441e97acf83f555e0deefef86db636bc8a37eb84747603412884e4df4d22804" - rpcClient := client.NewRPCClient(logger.Test(t), *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainAstar) + rpcClient := client.NewRPCClient(logger.Test(t), wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainAstar) defer rpcClient.Close() err := rpcClient.Dial(tests.Context(t)) require.NoError(t, err) diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index b5008a73607..b6b6a732363 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -23,7 +23,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" ) -var ErrNotFound = errors.New("not found") +var ( + ErrNotFound = errors.New("not found") +) type HasEVMConfigs interface { EVMConfigs() EVMConfigs @@ -311,16 +313,27 @@ func (c *EVMConfig) ValidateConfig() (err error) { err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: "must have at least one node"}) } else { var hasPrimary bool - for _, n := range c.Nodes { + var logBroadcasterEnabled bool + if c.LogBroadcasterEnabled != nil { + logBroadcasterEnabled = *c.LogBroadcasterEnabled + } + + for i, n := range c.Nodes { if n.SendOnly != nil && *n.SendOnly { continue } + hasPrimary = true - break + + // if the node is a primary node, then the WS URL is required when LogBroadcaster is enabled + if logBroadcasterEnabled && (n.WSURL == nil || n.WSURL.IsZero()) { + err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", Msg: fmt.Sprintf("%vth node (primary) must have a valid WSURL when LogBroadcaster is enabled", i)}) + } } + if !hasPrimary { err = multierr.Append(err, commonconfig.ErrMissing{Name: "Nodes", - Msg: "must have at least one primary node with WSURL"}) + Msg: "must have at least one primary node"}) } } @@ -976,19 +989,8 @@ func (n *Node) ValidateConfig() (err error) { err = multierr.Append(err, commonconfig.ErrEmpty{Name: "Name", Msg: "required for all nodes"}) } - var sendOnly bool - if n.SendOnly != nil { - sendOnly = *n.SendOnly - } - if n.WSURL == nil { - if !sendOnly { - err = multierr.Append(err, commonconfig.ErrMissing{Name: "WSURL", Msg: "required for primary nodes"}) - } - } else if n.WSURL.IsZero() { - if !sendOnly { - err = multierr.Append(err, commonconfig.ErrEmpty{Name: "WSURL", Msg: "required for primary nodes"}) - } - } else { + // relax the check here as WSURL can potentially be empty if LogBroadcaster is disabled (checked in EVMConfig Validation) + if n.WSURL != nil && !n.WSURL.IsZero() { switch n.WSURL.Scheme { case "ws", "wss": default: diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index 660b3e4a7dd..c237de5826d 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -473,7 +473,7 @@ ObservationGracePeriod = '1s' # Default [[EVM.Nodes]] # Name is a unique (per-chain) identifier for this node. Name = 'foo' # Example -# WSURL is the WS(S) endpoint for this node. Required for primary nodes. +# WSURL is the WS(S) endpoint for this node. Required for primary nodes when `LogBroadcasterEnabled` is `true` WSURL = 'wss://web.socket/test' # Example # HTTPURL is the HTTP(S) endpoint for this node. Required for all nodes. HTTPURL = 'https://foo.web' # Example diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 375884d8fea..64cc58588a8 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1348,7 +1348,8 @@ func TestConfig_Validate(t *testing.T) { - 1.ChainID: invalid value (1): duplicate - must be unique - 0.Nodes.1.Name: invalid value (foo): duplicate - must be unique - 3.Nodes.4.WSURL: invalid value (ws://dupe.com): duplicate - must be unique - - 0: 3 errors: + - 0: 4 errors: + - Nodes: missing: 0th node (primary) must have a valid WSURL when LogBroadcaster is enabled - GasEstimator.BumpTxDepth: invalid value (11): must be less than or equal to Transactions.MaxInFlight - GasEstimator: 6 errors: - BumpPercent: invalid value (1): may not be less than Geth's default of 10 @@ -1358,9 +1359,7 @@ func TestConfig_Validate(t *testing.T) { - PriceMax: invalid value (10 gwei): must be greater than or equal to PriceDefault - BlockHistory.BlockHistorySize: invalid value (0): must be greater than or equal to 1 with BlockHistory Mode - Nodes: 2 errors: - - 0: 2 errors: - - WSURL: missing: required for primary nodes - - HTTPURL: missing: required for all nodes + - 0.HTTPURL: missing: required for all nodes - 1.HTTPURL: missing: required for all nodes - 1: 10 errors: - ChainType: invalid value (Foo): must not be set with this chain id @@ -1381,18 +1380,19 @@ func TestConfig_Validate(t *testing.T) { - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - FinalityDepth: invalid value (0): must be greater than or equal to 1 - MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1 - - 3.Nodes: 5 errors: - - 0: 3 errors: + - 3: 3 errors: + - Nodes: missing: 0th node (primary) must have a valid WSURL when LogBroadcaster is enabled + - Nodes: missing: 2th node (primary) must have a valid WSURL when LogBroadcaster is enabled + - Nodes: 5 errors: + - 0: 2 errors: - Name: missing: required for all nodes - - WSURL: missing: required for primary nodes - HTTPURL: empty: required for all nodes - 1: 3 errors: - Name: missing: required for all nodes - WSURL: invalid value (http): must be ws or wss - HTTPURL: missing: required for all nodes - - 2: 3 errors: + - 2: 2 errors: - Name: empty: required for all nodes - - WSURL: missing: required for primary nodes - HTTPURL: invalid value (ws): must be http or https - 3.HTTPURL: missing: required for all nodes - 4.HTTPURL: missing: required for all nodes diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 79fe03d5e37..498e2ae44f6 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -9759,7 +9759,7 @@ Name is a unique (per-chain) identifier for this node. ```toml WSURL = 'wss://web.socket/test' # Example ``` -WSURL is the WS(S) endpoint for this node. Required for primary nodes. +WSURL is the WS(S) endpoint for this node. Required for primary nodes when `LogBroadcasterEnabled` is `true` ### HTTPURL ```toml From 6466bb229b9872f547efb8c47297e4bac773915f Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 3 Oct 2024 11:22:04 -0400 Subject: [PATCH 20/28] changing CODEOWNERS ownership (#14646) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e1b2b845809..49a0e35e61d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -102,6 +102,7 @@ core/scripts/gateway @smartcontractkit/functions # CI/CD /.github/** @smartcontractkit/releng @smartcontractkit/test-tooling-team +/.github/CODEOWNERS @smartcontractkit/prodsec-public @smartcontractkit/foundations /.github/workflows/performance-tests.yml @smartcontractkit/test-tooling-team /.github/workflows/automation-ondemand-tests.yml @smartcontractkit/keepers /.github/workflows/automation-benchmark-tests.yml @smartcontractkit/keepers From 815b2c80ff6b0dff940497f7734aff3662e7b4f9 Mon Sep 17 00:00:00 2001 From: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:41:26 -0700 Subject: [PATCH 21/28] Ccip-3606 few fixes (#14644) * few fixes * more * generate nops view --- integration-tests/deployment/ccip/state.go | 10 +- .../deployment/ccip/view/nops.go | 92 +++++++++++++++++++ .../deployment/ccip/view/state.go | 3 +- integration-tests/deployment/devenv/jd.go | 11 ++- integration-tests/deployment/environment.go | 4 + 5 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 integration-tests/deployment/ccip/view/nops.go diff --git a/integration-tests/deployment/ccip/state.go b/integration-tests/deployment/ccip/state.go index 6b894d93b0e..2f6e6b7f69e 100644 --- a/integration-tests/deployment/ccip/state.go +++ b/integration-tests/deployment/ccip/state.go @@ -190,7 +190,15 @@ func StateView(e deployment.Environment, ab deployment.AddressBook) (view.CCIPVi if err != nil { return view.CCIPView{}, err } - return state.View(e.AllChainSelectors()) + ccipView, err := state.View(e.AllChainSelectors()) + if err != nil { + return view.CCIPView{}, err + } + ccipView.NodeOperators, err = view.GenerateNopsView(e.NodeIDs, e.Offchain) + if err != nil { + return ccipView, err + } + return ccipView, nil } func LoadOnchainState(e deployment.Environment, ab deployment.AddressBook) (CCIPOnChainState, error) { diff --git a/integration-tests/deployment/ccip/view/nops.go b/integration-tests/deployment/ccip/view/nops.go new file mode 100644 index 00000000000..8b347390615 --- /dev/null +++ b/integration-tests/deployment/ccip/view/nops.go @@ -0,0 +1,92 @@ +package view + +import ( + "context" + "fmt" + + chainsel "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment" + nodev1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/node/v1" +) + +type NopsView struct { + Nops map[string]NopView `json:"nops,omitempty"` +} + +type NopView struct { + // NodeID is the unique identifier of the node + NodeID string `json:"nodeID"` + IsBootstrap bool `json:"isBootstrap"` + OCRKeys map[string]OCRKeyView `json:"ocrKeys"` + PayeeAddress string `json:"payeeAddress"` + CSAKey string `json:"csaKey"` + IsConnected bool `json:"isConnected"` + IsEnabled bool `json:"isEnabled"` +} + +type OCRKeyView struct { + OffchainPublicKey string `json:"offchainPublicKey"` + OnchainPublicKey string `json:"onchainPublicKey"` + PeerID string `json:"peerID"` + TransmitAccount string `json:"transmitAccount"` + ConfigEncryptionPublicKey string `json:"configEncryptionPublicKey"` + KeyBundleID string `json:"keyBundleID"` +} + +func NewNopsView() NopsView { + return NopsView{ + Nops: make(map[string]NopView), + } +} + +func GenerateNopsView(nodeIds []string, oc deployment.OffchainClient) (NopsView, error) { + nops := NewNopsView() + nodes, err := deployment.NodeInfo(nodeIds, oc) + if err != nil { + return nops, err + } + for _, node := range nodes { + // get node info + nodeDetails, err := oc.GetNode(context.Background(), &nodev1.GetNodeRequest{Id: node.NodeID}) + if err != nil { + return NopsView{}, err + } + if nodeDetails == nil || nodeDetails.Node == nil { + return NopsView{}, fmt.Errorf("failed to get node details from offchain client for node %s", node.NodeID) + } + nodeName := nodeDetails.Node.Name + if nodeName == "" { + nodeName = node.NodeID + } + nop := NopView{ + NodeID: node.NodeID, + IsBootstrap: node.IsBootstrap, + OCRKeys: make(map[string]OCRKeyView), + PayeeAddress: node.AdminAddr, + CSAKey: nodeDetails.Node.PublicKey, + IsConnected: nodeDetails.Node.IsConnected, + IsEnabled: nodeDetails.Node.IsEnabled, + } + for sel, ocrConfig := range node.SelToOCRConfig { + chainid, err := chainsel.ChainIdFromSelector(sel) + if err != nil { + return nops, err + } + chainName, err := chainsel.NameFromChainId(chainid) + if err != nil { + return nops, err + } + nop.OCRKeys[chainName] = OCRKeyView{ + OffchainPublicKey: fmt.Sprintf("%x", ocrConfig.OffchainPublicKey[:]), + OnchainPublicKey: fmt.Sprintf("%x", ocrConfig.OnchainPublicKey[:]), + PeerID: ocrConfig.PeerID.String(), + TransmitAccount: string(ocrConfig.TransmitAccount), + ConfigEncryptionPublicKey: fmt.Sprintf("%x", ocrConfig.ConfigEncryptionPublicKey[:]), + KeyBundleID: ocrConfig.KeyBundleID, + } + } + nops.Nops[nodeName] = nop + } + return nops, nil +} diff --git a/integration-tests/deployment/ccip/view/state.go b/integration-tests/deployment/ccip/view/state.go index 5c86f8e27a5..b02ff676c13 100644 --- a/integration-tests/deployment/ccip/view/state.go +++ b/integration-tests/deployment/ccip/view/state.go @@ -1,7 +1,8 @@ package view type CCIPView struct { - Chains map[string]ChainView `json:"chains,omitempty"` + Chains map[string]ChainView `json:"chains,omitempty"` + NodeOperators NopsView `json:"nodeOperators,omitempty"` } func NewCCIPView() CCIPView { diff --git a/integration-tests/deployment/devenv/jd.go b/integration-tests/deployment/devenv/jd.go index 7a8183f04aa..feac1c4ffb4 100644 --- a/integration-tests/deployment/devenv/jd.go +++ b/integration-tests/deployment/devenv/jd.go @@ -58,9 +58,11 @@ func NewJDClient(ctx context.Context, cfg JDConfig) (deployment.OffchainClient, JobServiceClient: jobv1.NewJobServiceClient(conn), CSAServiceClient: csav1.NewCSAServiceClient(conn), } - jd.don, err = NewRegisteredDON(ctx, cfg.nodeInfo, *jd) - if err != nil { - return nil, fmt.Errorf("failed to create registered DON: %w", err) + if cfg.nodeInfo != nil && len(cfg.nodeInfo) > 0 { + jd.don, err = NewRegisteredDON(ctx, cfg.nodeInfo, *jd) + if err != nil { + return nil, fmt.Errorf("failed to create registered DON: %w", err) + } } return jd, err } @@ -90,6 +92,9 @@ func (jd JobDistributor) ProposeJob(ctx context.Context, in *jobv1.ProposeJobReq if res.Proposal == nil { return nil, fmt.Errorf("failed to propose job. err: proposal is nil") } + if jd.don == nil || len(jd.don.Nodes) == 0 { + return res, nil + } for _, node := range jd.don.Nodes { if node.NodeId != in.NodeId { continue diff --git a/integration-tests/deployment/environment.go b/integration-tests/deployment/environment.go index 0f3c85a3627..32c1c3befd7 100644 --- a/integration-tests/deployment/environment.go +++ b/integration-tests/deployment/environment.go @@ -175,6 +175,7 @@ type Node struct { PeerID p2pkey.PeerID IsBootstrap bool MultiAddr string + AdminAddr string } func (n Node) FirstOCRKeybundle() OCRConfig { @@ -208,6 +209,7 @@ func NodeInfo(nodeIDs []string, oc OffchainClient) (Nodes, error) { bootstrap := false var peerID p2pkey.PeerID var multiAddr string + var adminAddr string for _, chainConfig := range nodeChainConfigs.ChainConfigs { if chainConfig.Chain.Type == nodev1.ChainType_CHAIN_TYPE_SOLANA { // Note supported for CCIP yet. @@ -217,6 +219,7 @@ func NodeInfo(nodeIDs []string, oc OffchainClient) (Nodes, error) { // Might make sense to change proto as peerID/multiAddr is 1-1 with nodeID? peerID = MustPeerIDFromString(chainConfig.Ocr2Config.P2PKeyBundle.PeerId) multiAddr = chainConfig.Ocr2Config.Multiaddr + adminAddr = chainConfig.AdminAddress if chainConfig.Ocr2Config.IsBootstrap { // NOTE: Assume same peerID for all chains. // Might make sense to change proto as peerID is 1-1 with nodeID? @@ -254,6 +257,7 @@ func NodeInfo(nodeIDs []string, oc OffchainClient) (Nodes, error) { IsBootstrap: bootstrap, PeerID: peerID, MultiAddr: multiAddr, + AdminAddr: adminAddr, }) } From ad9398a8fd2c2e95f3b4241bbfcfe264d8cda030 Mon Sep 17 00:00:00 2001 From: martin-cll <121895364+martin-cll@users.noreply.github.com> Date: Fri, 4 Oct 2024 02:16:59 +1000 Subject: [PATCH 22/28] Mercury transmitter can use different codecs for native & link price reports (#14631) * Mercury transmitter can use different codecs for native & link price reports * Fix lint. * Don't shadow err var --------- Co-authored-by: Ivaylo Novakov --- .changeset/cuddly-colts-speak.md | 5 ++ core/services/relay/evm/evm.go | 44 +++++++++++------ .../services/relay/evm/mercury/transmitter.go | 12 +++-- .../relay/evm/mercury/transmitter_test.go | 48 ++++++++++++------- 4 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 .changeset/cuddly-colts-speak.md diff --git a/.changeset/cuddly-colts-speak.md b/.changeset/cuddly-colts-speak.md new file mode 100644 index 00000000000..ab16cc83123 --- /dev/null +++ b/.changeset/cuddly-colts-speak.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Fix for Mercury transmitter decoding reports of a different codec version #internal diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 880ad73ad21..6031d396936 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -379,7 +379,6 @@ func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commonty if relayConfig.FeedID == nil { return nil, pkgerrors.New("FeedID must be specified") } - feedID := mercuryutils.FeedID(*relayConfig.FeedID) if relayConfig.ChainID.String() != r.chain.ID().String() { return nil, fmt.Errorf("internal error: chain id in spec does not match this relayer's chain: have %s expected %s", relayConfig.ChainID.String(), r.chain.ID().String()) @@ -430,20 +429,37 @@ func (r *Relayer) NewMercuryProvider(rargs commontypes.RelayArgs, pargs commonty reportCodecV3 := reportcodecv3.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV3")) reportCodecV4 := reportcodecv4.NewReportCodec(*relayConfig.FeedID, lggr.Named("ReportCodecV4")) - var transmitterCodec mercury.TransmitterReportDecoder - switch feedID.Version() { - case 1: - transmitterCodec = reportCodecV1 - case 2: - transmitterCodec = reportCodecV2 - case 3: - transmitterCodec = reportCodecV3 - case 4: - transmitterCodec = reportCodecV4 - default: - return nil, fmt.Errorf("invalid feed version %d", feedID.Version()) + getCodecForFeed := func(feedID mercuryutils.FeedID) (mercury.TransmitterReportDecoder, error) { + var transmitterCodec mercury.TransmitterReportDecoder + switch feedID.Version() { + case 1: + transmitterCodec = reportCodecV1 + case 2: + transmitterCodec = reportCodecV2 + case 3: + transmitterCodec = reportCodecV3 + case 4: + transmitterCodec = reportCodecV4 + default: + return nil, fmt.Errorf("invalid feed version %d", feedID.Version()) + } + return transmitterCodec, nil + } + + benchmarkPriceDecoder := func(feedID mercuryutils.FeedID, report ocrtypes.Report) (*big.Int, error) { + benchmarkPriceCodec, benchmarkPriceErr := getCodecForFeed(feedID) + if benchmarkPriceErr != nil { + return nil, benchmarkPriceErr + } + return benchmarkPriceCodec.BenchmarkPriceFromReport(report) } - transmitter := mercury.NewTransmitter(lggr, r.transmitterCfg, clients, privKey.PublicKey, rargs.JobID, *relayConfig.FeedID, r.mercuryORM, transmitterCodec, r.triggerCapability) + + transmitterCodec, err := getCodecForFeed(mercuryutils.FeedID(*relayConfig.FeedID)) + if err != nil { + return nil, err + } + + transmitter := mercury.NewTransmitter(lggr, r.transmitterCfg, clients, privKey.PublicKey, rargs.JobID, *relayConfig.FeedID, r.mercuryORM, transmitterCodec, benchmarkPriceDecoder, r.triggerCapability) return NewMercuryProvider(cp, r.codec, NewMercuryChainReader(r.chain.HeadTracker()), transmitter, reportCodecV1, reportCodecV2, reportCodecV3, reportCodecV4, lggr), nil } diff --git a/core/services/relay/evm/mercury/transmitter.go b/core/services/relay/evm/mercury/transmitter.go index b914e67b453..c2cb89387a4 100644 --- a/core/services/relay/evm/mercury/transmitter.go +++ b/core/services/relay/evm/mercury/transmitter.go @@ -101,6 +101,8 @@ type TransmitterReportDecoder interface { ObservationTimestampFromReport(report ocrtypes.Report) (uint32, error) } +type BenchmarkPriceDecoder func(feedID mercuryutils.FeedID, report ocrtypes.Report) (*big.Int, error) + var _ Transmitter = (*mercuryTransmitter)(nil) type TransmitterConfig interface { @@ -116,8 +118,9 @@ type mercuryTransmitter struct { orm ORM servers map[string]*server - codec TransmitterReportDecoder - triggerCapability *triggers.MercuryTriggerService + codec TransmitterReportDecoder + benchmarkPriceDecoder BenchmarkPriceDecoder + triggerCapability *triggers.MercuryTriggerService feedID mercuryutils.FeedID jobID int32 @@ -301,7 +304,7 @@ func newServer(lggr logger.Logger, cfg TransmitterConfig, client wsrpc.Client, p } } -func NewTransmitter(lggr logger.Logger, cfg TransmitterConfig, clients map[string]wsrpc.Client, fromAccount ed25519.PublicKey, jobID int32, feedID [32]byte, orm ORM, codec TransmitterReportDecoder, triggerCapability *triggers.MercuryTriggerService) *mercuryTransmitter { +func NewTransmitter(lggr logger.Logger, cfg TransmitterConfig, clients map[string]wsrpc.Client, fromAccount ed25519.PublicKey, jobID int32, feedID [32]byte, orm ORM, codec TransmitterReportDecoder, benchmarkPriceDecoder BenchmarkPriceDecoder, triggerCapability *triggers.MercuryTriggerService) *mercuryTransmitter { sugared := logger.Sugared(lggr) feedIDHex := fmt.Sprintf("0x%x", feedID[:]) servers := make(map[string]*server, len(clients)) @@ -317,6 +320,7 @@ func NewTransmitter(lggr logger.Logger, cfg TransmitterConfig, clients map[strin orm, servers, codec, + benchmarkPriceDecoder, triggerCapability, feedID, jobID, @@ -513,7 +517,7 @@ func (mt *mercuryTransmitter) LatestPrice(ctx context.Context, feedID [32]byte) if !is { return nil, fmt.Errorf("expected report to be []byte, but it was %T", m["report"]) } - return mt.codec.BenchmarkPriceFromReport(report) + return mt.benchmarkPriceDecoder(feedID, report) } // LatestTimestamp will return -1, nil if the feed is missing diff --git a/core/services/relay/evm/mercury/transmitter_test.go b/core/services/relay/evm/mercury/transmitter_test.go index 4eedc0c24a8..31868881c03 100644 --- a/core/services/relay/evm/mercury/transmitter_test.go +++ b/core/services/relay/evm/mercury/transmitter_test.go @@ -9,11 +9,10 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "github.com/smartcontractkit/chainlink-common/pkg/capabilities/triggers" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" @@ -21,6 +20,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" mercurytypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/types" + mercuryutils "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/utils" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/wsrpc/pb" @@ -43,6 +43,9 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { pgtest.MustExec(t, db, `SET CONSTRAINTS mercury_transmit_requests_job_id_fkey DEFERRED`) pgtest.MustExec(t, db, `SET CONSTRAINTS feed_latest_reports_job_id_fkey DEFERRED`) codec := new(mockCodec) + benchmarkPriceDecoder := func(feedID mercuryutils.FeedID, report ocrtypes.Report) (*big.Int, error) { + return codec.BenchmarkPriceFromReport(report) + } orm := NewORM(db) clients := map[string]wsrpc.Client{} @@ -51,7 +54,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { report := sampleV1Report c := &mocks.MockWSRPCClient{} clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) // init the queue since we skipped starting transmitter mt.servers[sURL].q.Init([]*Transmission{}) err := mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) @@ -65,7 +68,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { report := sampleV2Report c := &mocks.MockWSRPCClient{} clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) // init the queue since we skipped starting transmitter mt.servers[sURL].q.Init([]*Transmission{}) err := mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) @@ -79,7 +82,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { report := sampleV3Report c := &mocks.MockWSRPCClient{} clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) // init the queue since we skipped starting transmitter mt.servers[sURL].q.Init([]*Transmission{}) err := mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) @@ -94,7 +97,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { c := &mocks.MockWSRPCClient{} clients[sURL] = c triggerService := triggers.NewMercuryTriggerService(0, lggr) - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, triggerService) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, triggerService) // init the queue since we skipped starting transmitter mt.servers[sURL].q.Init([]*Transmission{}) err := mt.Transmit(testutils.Context(t), sampleReportContext, report, sampleSigs) @@ -111,7 +114,7 @@ func Test_MercuryTransmitter_Transmit(t *testing.T) { clients[sURL2] = c clients[sURL3] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) // init the queue since we skipped starting transmitter mt.servers[sURL].q.Init([]*Transmission{}) mt.servers[sURL2].q.Init([]*Transmission{}) @@ -136,6 +139,9 @@ func Test_MercuryTransmitter_LatestTimestamp(t *testing.T) { db := pgtest.NewSqlxDB(t) var jobID int32 codec := new(mockCodec) + benchmarkPriceDecoder := func(feedID mercuryutils.FeedID, report ocrtypes.Report) (*big.Int, error) { + return codec.BenchmarkPriceFromReport(report) + } orm := NewORM(db) clients := map[string]wsrpc.Client{} @@ -153,7 +159,7 @@ func Test_MercuryTransmitter_LatestTimestamp(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) ts, err := mt.LatestTimestamp(testutils.Context(t)) require.NoError(t, err) @@ -169,7 +175,7 @@ func Test_MercuryTransmitter_LatestTimestamp(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) ts, err := mt.LatestTimestamp(testutils.Context(t)) require.NoError(t, err) @@ -183,7 +189,7 @@ func Test_MercuryTransmitter_LatestTimestamp(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) _, err := mt.LatestTimestamp(testutils.Context(t)) require.Error(t, err) assert.Contains(t, err.Error(), "something exploded") @@ -213,7 +219,7 @@ func Test_MercuryTransmitter_LatestTimestamp(t *testing.T) { return out, nil }, } - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) ts, err := mt.LatestTimestamp(testutils.Context(t)) require.NoError(t, err) @@ -243,6 +249,9 @@ func Test_MercuryTransmitter_LatestPrice(t *testing.T) { var jobID int32 codec := new(mockCodec) + benchmarkPriceDecoder := func(feedID mercuryutils.FeedID, report ocrtypes.Report) (*big.Int, error) { + return codec.BenchmarkPriceFromReport(report) + } orm := NewORM(db) clients := map[string]wsrpc.Client{} @@ -260,7 +269,7 @@ func Test_MercuryTransmitter_LatestPrice(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) t.Run("BenchmarkPriceFromReport succeeds", func(t *testing.T) { codec.val = originalPrice @@ -291,7 +300,7 @@ func Test_MercuryTransmitter_LatestPrice(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) price, err := mt.LatestPrice(testutils.Context(t), sampleFeedID) require.NoError(t, err) @@ -305,7 +314,7 @@ func Test_MercuryTransmitter_LatestPrice(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) _, err := mt.LatestPrice(testutils.Context(t), sampleFeedID) require.Error(t, err) assert.Contains(t, err.Error(), "something exploded") @@ -319,6 +328,9 @@ func Test_MercuryTransmitter_FetchInitialMaxFinalizedBlockNumber(t *testing.T) { db := pgtest.NewSqlxDB(t) var jobID int32 codec := new(mockCodec) + benchmarkPriceDecoder := func(feedID mercuryutils.FeedID, report ocrtypes.Report) (*big.Int, error) { + return codec.BenchmarkPriceFromReport(report) + } orm := NewORM(db) clients := map[string]wsrpc.Client{} @@ -335,7 +347,7 @@ func Test_MercuryTransmitter_FetchInitialMaxFinalizedBlockNumber(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) bn, err := mt.FetchInitialMaxFinalizedBlockNumber(testutils.Context(t)) require.NoError(t, err) @@ -351,7 +363,7 @@ func Test_MercuryTransmitter_FetchInitialMaxFinalizedBlockNumber(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) bn, err := mt.FetchInitialMaxFinalizedBlockNumber(testutils.Context(t)) require.NoError(t, err) @@ -364,7 +376,7 @@ func Test_MercuryTransmitter_FetchInitialMaxFinalizedBlockNumber(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) _, err := mt.FetchInitialMaxFinalizedBlockNumber(testutils.Context(t)) require.Error(t, err) assert.Contains(t, err.Error(), "something exploded") @@ -382,7 +394,7 @@ func Test_MercuryTransmitter_FetchInitialMaxFinalizedBlockNumber(t *testing.T) { }, } clients[sURL] = c - mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, nil) + mt := NewTransmitter(lggr, mockCfg{}, clients, sampleClientPubKey, jobID, sampleFeedID, orm, codec, benchmarkPriceDecoder, nil) _, err := mt.FetchInitialMaxFinalizedBlockNumber(testutils.Context(t)) require.Error(t, err) assert.Contains(t, err.Error(), "latestReport failed; mismatched feed IDs, expected: 0x1c916b4aa7e57ca7b68ae1bf45653f56b656fd3aa335ef7fae696b663f1b8472, got: 0x") From 9efabb039f78c1890a4a3749fb4986d6b7947733 Mon Sep 17 00:00:00 2001 From: Connor Stein Date: Thu, 3 Oct 2024 12:40:01 -0400 Subject: [PATCH 23/28] Parameterize MCMS configuration (#14626) * Parameterize MCMS configuration * Fix a test * Fix tests * Fix another test * More tests * Fix executor config * Separate mcms --- .../deployment/ccip/add_chain.go | 20 +- .../deployment/ccip/add_chain_test.go | 18 +- .../deployment/ccip/add_lane_test.go | 14 +- .../deployment/ccip/changeset/cap_reg.go | 3 +- .../ccip/changeset/initial_deploy.go | 3 +- .../ccip/changeset/initial_deploy_test.go | 19 +- integration-tests/deployment/ccip/deploy.go | 419 +++++++++++------- .../deployment/ccip/deploy_home_chain.go | 13 +- .../deployment/ccip/deploy_test.go | 38 +- integration-tests/deployment/ccip/propose.go | 59 ++- integration-tests/deployment/ccip/state.go | 27 +- .../deployment/ccip/test_helpers.go | 101 +++-- .../deployment/ccip/token_info.go | 5 +- integration-tests/deployment/helpers.go | 7 + integration-tests/smoke/ccip_test.go | 16 +- 15 files changed, 462 insertions(+), 300 deletions(-) diff --git a/integration-tests/deployment/ccip/add_chain.go b/integration-tests/deployment/ccip/add_chain.go index 48cf7542d7f..86faaec167d 100644 --- a/integration-tests/deployment/ccip/add_chain.go +++ b/integration-tests/deployment/ccip/add_chain.go @@ -38,7 +38,7 @@ func NewChainInboundProposal( timelockAddresses := make(map[mcms.ChainIdentifier]common.Address) for _, source := range sources { chain, _ := chainsel.ChainBySelector(source) - enableOnRampDest, err := state.Chains[source].OnRamp.ApplyDestChainConfigUpdates(SimTransactOpts(), []onramp.OnRampDestChainConfigArgs{ + enableOnRampDest, err := state.Chains[source].OnRamp.ApplyDestChainConfigUpdates(deployment.SimTransactOpts(), []onramp.OnRampDestChainConfigArgs{ { DestChainSelector: newChainSel, Router: state.Chains[source].TestRouter.Address(), @@ -48,7 +48,7 @@ func NewChainInboundProposal( return nil, err } enableFeeQuoterDest, err := state.Chains[source].FeeQuoter.ApplyDestChainConfigUpdates( - SimTransactOpts(), + deployment.SimTransactOpts(), []fee_quoter.FeeQuoterDestChainConfigArgs{ { DestChainSelector: newChainSel, @@ -59,7 +59,7 @@ func NewChainInboundProposal( return nil, err } initialPrices, err := state.Chains[source].FeeQuoter.UpdatePrices( - SimTransactOpts(), + deployment.SimTransactOpts(), fee_quoter.InternalPriceUpdates{ TokenPriceUpdates: []fee_quoter.InternalTokenPriceUpdate{}, GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ @@ -94,13 +94,13 @@ func NewChainInboundProposal( }, }, }) - opCount, err := state.Chains[source].Mcm.GetOpCount(nil) + opCount, err := state.Chains[source].ProposerMcm.GetOpCount(nil) if err != nil { return nil, err } metaDataPerChain[mcms.ChainIdentifier(chain.Selector)] = mcms.ChainMetadata{ StartingOpCount: opCount.Uint64(), - MCMAddress: state.Chains[source].Mcm.Address(), + MCMAddress: state.Chains[source].ProposerMcm.Address(), } timelockAddresses[mcms.ChainIdentifier(chain.Selector)] = state.Chains[source].Timelock.Address() } @@ -121,7 +121,7 @@ func NewChainInboundProposal( } chainConfig := SetupConfigInfo(newChainSel, nodes.NonBootstraps().PeerIDs(), nodes.DefaultF(), encodedExtraChainConfig) - addChain, err := state.Chains[homeChainSel].CCIPConfig.ApplyChainConfigUpdates(SimTransactOpts(), nil, []ccip_config.CCIPConfigTypesChainConfigInfo{ + addChain, err := state.Chains[homeChainSel].CCIPConfig.ApplyChainConfigUpdates(deployment.SimTransactOpts(), nil, []ccip_config.CCIPConfigTypesChainConfigInfo{ chainConfig, }) if err != nil { @@ -133,13 +133,13 @@ func NewChainInboundProposal( state.Chains[newChainSel].OffRamp, e.Chains[newChainSel], feedChainSel, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel]), + tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken), nodes.NonBootstraps(), ) if err != nil { return nil, err } - addDON, err := state.Chains[homeChainSel].CapabilityRegistry.AddDON(SimTransactOpts(), + addDON, err := state.Chains[homeChainSel].CapabilityRegistry.AddDON(deployment.SimTransactOpts(), nodes.NonBootstraps().PeerIDs(), []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ { CapabilityId: CCIPCapabilityID, @@ -150,13 +150,13 @@ func NewChainInboundProposal( return nil, err } homeChain, _ := chainsel.ChainBySelector(homeChainSel) - opCount, err := state.Chains[homeChainSel].Mcm.GetOpCount(nil) + opCount, err := state.Chains[homeChainSel].ProposerMcm.GetOpCount(nil) if err != nil { return nil, err } metaDataPerChain[mcms.ChainIdentifier(homeChain.Selector)] = mcms.ChainMetadata{ StartingOpCount: opCount.Uint64(), - MCMAddress: state.Chains[homeChainSel].Mcm.Address(), + MCMAddress: state.Chains[homeChainSel].ProposerMcm.Address(), } timelockAddresses[mcms.ChainIdentifier(homeChain.Selector)] = state.Chains[homeChainSel].Timelock.Address() batches = append(batches, timelock.BatchChainOperation{ diff --git a/integration-tests/deployment/ccip/add_chain_test.go b/integration-tests/deployment/ccip/add_chain_test.go index fc39bddcd93..c1a9571d40d 100644 --- a/integration-tests/deployment/ccip/add_chain_test.go +++ b/integration-tests/deployment/ccip/add_chain_test.go @@ -41,15 +41,16 @@ func TestAddChainInbound(t *testing.T) { DeviationPPB: cciptypes.NewBigIntFromInt64(1e9), }, ) - ab, err := DeployCCIPContracts(e.Env, DeployCCIPContractConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainsToDeploy: initialDeploy, - TokenConfig: tokenConfig, - CCIPOnChainState: state, + err = DeployCCIPContracts(e.Env, e.Ab, DeployCCIPContractConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainsToDeploy: initialDeploy, + TokenConfig: tokenConfig, + MCMSConfig: NewTestMCMSConfig(t, e.Env), + FeeTokenContracts: e.FeeTokenContracts, + CapabilityRegistry: state.Chains[e.HomeChainSel].CapabilityRegistry.Address(), }) require.NoError(t, err) - require.NoError(t, e.Ab.Merge(ab)) state, err = LoadOnchainState(e.Env, e.Ab) require.NoError(t, err) @@ -63,9 +64,8 @@ func TestAddChainInbound(t *testing.T) { } // Deploy contracts to new chain - newAddresses, err := DeployChainContracts(e.Env, e.Env.Chains[newChain], deployment.NewMemoryAddressBook()) + err = DeployChainContracts(e.Env, e.Env.Chains[newChain], e.Ab, e.FeeTokenContracts[newChain], NewTestMCMSConfig(t, e.Env)) require.NoError(t, err) - require.NoError(t, e.Ab.Merge(newAddresses)) state, err = LoadOnchainState(e.Env, e.Ab) require.NoError(t, err) diff --git a/integration-tests/deployment/ccip/add_lane_test.go b/integration-tests/deployment/ccip/add_lane_test.go index 540b5dded54..5a47b8f087f 100644 --- a/integration-tests/deployment/ccip/add_lane_test.go +++ b/integration-tests/deployment/ccip/add_lane_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -21,14 +20,15 @@ func TestAddLane(t *testing.T) { state, err := LoadOnchainState(e.Env, e.Ab) require.NoError(t, err) // Set up CCIP contracts and a DON per chain. - ab, err := DeployCCIPContracts(e.Env, DeployCCIPContractConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - TokenConfig: NewTokenConfig(), - CCIPOnChainState: state, + err = DeployCCIPContracts(e.Env, e.Ab, DeployCCIPContractConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + TokenConfig: NewTokenConfig(), + MCMSConfig: NewTestMCMSConfig(t, e.Env), + FeeTokenContracts: e.FeeTokenContracts, + CapabilityRegistry: state.Chains[e.HomeChainSel].CapabilityRegistry.Address(), }) require.NoError(t, err) - require.NoError(t, e.Ab.Merge(ab)) // We expect no lanes available on any chain. state, err = LoadOnchainState(e.Env, e.Ab) diff --git a/integration-tests/deployment/ccip/changeset/cap_reg.go b/integration-tests/deployment/ccip/changeset/cap_reg.go index 0c12d20d94a..f1de1016ddf 100644 --- a/integration-tests/deployment/ccip/changeset/cap_reg.go +++ b/integration-tests/deployment/ccip/changeset/cap_reg.go @@ -10,7 +10,8 @@ import ( // Separated changset because cap reg is an env var for CL nodes. func CapRegChangeSet(env deployment.Environment, homeChainSel uint64) (deployment.ChangesetOutput, error) { // Note we also deploy the cap reg. - ab, _, err := ccipdeployment.DeployCapReg(env.Logger, env.Chains[homeChainSel]) + ab := deployment.NewMemoryAddressBook() + _, err := ccipdeployment.DeployCapReg(env.Logger, ab, env.Chains[homeChainSel]) if err != nil { env.Logger.Errorw("Failed to deploy cap reg", "err", err, "addresses", ab) return deployment.ChangesetOutput{}, err diff --git a/integration-tests/deployment/ccip/changeset/initial_deploy.go b/integration-tests/deployment/ccip/changeset/initial_deploy.go index 9a97150ade7..562ab763d0c 100644 --- a/integration-tests/deployment/ccip/changeset/initial_deploy.go +++ b/integration-tests/deployment/ccip/changeset/initial_deploy.go @@ -13,7 +13,8 @@ import ( // Note if the change set is a deployment and it fails we have 2 options: // - Just throw away the addresses, fix issue and try again (potentially expensive on mainnet) func InitialDeployChangeSet(env deployment.Environment, c ccipdeployment.DeployCCIPContractConfig) (deployment.ChangesetOutput, error) { - ab, err := ccipdeployment.DeployCCIPContracts(env, c) + ab := deployment.NewMemoryAddressBook() + err := ccipdeployment.DeployCCIPContracts(env, ab, c) if err != nil { env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "addresses", ab) return deployment.ChangesetOutput{}, deployment.MaybeDataErr(err) diff --git a/integration-tests/deployment/ccip/changeset/initial_deploy_test.go b/integration-tests/deployment/ccip/changeset/initial_deploy_test.go index 6f40d41acb9..ee207391918 100644 --- a/integration-tests/deployment/ccip/changeset/initial_deploy_test.go +++ b/integration-tests/deployment/ccip/changeset/initial_deploy_test.go @@ -25,6 +25,7 @@ func TestInitialDeploy(t *testing.T) { state, err := ccdeploy.LoadOnchainState(tenv.Env, tenv.Ab) require.NoError(t, err) + require.NotNil(t, state.Chains[tenv.HomeChainSel].LinkToken) feeds := state.Chains[tenv.FeedChainSel].USDFeeds tokenConfig := ccdeploy.NewTokenConfig() @@ -35,18 +36,20 @@ func TestInitialDeploy(t *testing.T) { DeviationPPB: cciptypes.NewBigIntFromInt64(1e9), }, ) - // Apply changeset + output, err := InitialDeployChangeSet(tenv.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: tokenConfig, - // Capreg/config and feeds already exist. - CCIPOnChainState: state, + HomeChainSel: tenv.HomeChainSel, + FeedChainSel: tenv.FeedChainSel, + ChainsToDeploy: tenv.Env.AllChainSelectors(), + TokenConfig: tokenConfig, + MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), + CapabilityRegistry: state.Chains[tenv.HomeChainSel].CapabilityRegistry.Address(), + FeeTokenContracts: tenv.FeeTokenContracts, }) require.NoError(t, err) + require.NoError(t, tenv.Ab.Merge(output.AddressBook)) // Get new state after migration. - state, err = ccdeploy.LoadOnchainState(e, output.AddressBook) + state, err = ccdeploy.LoadOnchainState(e, tenv.Ab) require.NoError(t, err) // Ensure capreg logs are up to date. diff --git a/integration-tests/deployment/ccip/deploy.go b/integration-tests/deployment/ccip/deploy.go index 0f42bd235b9..c2d1998be64 100644 --- a/integration-tests/deployment/ccip/deploy.go +++ b/integration-tests/deployment/ccip/deploy.go @@ -1,7 +1,6 @@ package ccipdeployment import ( - "crypto/ecdsa" "fmt" "math/big" @@ -9,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" @@ -36,37 +34,30 @@ import ( ) var ( - RMNRemote deployment.ContractType = "RMNRemote" - LinkToken deployment.ContractType = "LinkToken" - ARMProxy deployment.ContractType = "ARMProxy" - WETH9 deployment.ContractType = "WETH9" - Router deployment.ContractType = "Router" - CommitStore deployment.ContractType = "CommitStore" - TokenAdminRegistry deployment.ContractType = "TokenAdminRegistry" - NonceManager deployment.ContractType = "NonceManager" - FeeQuoter deployment.ContractType = "FeeQuoter" - ManyChainMultisig deployment.ContractType = "ManyChainMultiSig" - CCIPConfig deployment.ContractType = "CCIPConfig" - RBACTimelock deployment.ContractType = "RBACTimelock" - OnRamp deployment.ContractType = "OnRamp" - OffRamp deployment.ContractType = "OffRamp" - CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" - PriceFeed deployment.ContractType = "PriceFeed" + RMNRemote deployment.ContractType = "RMNRemote" + LinkToken deployment.ContractType = "LinkToken" + ARMProxy deployment.ContractType = "ARMProxy" + WETH9 deployment.ContractType = "WETH9" + Router deployment.ContractType = "Router" + CommitStore deployment.ContractType = "CommitStore" + TokenAdminRegistry deployment.ContractType = "TokenAdminRegistry" + NonceManager deployment.ContractType = "NonceManager" + FeeQuoter deployment.ContractType = "FeeQuoter" + AdminManyChainMultisig deployment.ContractType = "AdminManyChainMultiSig" + BypasserManyChainMultisig deployment.ContractType = "BypasserManyChainMultiSig" + CancellerManyChainMultisig deployment.ContractType = "CancellerManyChainMultiSig" + ProposerManyChainMultisig deployment.ContractType = "ProposerManyChainMultiSig" + CCIPConfig deployment.ContractType = "CCIPConfig" + RBACTimelock deployment.ContractType = "RBACTimelock" + OnRamp deployment.ContractType = "OnRamp" + OffRamp deployment.ContractType = "OffRamp" + CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" + PriceFeed deployment.ContractType = "PriceFeed" // Note test router maps to a regular router contract. TestRouter deployment.ContractType = "TestRouter" CCIPReceiver deployment.ContractType = "CCIPReceiver" - - TestXXXMCMSSigner *ecdsa.PrivateKey ) -func init() { - key, err := crypto.GenerateKey() - if err != nil { - panic(err) - } - TestXXXMCMSSigner = key -} - type Contracts interface { *capabilities_registry.CapabilitiesRegistry | *rmn_proxy_contract.RMNProxyContract | @@ -123,86 +114,102 @@ func deployContract[C Contracts]( } type DeployCCIPContractConfig struct { - HomeChainSel uint64 - FeedChainSel uint64 - ChainsToDeploy []uint64 - TokenConfig TokenConfig - // Existing contracts which we want to skip deployment - // Leave empty if we want to deploy everything - // TODO: Add skips to deploy function. - CCIPOnChainState + HomeChainSel uint64 + FeedChainSel uint64 + ChainsToDeploy []uint64 + TokenConfig TokenConfig + CapabilityRegistry common.Address + FeeTokenContracts map[uint64]FeeTokenContracts + // I believe it makes sense to have the same signers across all chains + // since that's the point MCMS. + MCMSConfig MCMSConfig } -// TODO: Likely we'll want to further parameterize the deployment -// For example a list of contracts to skip deploying if they already exist. -// Or mock vs real RMN. -// Deployment produces an address book of everything it deployed. -func DeployCCIPContracts(e deployment.Environment, c DeployCCIPContractConfig) (deployment.AddressBook, error) { - var ab deployment.AddressBook = deployment.NewMemoryAddressBook() +// DeployCCIPContracts assumes that the capability registry and ccip home contracts +// are already deployed (needed as a first step because the chainlink nodes point to them). +// It then deploys +func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c DeployCCIPContractConfig) error { nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) if err != nil || len(nodes) == 0 { e.Logger.Errorw("Failed to get node info", "err", err) - return ab, err + return err } - if c.Chains[c.HomeChainSel].CapabilityRegistry == nil { - return ab, fmt.Errorf("capability registry not found for home chain %d, needs to be deployed first", c.HomeChainSel) + capReg, err := capabilities_registry.NewCapabilitiesRegistry(c.CapabilityRegistry, e.Chains[c.HomeChainSel].Client) + if err != nil { + e.Logger.Errorw("Failed to get capability registry", "err", err) + return err } - cr, err := c.Chains[c.HomeChainSel].CapabilityRegistry.GetHashedCapabilityId( + cr, err := capReg.GetHashedCapabilityId( &bind.CallOpts{}, CapabilityLabelledName, CapabilityVersion) if err != nil { e.Logger.Errorw("Failed to get hashed capability id", "err", err) - return ab, err + return err } if cr != CCIPCapabilityID { - return ab, fmt.Errorf("Capability registry does not support CCIP %s %s", hexutil.Encode(cr[:]), hexutil.Encode(CCIPCapabilityID[:])) + return fmt.Errorf("Capability registry does not support CCIP %s %s", hexutil.Encode(cr[:]), hexutil.Encode(CCIPCapabilityID[:])) + } + capability, err := capReg.GetCapability(nil, CCIPCapabilityID) + if err != nil { + e.Logger.Errorw("Failed to get capability", "err", err) + return err + } + ccipConfig, err := ccip_config.NewCCIPConfig(capability.ConfigurationContract, e.Chains[c.HomeChainSel].Client) + if err != nil { + e.Logger.Errorw("Failed to get ccip config", "err", err) + return err } + // Signal to CR that our nodes support CCIP capability. if err := AddNodes( - c.Chains[c.HomeChainSel].CapabilityRegistry, + capReg, e.Chains[c.HomeChainSel], nodes.NonBootstraps().PeerIDs(), ); err != nil { - return ab, err + return err } for _, chainSel := range c.ChainsToDeploy { chain, ok := e.Chains[chainSel] if !ok { - return ab, fmt.Errorf("Chain %d not found", chainSel) + return fmt.Errorf("Chain %d not found", chainSel) + } + chainConfig, ok := c.FeeTokenContracts[chainSel] + if !ok { + return fmt.Errorf("Chain %d config not found", chainSel) } - ab, err = DeployChainContracts(e, chain, ab) + err = DeployChainContracts(e, chain, ab, chainConfig, c.MCMSConfig) if err != nil { - return ab, err + return err } chainAddresses, err := ab.AddressesForChain(chain.Selector) if err != nil { e.Logger.Errorw("Failed to get chain addresses", "err", err) - return ab, err + return err } chainState, err := LoadChainState(chain, chainAddresses) if err != nil { e.Logger.Errorw("Failed to load chain state", "err", err) - return ab, err + return err } - tokenInfo := c.TokenConfig.GetTokenInfo(e.Logger, chainState) + tokenInfo := c.TokenConfig.GetTokenInfo(e.Logger, c.FeeTokenContracts[chainSel].LinkToken) // TODO: Do we want to extract this? // Add chain config for each chain. _, err = AddChainConfig( e.Logger, e.Chains[c.HomeChainSel], - c.Chains[c.HomeChainSel].CCIPConfig, + ccipConfig, chain.Selector, nodes.NonBootstraps().PeerIDs()) if err != nil { - return ab, err + return err } // For each chain, we create a DON on the home chain (2 OCR instances) if err := AddDON( e.Logger, - c.Chains[c.HomeChainSel].CapabilityRegistry, - c.Chains[c.HomeChainSel].CCIPConfig, + capReg, + ccipConfig, chainState.OffRamp, c.FeedChainSel, tokenInfo, @@ -211,78 +218,43 @@ func DeployCCIPContracts(e deployment.Environment, c DeployCCIPContractConfig) ( nodes.NonBootstraps(), ); err != nil { e.Logger.Errorw("Failed to add DON", "err", err) - return ab, err + return err } } - return ab, nil + return nil } -func DeployChainContracts( - e deployment.Environment, +type MCMSConfig struct { + Admin config.Config + Canceller config.Config + Bypasser config.Config + Proposer config.Config + Executors []common.Address +} + +func DeployMCMSWithConfig( + contractType deployment.ContractType, + lggr logger.Logger, chain deployment.Chain, ab deployment.AddressBook, -) (deployment.AddressBook, error) { - ccipReceiver, err := deployContract(e.Logger, chain, ab, - func(chain deployment.Chain) ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver] { - receiverAddr, tx, receiver, err2 := maybe_revert_message_receiver.DeployMaybeRevertMessageReceiver( - chain.DeployerKey, - chain.Client, - false, - ) - return ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver]{ - receiverAddr, receiver, tx, deployment.NewTypeAndVersion(CCIPReceiver, deployment.Version1_0_0), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy receiver", "err", err) - return ab, err - } - e.Logger.Infow("deployed receiver", "addr", ccipReceiver.Address) - - // TODO: Still waiting for RMNRemote/RMNHome contracts etc. - rmnRemote, err := deployContract(e.Logger, chain, ab, - func(chain deployment.Chain) ContractDeploy[*rmn_remote.RMNRemote] { - rmnRemoteAddr, tx, rmnRemote, err2 := rmn_remote.DeployRMNRemote( - chain.DeployerKey, - chain.Client, - chain.Selector, - ) - return ContractDeploy[*rmn_remote.RMNRemote]{ - rmnRemoteAddr, rmnRemote, tx, deployment.NewTypeAndVersion(RMNRemote, deployment.Version1_6_0_dev), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy RMNRemote", "err", err) - return ab, err - } - e.Logger.Infow("deployed RMNRemote", "addr", rmnRemote.Address) - - mcm, err := deployContract(e.Logger, chain, ab, + mcmConfig config.Config, +) (*ContractDeploy[*owner_helpers.ManyChainMultiSig], error) { + groupQuorums, groupParents, signerAddresses, signerGroups := mcmConfig.ExtractSetConfigInputs() + mcm, err := deployContract(lggr, chain, ab, func(chain deployment.Chain) ContractDeploy[*owner_helpers.ManyChainMultiSig] { mcmAddr, tx, mcm, err2 := owner_helpers.DeployManyChainMultiSig( chain.DeployerKey, chain.Client, ) return ContractDeploy[*owner_helpers.ManyChainMultiSig]{ - mcmAddr, mcm, tx, deployment.NewTypeAndVersion(ManyChainMultisig, deployment.Version1_0_0), err2, + mcmAddr, mcm, tx, deployment.NewTypeAndVersion(contractType, deployment.Version1_0_0), err2, } }) if err != nil { - e.Logger.Errorw("Failed to deploy mcm", "err", err) - return ab, err - } - // TODO: Parameterize this. - e.Logger.Infow("deployed mcm", "addr", mcm.Address) - publicKey := TestXXXMCMSSigner.Public().(*ecdsa.PublicKey) - // Convert the public key to an Ethereum address - address := crypto.PubkeyToAddress(*publicKey) - c, err := config.NewConfig(1, []common.Address{address}, []config.Config{}) - if err != nil { - e.Logger.Errorw("Failed to create config", "err", err) - return ab, err + lggr.Errorw("Failed to deploy mcm", "err", err) + return mcm, err } - groupQuorums, groupParents, signerAddresses, signerGroups := c.ExtractSetConfigInputs() mcmsTx, err := mcm.Contract.SetConfig(chain.DeployerKey, signerAddresses, signerGroups, // Signer 1 is int group 0 (root group) with quorum 1. @@ -291,53 +263,91 @@ func DeployChainContracts( false, ) if _, err := deployment.ConfirmIfNoError(chain, mcmsTx, err); err != nil { - e.Logger.Errorw("Failed to confirm mcm config", "err", err) - return ab, err + lggr.Errorw("Failed to confirm mcm config", "err", err) + return mcm, err + } + return mcm, nil +} + +type MCMSContracts struct { + Admin *ContractDeploy[*owner_helpers.ManyChainMultiSig] + Canceller *ContractDeploy[*owner_helpers.ManyChainMultiSig] + Bypasser *ContractDeploy[*owner_helpers.ManyChainMultiSig] + Proposer *ContractDeploy[*owner_helpers.ManyChainMultiSig] + Timelock *ContractDeploy[*owner_helpers.RBACTimelock] +} + +// DeployMCMSContracts deploys the MCMS contracts for the given configuration +// as well as the timelock. +func DeployMCMSContracts( + lggr logger.Logger, + chain deployment.Chain, + ab deployment.AddressBook, + mcmConfig MCMSConfig, +) (*MCMSContracts, error) { + adminMCM, err := DeployMCMSWithConfig(AdminManyChainMultisig, lggr, chain, ab, mcmConfig.Admin) + if err != nil { + return nil, err + } + bypasser, err := DeployMCMSWithConfig(BypasserManyChainMultisig, lggr, chain, ab, mcmConfig.Bypasser) + if err != nil { + return nil, err + } + canceller, err := DeployMCMSWithConfig(CancellerManyChainMultisig, lggr, chain, ab, mcmConfig.Canceller) + if err != nil { + return nil, err + } + proposer, err := DeployMCMSWithConfig(ProposerManyChainMultisig, lggr, chain, ab, mcmConfig.Proposer) + if err != nil { + return nil, err } - timelock, err := deployContract(e.Logger, chain, ab, + timelock, err := deployContract(lggr, chain, ab, func(chain deployment.Chain) ContractDeploy[*owner_helpers.RBACTimelock] { timelock, tx, cc, err2 := owner_helpers.DeployRBACTimelock( chain.DeployerKey, chain.Client, big.NewInt(0), // minDelay - mcm.Address, - // TODO: Actual MCM groups need to be parameterized. - []common.Address{mcm.Address}, // proposers - []common.Address{chain.DeployerKey.From}, //executors - []common.Address{mcm.Address}, // cancellers - []common.Address{mcm.Address}, // bypassers + adminMCM.Address, + []common.Address{proposer.Address}, // proposers + mcmConfig.Executors, //executors + []common.Address{canceller.Address}, // cancellers + []common.Address{bypasser.Address}, // bypassers ) return ContractDeploy[*owner_helpers.RBACTimelock]{ timelock, cc, tx, deployment.NewTypeAndVersion(RBACTimelock, deployment.Version1_0_0), err2, } }) if err != nil { - e.Logger.Errorw("Failed to deploy timelock", "err", err) - return ab, err + lggr.Errorw("Failed to deploy timelock", "err", err) + return nil, err } - e.Logger.Infow("deployed timelock", "addr", mcm.Address) + lggr.Infow("deployed timelock", "addr", timelock.Address) + return &MCMSContracts{ + Admin: adminMCM, + Canceller: canceller, + Bypasser: bypasser, + Proposer: proposer, + Timelock: timelock, + }, nil +} - rmnProxy, err := deployContract(e.Logger, chain, ab, - func(chain deployment.Chain) ContractDeploy[*rmn_proxy_contract.RMNProxyContract] { - rmnProxyAddr, tx, rmnProxy, err2 := rmn_proxy_contract.DeployRMNProxyContract( - chain.DeployerKey, - chain.Client, - rmnRemote.Address, - ) - return ContractDeploy[*rmn_proxy_contract.RMNProxyContract]{ - rmnProxyAddr, rmnProxy, tx, deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_0_0), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy rmnProxy", "err", err) - return ab, err +func DeployFeeTokensToChains(lggr logger.Logger, ab deployment.AddressBook, chains map[uint64]deployment.Chain) (map[uint64]FeeTokenContracts, error) { + feeTokenContractsByChain := make(map[uint64]FeeTokenContracts) + for _, chain := range chains { + feeTokenContracts, err := DeployFeeTokens(lggr, chain, ab) + if err != nil { + return nil, err + } + feeTokenContractsByChain[chain.Selector] = feeTokenContracts } - e.Logger.Infow("deployed rmnProxy", "addr", rmnProxy.Address) + return feeTokenContractsByChain, nil +} - // TODO: Need general configuration for using pre-existing weth9 - // link tokens. - weth9, err := deployContract(e.Logger, chain, ab, +// DeployFeeTokens deploys link and weth9. This is _usually_ for test environments only, +// real environments they tend to already exist, but sometimes we still have to deploy them to real chains. +func DeployFeeTokens(lggr logger.Logger, chain deployment.Chain, ab deployment.AddressBook) (FeeTokenContracts, error) { + weth9, err := deployContract(lggr, chain, ab, func(chain deployment.Chain) ContractDeploy[*weth9.WETH9] { weth9Addr, tx, weth9c, err2 := weth9.DeployWETH9( chain.DeployerKey, @@ -348,11 +358,12 @@ func DeployChainContracts( } }) if err != nil { - e.Logger.Errorw("Failed to deploy weth9", "err", err) - return ab, err + lggr.Errorw("Failed to deploy weth9", "err", err) + return FeeTokenContracts{}, err } + lggr.Infow("deployed weth9", "addr", weth9.Address) - linkToken, err := deployContract(e.Logger, chain, ab, + linkToken, err := deployContract(lggr, chain, ab, func(chain deployment.Chain) ContractDeploy[*burn_mint_erc677.BurnMintERC677] { linkTokenAddr, tx, linkToken, err2 := burn_mint_erc677.DeployBurnMintERC677( chain.DeployerKey, @@ -367,16 +378,90 @@ func DeployChainContracts( } }) if err != nil { - e.Logger.Errorw("Failed to deploy linkToken", "err", err) - return ab, err + lggr.Errorw("Failed to deploy linkToken", "err", err) + return FeeTokenContracts{}, err } + lggr.Infow("deployed linkToken", "addr", linkToken.Address) + return FeeTokenContracts{ + LinkToken: linkToken.Contract, + Weth9: weth9.Contract, + }, nil +} + +type FeeTokenContracts struct { + LinkToken *burn_mint_erc677.BurnMintERC677 + Weth9 *weth9.WETH9 +} + +func DeployChainContracts( + e deployment.Environment, + chain deployment.Chain, + ab deployment.AddressBook, + contractConfig FeeTokenContracts, + mcmsConfig MCMSConfig, +) error { + mcmsContracts, err := DeployMCMSContracts(e.Logger, chain, ab, mcmsConfig) + if err != nil { + return err + } + ccipReceiver, err := deployContract(e.Logger, chain, ab, + func(chain deployment.Chain) ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver] { + receiverAddr, tx, receiver, err2 := maybe_revert_message_receiver.DeployMaybeRevertMessageReceiver( + chain.DeployerKey, + chain.Client, + false, + ) + return ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver]{ + receiverAddr, receiver, tx, deployment.NewTypeAndVersion(CCIPReceiver, deployment.Version1_0_0), err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy receiver", "err", err) + return err + } + e.Logger.Infow("deployed receiver", "addr", ccipReceiver.Address) + + // TODO: Still waiting for RMNHome contracts etc. + rmnRemote, err := deployContract(e.Logger, chain, ab, + func(chain deployment.Chain) ContractDeploy[*rmn_remote.RMNRemote] { + rmnRemoteAddr, tx, rmnRemote, err2 := rmn_remote.DeployRMNRemote( + chain.DeployerKey, + chain.Client, + chain.Selector, + ) + return ContractDeploy[*rmn_remote.RMNRemote]{ + rmnRemoteAddr, rmnRemote, tx, deployment.NewTypeAndVersion(RMNRemote, deployment.Version1_6_0_dev), err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy RMNRemote", "err", err) + return err + } + e.Logger.Infow("deployed RMNRemote", "addr", rmnRemote.Address) + + rmnProxy, err := deployContract(e.Logger, chain, ab, + func(chain deployment.Chain) ContractDeploy[*rmn_proxy_contract.RMNProxyContract] { + rmnProxyAddr, tx, rmnProxy, err2 := rmn_proxy_contract.DeployRMNProxyContract( + chain.DeployerKey, + chain.Client, + rmnRemote.Address, + ) + return ContractDeploy[*rmn_proxy_contract.RMNProxyContract]{ + rmnProxyAddr, rmnProxy, tx, deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_0_0), err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy rmnProxy", "err", err) + return err + } + e.Logger.Infow("deployed rmnProxy", "addr", rmnProxy.Address) routerContract, err := deployContract(e.Logger, chain, ab, func(chain deployment.Chain) ContractDeploy[*router.Router] { routerAddr, tx, routerC, err2 := router.DeployRouter( chain.DeployerKey, chain.Client, - weth9.Address, + contractConfig.Weth9.Address(), rmnProxy.Address, ) return ContractDeploy[*router.Router]{ @@ -385,7 +470,7 @@ func DeployChainContracts( }) if err != nil { e.Logger.Errorw("Failed to deploy router", "err", err) - return ab, err + return err } e.Logger.Infow("deployed router", "addr", routerContract) @@ -394,7 +479,7 @@ func DeployChainContracts( routerAddr, tx, routerC, err2 := router.DeployRouter( chain.DeployerKey, chain.Client, - weth9.Address, + contractConfig.Weth9.Address(), rmnProxy.Address, ) return ContractDeploy[*router.Router]{ @@ -403,7 +488,7 @@ func DeployChainContracts( }) if err != nil { e.Logger.Errorw("Failed to deploy test router", "err", err) - return ab, err + return err } e.Logger.Infow("deployed test router", "addr", testRouterContract.Address) @@ -418,7 +503,7 @@ func DeployChainContracts( }) if err != nil { e.Logger.Errorw("Failed to deploy token admin registry", "err", err) - return ab, err + return err } e.Logger.Infow("deployed tokenAdminRegistry", "addr", tokenAdminRegistry) @@ -435,7 +520,7 @@ func DeployChainContracts( }) if err != nil { e.Logger.Errorw("Failed to deploy router", "err", err) - return ab, err + return err } feeQuoter, err := deployContract(e.Logger, chain, ab, @@ -445,21 +530,21 @@ func DeployChainContracts( chain.Client, fee_quoter.FeeQuoterStaticConfig{ MaxFeeJuelsPerMsg: big.NewInt(0).Mul(big.NewInt(2e2), big.NewInt(1e18)), - LinkToken: linkToken.Address, + LinkToken: contractConfig.LinkToken.Address(), StalenessThreshold: uint32(24 * 60 * 60), }, - []common.Address{timelock.Address}, // timelock should be able to update, ramps added after - []common.Address{weth9.Address, linkToken.Address}, // fee tokens + []common.Address{mcmsContracts.Timelock.Address}, // timelock should be able to update, ramps added after + []common.Address{contractConfig.Weth9.Address(), contractConfig.LinkToken.Address()}, // fee tokens []fee_quoter.FeeQuoterTokenPriceFeedUpdate{}, []fee_quoter.FeeQuoterTokenTransferFeeConfigArgs{}, // TODO: tokens []fee_quoter.FeeQuoterPremiumMultiplierWeiPerEthArgs{ { PremiumMultiplierWeiPerEth: 9e17, // 0.9 ETH - Token: linkToken.Address, + Token: contractConfig.LinkToken.Address(), }, { PremiumMultiplierWeiPerEth: 1e18, - Token: weth9.Address, + Token: contractConfig.Weth9.Address(), }, }, []fee_quoter.FeeQuoterDestChainConfigArgs{}, @@ -470,7 +555,7 @@ func DeployChainContracts( }) if err != nil { e.Logger.Errorw("Failed to deploy fee quoter", "err", err) - return ab, err + return err } onRamp, err := deployContract(e.Logger, chain, ab, @@ -496,7 +581,7 @@ func DeployChainContracts( }) if err != nil { e.Logger.Errorw("Failed to deploy onramp", "err", err) - return ab, err + return err } e.Logger.Infow("deployed onramp", "addr", onRamp.Address) @@ -525,7 +610,7 @@ func DeployChainContracts( }) if err != nil { e.Logger.Errorw("Failed to deploy offramp", "err", err) - return ab, err + return err } e.Logger.Infow("deployed offramp", "addr", offRamp) @@ -537,7 +622,7 @@ func DeployChainContracts( }) if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { e.Logger.Errorw("Failed to confirm fee quoter authorized caller update", "err", err) - return ab, err + return err } tx, err = nonceManager.Contract.ApplyAuthorizedCallerUpdates(chain.DeployerKey, nonce_manager.AuthorizedCallersAuthorizedCallerArgs{ @@ -545,7 +630,7 @@ func DeployChainContracts( }) if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { e.Logger.Errorw("Failed to update nonce manager with ramps", "err", err) - return ab, err + return err } - return ab, nil + return nil } diff --git a/integration-tests/deployment/ccip/deploy_home_chain.go b/integration-tests/deployment/ccip/deploy_home_chain.go index 5a625a1ef1c..abd1d13d0a9 100644 --- a/integration-tests/deployment/ccip/deploy_home_chain.go +++ b/integration-tests/deployment/ccip/deploy_home_chain.go @@ -70,8 +70,7 @@ func MustABIEncode(abiString string, args ...interface{}) []byte { return encoded } -func DeployCapReg(lggr logger.Logger, chain deployment.Chain) (deployment.AddressBook, common.Address, error) { - ab := deployment.NewMemoryAddressBook() +func DeployCapReg(lggr logger.Logger, ab deployment.AddressBook, chain deployment.Chain) (*ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) { capReg, err := deployContract(lggr, chain, ab, func(chain deployment.Chain) ContractDeploy[*capabilities_registry.CapabilitiesRegistry] { crAddr, tx, cr, err2 := capabilities_registry.DeployCapabilitiesRegistry( @@ -84,7 +83,7 @@ func DeployCapReg(lggr logger.Logger, chain deployment.Chain) (deployment.Addres }) if err != nil { lggr.Errorw("Failed to deploy capreg", "err", err) - return ab, common.Address{}, err + return nil, err } lggr.Infow("deployed capreg", "addr", capReg.Address) @@ -102,7 +101,7 @@ func DeployCapReg(lggr logger.Logger, chain deployment.Chain) (deployment.Addres }) if err != nil { lggr.Errorw("Failed to deploy ccip config", "err", err) - return ab, common.Address{}, err + return nil, err } lggr.Infow("deployed ccip config", "addr", ccipConfig.Address) @@ -117,7 +116,7 @@ func DeployCapReg(lggr logger.Logger, chain deployment.Chain) (deployment.Addres }) if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { lggr.Errorw("Failed to add capabilities", "err", err) - return ab, common.Address{}, err + return nil, err } // TODO: Just one for testing. tx, err = capReg.Contract.AddNodeOperators(chain.DeployerKey, []capabilities_registry.CapabilitiesRegistryNodeOperator{ @@ -128,9 +127,9 @@ func DeployCapReg(lggr logger.Logger, chain deployment.Chain) (deployment.Addres }) if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { lggr.Errorw("Failed to add node operators", "err", err) - return ab, common.Address{}, err + return nil, err } - return ab, capReg.Address, nil + return capReg, nil } func AddNodes( diff --git a/integration-tests/deployment/ccip/deploy_test.go b/integration-tests/deployment/ccip/deploy_test.go index a278f29842e..f467cfe82b6 100644 --- a/integration-tests/deployment/ccip/deploy_test.go +++ b/integration-tests/deployment/ccip/deploy_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink/integration-tests/deployment" "github.com/smartcontractkit/chainlink/integration-tests/deployment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -21,32 +22,25 @@ func TestDeployCCIPContracts(t *testing.T) { Nodes: 4, }) // Deploy all the CCIP contracts. - homeChain := e.AllChainSelectors()[HomeChainIndex] - addressBook, _, err := DeployCapReg(lggr, e.Chains[homeChain]) - require.NoError(t, err) - s, err := LoadOnchainState(e, addressBook) - require.NoError(t, err) - - feedChain := e.AllChainSelectors()[FeedChainIndex] - feedAddresses, _, err := DeployFeeds(lggr, e.Chains[feedChain]) - require.NoError(t, err) - - // Merge the feed addresses into the address book. - require.NoError(t, addressBook.Merge(feedAddresses)) + ab := deployment.NewMemoryAddressBook() + homeChainSel, feedChainSel := allocateCCIPChainSelectors(e.Chains) + feeTokenContracts, _ := DeployTestContracts(t, lggr, ab, homeChainSel, feedChainSel, e.Chains) // Load the state after deploying the cap reg and feeds. - homeAndFeedStates, err := LoadOnchainState(e, addressBook) + s, err := LoadOnchainState(e, ab) require.NoError(t, err) - require.NotNil(t, s.Chains[homeChain].CapabilityRegistry) - require.NotNil(t, s.Chains[homeChain].CCIPConfig) - require.NotNil(t, homeAndFeedStates.Chains[feedChain].USDFeeds) + require.NotNil(t, s.Chains[homeChainSel].CapabilityRegistry) + require.NotNil(t, s.Chains[homeChainSel].CCIPConfig) + require.NotNil(t, s.Chains[feedChainSel].USDFeeds) - ab, err := DeployCCIPContracts(e, DeployCCIPContractConfig{ - HomeChainSel: homeChain, - FeedChainSel: feedChain, - ChainsToDeploy: e.AllChainSelectors(), - TokenConfig: NewTokenConfig(), - CCIPOnChainState: homeAndFeedStates, + err = DeployCCIPContracts(e, ab, DeployCCIPContractConfig{ + HomeChainSel: homeChainSel, + FeedChainSel: feedChainSel, + ChainsToDeploy: e.AllChainSelectors(), + TokenConfig: NewTokenConfig(), + CapabilityRegistry: s.Chains[homeChainSel].CapabilityRegistry.Address(), + FeeTokenContracts: feeTokenContracts, + MCMSConfig: NewTestMCMSConfig(t, e), }) require.NoError(t, err) state, err := LoadOnchainState(e, ab) diff --git a/integration-tests/deployment/ccip/propose.go b/integration-tests/deployment/ccip/propose.go index df4fe90a76a..9738ac699fc 100644 --- a/integration-tests/deployment/ccip/propose.go +++ b/integration-tests/deployment/ccip/propose.go @@ -3,13 +3,14 @@ package ccipdeployment import ( "bytes" "context" + "crypto/ecdsa" "math/big" "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" @@ -19,11 +20,41 @@ import ( "github.com/smartcontractkit/chainlink/integration-tests/deployment" ) -// TODO: Pull up to deploy -func SimTransactOpts() *bind.TransactOpts { - return &bind.TransactOpts{Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { - return transaction, nil - }, From: common.HexToAddress("0x0"), NoSend: true, GasLimit: 1_000_000} +var ( + TestXXXMCMSSigner *ecdsa.PrivateKey +) + +func init() { + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + TestXXXMCMSSigner = key +} + +func SingleGroupMCMS(t *testing.T) config.Config { + publicKey := TestXXXMCMSSigner.Public().(*ecdsa.PublicKey) + // Convert the public key to an Ethereum address + address := crypto.PubkeyToAddress(*publicKey) + c, err := config.NewConfig(1, []common.Address{address}, []config.Config{}) + require.NoError(t, err) + return *c +} + +func NewTestMCMSConfig(t *testing.T, e deployment.Environment) MCMSConfig { + c := SingleGroupMCMS(t) + // All deployer keys can execute. + var executors []common.Address + for _, chain := range e.Chains { + executors = append(executors, chain.DeployerKey.From) + } + return MCMSConfig{ + Admin: c, + Bypasser: c, + Canceller: c, + Executors: executors, + Proposer: c, + } } func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.MCMSWithTimelockProposal) *mcms.Executor { @@ -106,21 +137,21 @@ func GenerateAcceptOwnershipProposal( timelockAddresses := make(map[mcms.ChainIdentifier]common.Address) for _, sel := range chains { chain, _ := chainsel.ChainBySelector(sel) - acceptOnRamp, err := state.Chains[sel].OnRamp.AcceptOwnership(SimTransactOpts()) + acceptOnRamp, err := state.Chains[sel].OnRamp.AcceptOwnership(deployment.SimTransactOpts()) if err != nil { return nil, err } - acceptFeeQuoter, err := state.Chains[sel].FeeQuoter.AcceptOwnership(SimTransactOpts()) + acceptFeeQuoter, err := state.Chains[sel].FeeQuoter.AcceptOwnership(deployment.SimTransactOpts()) if err != nil { return nil, err } chainSel := mcms.ChainIdentifier(chain.Selector) - opCount, err := state.Chains[sel].Mcm.GetOpCount(nil) + opCount, err := state.Chains[sel].ProposerMcm.GetOpCount(nil) if err != nil { return nil, err } metaDataPerChain[chainSel] = mcms.ChainMetadata{ - MCMAddress: state.Chains[sel].Mcm.Address(), + MCMAddress: state.Chains[sel].ProposerMcm.Address(), StartingOpCount: opCount.Uint64(), } timelockAddresses[chainSel] = state.Chains[sel].Timelock.Address() @@ -141,22 +172,22 @@ func GenerateAcceptOwnershipProposal( }) } - acceptCR, err := state.Chains[homeChain].CapabilityRegistry.AcceptOwnership(SimTransactOpts()) + acceptCR, err := state.Chains[homeChain].CapabilityRegistry.AcceptOwnership(deployment.SimTransactOpts()) if err != nil { return nil, err } - acceptCCIPConfig, err := state.Chains[homeChain].CCIPConfig.AcceptOwnership(SimTransactOpts()) + acceptCCIPConfig, err := state.Chains[homeChain].CCIPConfig.AcceptOwnership(deployment.SimTransactOpts()) if err != nil { return nil, err } homeChainID := mcms.ChainIdentifier(homeChain) - opCount, err := state.Chains[homeChain].Mcm.GetOpCount(nil) + opCount, err := state.Chains[homeChain].ProposerMcm.GetOpCount(nil) if err != nil { return nil, err } metaDataPerChain[homeChainID] = mcms.ChainMetadata{ StartingOpCount: opCount.Uint64(), - MCMAddress: state.Chains[homeChain].Mcm.Address(), + MCMAddress: state.Chains[homeChain].ProposerMcm.Address(), } timelockAddresses[homeChainID] = state.Chains[homeChain].Timelock.Address() batches = append(batches, timelock.BatchChainOperation{ diff --git a/integration-tests/deployment/ccip/state.go b/integration-tests/deployment/ccip/state.go index 2f6e6b7f69e..14ff794b6d0 100644 --- a/integration-tests/deployment/ccip/state.go +++ b/integration-tests/deployment/ccip/state.go @@ -63,7 +63,10 @@ type CCIPChainState struct { // Note we only expect one of these (on the home chain) CapabilityRegistry *capabilities_registry.CapabilitiesRegistry CCIPConfig *ccip_config.CCIPConfig - Mcm *owner_wrappers.ManyChainMultiSig + AdminMcm *owner_wrappers.ManyChainMultiSig + BypasserMcm *owner_wrappers.ManyChainMultiSig + CancellerMcm *owner_wrappers.ManyChainMultiSig + ProposerMcm *owner_wrappers.ManyChainMultiSig Timelock *owner_wrappers.RBACTimelock // Test contracts @@ -236,12 +239,30 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, err } state.Timelock = tl - case deployment.NewTypeAndVersion(ManyChainMultisig, deployment.Version1_0_0).String(): + case deployment.NewTypeAndVersion(AdminManyChainMultisig, deployment.Version1_0_0).String(): mcms, err := owner_wrappers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) if err != nil { return state, err } - state.Mcm = mcms + state.AdminMcm = mcms + case deployment.NewTypeAndVersion(ProposerManyChainMultisig, deployment.Version1_0_0).String(): + mcms, err := owner_wrappers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + state.ProposerMcm = mcms + case deployment.NewTypeAndVersion(BypasserManyChainMultisig, deployment.Version1_0_0).String(): + mcms, err := owner_wrappers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + state.BypasserMcm = mcms + case deployment.NewTypeAndVersion(CancellerManyChainMultisig, deployment.Version1_0_0).String(): + mcms, err := owner_wrappers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + state.CancellerMcm = mcms case deployment.NewTypeAndVersion(CapabilitiesRegistry, deployment.Version1_0_0).String(): cr, err := capabilities_registry.NewCapabilitiesRegistry(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/integration-tests/deployment/ccip/test_helpers.go b/integration-tests/deployment/ccip/test_helpers.go index 59eecae4f29..f4c80f095bc 100644 --- a/integration-tests/deployment/ccip/test_helpers.go +++ b/integration-tests/deployment/ccip/test_helpers.go @@ -61,11 +61,12 @@ func Context(tb testing.TB) context.Context { } type DeployedEnv struct { - Ab deployment.AddressBook - Env deployment.Environment - HomeChainSel uint64 - FeedChainSel uint64 - ReplayBlocks map[uint64]uint64 + Env deployment.Environment + Ab deployment.AddressBook + HomeChainSel uint64 + FeedChainSel uint64 + ReplayBlocks map[uint64]uint64 + FeeTokenContracts map[uint64]FeeTokenContracts } func (e *DeployedEnv) SetupJobs(t *testing.T) { @@ -100,17 +101,24 @@ func ReplayLogs(t *testing.T, oc deployment.OffchainClient, replayBlocks map[uin } } -func SetUpHomeAndFeedChains(t *testing.T, lggr logger.Logger, homeChainSel, feedChainSel uint64, chains map[uint64]deployment.Chain) (deployment.AddressBook, deployment.CapabilityRegistryConfig) { - homeChainEVM, _ := chainsel.ChainIdFromSelector(homeChainSel) - ab, capReg, err := DeployCapReg(lggr, chains[homeChainSel]) +func DeployTestContracts(t *testing.T, + lggr logger.Logger, + ab deployment.AddressBook, + homeChainSel, + feedChainSel uint64, + chains map[uint64]deployment.Chain, +) (map[uint64]FeeTokenContracts, deployment.CapabilityRegistryConfig) { + capReg, err := DeployCapReg(lggr, ab, chains[homeChainSel]) require.NoError(t, err) - - feedAb, _, err := DeployFeeds(lggr, chains[feedChainSel]) + _, err = DeployFeeds(lggr, ab, chains[feedChainSel]) + require.NoError(t, err) + feeTokenContracts, err := DeployFeeTokensToChains(lggr, ab, chains) + require.NoError(t, err) + evmChainID, err := chainsel.ChainIdFromSelector(homeChainSel) require.NoError(t, err) - require.NoError(t, ab.Merge(feedAb)) - return ab, deployment.CapabilityRegistryConfig{ - EVMChainID: homeChainEVM, - Contract: capReg, + return feeTokenContracts, deployment.CapabilityRegistryConfig{ + EVMChainID: evmChainID, + Contract: capReg.Address, } } @@ -127,12 +135,7 @@ func LatestBlocksByChain(ctx context.Context, chains map[uint64]deployment.Chain return latestBlocks, nil } -// NewMemoryEnvironment creates a new CCIP environment -// with capreg, feeds and nodes set up. -func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int) DeployedEnv { - require.GreaterOrEqual(t, numChains, 2, "numChains must be at least 2 for home and feed chains") - ctx := testcontext.Get(t) - chains := memory.NewMemoryChains(t, numChains) +func allocateCCIPChainSelectors(chains map[uint64]deployment.Chain) (homeChainSel uint64, feeChainSel uint64) { // Lower chainSel is home chain. var chainSels []uint64 // Say first chain is home chain. @@ -143,13 +146,22 @@ func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int) Deplo return chainSels[i] < chainSels[j] }) // Take lowest for determinism. - homeChainSel := chainSels[HomeChainIndex] - feedSel := chainSels[FeedChainIndex] + return chainSels[HomeChainIndex], chainSels[FeedChainIndex] +} + +// NewMemoryEnvironment creates a new CCIP environment +// with capreg, fee tokens, feeds and nodes set up. +func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int) DeployedEnv { + require.GreaterOrEqual(t, numChains, 2, "numChains must be at least 2 for home and feed chains") + ctx := testcontext.Get(t) + chains := memory.NewMemoryChains(t, numChains) + homeChainSel, feedSel := allocateCCIPChainSelectors(chains) replayBlocks, err := LatestBlocksByChain(ctx, chains) require.NoError(t, err) - ab, capReg := SetUpHomeAndFeedChains(t, lggr, homeChainSel, feedSel, chains) - nodes := memory.NewNodes(t, zapcore.InfoLevel, chains, 4, 1, capReg) + ab := deployment.NewMemoryAddressBook() + feeTokenContracts, crConfig := DeployTestContracts(t, lggr, ab, homeChainSel, feedSel, chains) + nodes := memory.NewNodes(t, zapcore.InfoLevel, chains, 4, 1, crConfig) for _, node := range nodes { require.NoError(t, node.App.Start(ctx)) t.Cleanup(func() { @@ -159,11 +171,12 @@ func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int) Deplo e := memory.NewMemoryEnvironmentFromChainsNodes(t, lggr, chains, nodes) return DeployedEnv{ - Ab: ab, - Env: e, - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ReplayBlocks: replayBlocks, + Ab: ab, + Env: e, + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ReplayBlocks: replayBlocks, + FeeTokenContracts: feeTokenContracts, } } @@ -255,10 +268,14 @@ func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedEnv { require.NotEmpty(t, feedSel, "feedSel should not be empty") replayBlocks, err := LatestBlocksByChain(ctx, chains) require.NoError(t, err) - ab, capReg := SetUpHomeAndFeedChains(t, lggr, homeChainSel, feedSel, chains) + + ab := deployment.NewMemoryAddressBook() + feeContracts, crConfig := DeployTestContracts(t, lggr, ab, homeChainSel, feedSel, chains) // start the chainlink nodes with the CR address - err = devenv.StartChainlinkNodes(t, envConfig, capReg, testEnv, cfg) + err = devenv.StartChainlinkNodes(t, envConfig, + crConfig, + testEnv, cfg) require.NoError(t, err) e, don, err := devenv.NewEnvironment(ctx, lggr, *envConfig) @@ -269,11 +286,12 @@ func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedEnv { devenv.FundNodes(t, zeroLogLggr, testEnv, cfg, don.PluginNodes()) return DeployedEnv{ - Ab: ab, - Env: *e, - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ReplayBlocks: replayBlocks, + Ab: ab, + Env: *e, + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ReplayBlocks: replayBlocks, + FeeTokenContracts: feeContracts, } } @@ -313,8 +331,7 @@ var ( } ) -func DeployFeeds(lggr logger.Logger, chain deployment.Chain) (deployment.AddressBook, map[string]common.Address, error) { - ab := deployment.NewMemoryAddressBook() +func DeployFeeds(lggr logger.Logger, ab deployment.AddressBook, chain deployment.Chain) (map[string]common.Address, error) { linkTV := deployment.NewTypeAndVersion(PriceFeed, deployment.Version1_0_0) mockLinkFeed, err := deployContract(lggr, chain, ab, func(chain deployment.Chain) ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface] { @@ -333,7 +350,7 @@ func DeployFeeds(lggr logger.Logger, chain deployment.Chain) (deployment.Address if err != nil { lggr.Errorw("Failed to deploy link feed", "err", err) - return ab, nil, err + return nil, err } lggr.Infow("deployed mockLinkFeed", "addr", mockLinkFeed.Address) @@ -341,16 +358,16 @@ func DeployFeeds(lggr logger.Logger, chain deployment.Chain) (deployment.Address desc, err := mockLinkFeed.Contract.Description(&bind.CallOpts{}) if err != nil { lggr.Errorw("Failed to get description", "err", err) - return ab, nil, err + return nil, err } if desc != MockLinkAggregatorDescription { lggr.Errorw("Unexpected description for Link token", "desc", desc) - return ab, nil, fmt.Errorf("unexpected description: %s", desc) + return nil, fmt.Errorf("unexpected description: %s", desc) } tvToAddress := map[string]common.Address{ desc: mockLinkFeed.Address, } - return ab, tvToAddress, nil + return tvToAddress, nil } diff --git a/integration-tests/deployment/ccip/token_info.go b/integration-tests/deployment/ccip/token_info.go index 1ea09949bc4..a7dae7f0264 100644 --- a/integration-tests/deployment/ccip/token_info.go +++ b/integration-tests/deployment/ccip/token_info.go @@ -2,6 +2,7 @@ package ccipdeployment import ( "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -30,14 +31,14 @@ func (tc *TokenConfig) UpsertTokenInfo( // GetTokenInfo Adds mapping between dest chain tokens and their respective aggregators on feed chain. func (tc *TokenConfig) GetTokenInfo( lggr logger.Logger, - destState CCIPChainState, + linkToken *burn_mint_erc677.BurnMintERC677, ) map[ocrtypes.Account]pluginconfig.TokenInfo { tokenToAggregate := make(map[ocrtypes.Account]pluginconfig.TokenInfo) if _, ok := tc.TokenSymbolToInfo[LinkSymbol]; !ok { lggr.Debugw("Link aggregator not found, deploy without mapping link token") } else { lggr.Debugw("Mapping LinkToken to Link aggregator") - acc := ocrtypes.Account(destState.LinkToken.Address().String()) + acc := ocrtypes.Account(linkToken.Address().String()) tokenToAggregate[acc] = tc.TokenSymbolToInfo[LinkSymbol] } diff --git a/integration-tests/deployment/helpers.go b/integration-tests/deployment/helpers.go index 5e81eadd39d..c691d1a1ec1 100644 --- a/integration-tests/deployment/helpers.go +++ b/integration-tests/deployment/helpers.go @@ -16,6 +16,13 @@ import ( "github.com/pkg/errors" ) +// SimTransactOpts is useful to generate just the calldata for a given gethwrapper method. +func SimTransactOpts() *bind.TransactOpts { + return &bind.TransactOpts{Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { + return transaction, nil + }, From: common.HexToAddress("0x0"), NoSend: true, GasLimit: 1_000_000} +} + func GetErrorReasonFromTx(client bind.ContractBackend, from common.Address, tx types.Transaction, receipt *types.Receipt) (string, error) { call := ethereum.CallMsg{ From: from, diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index 8e80985620d..6108d3889d6 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -36,16 +36,18 @@ func TestInitialDeployOnLocal(t *testing.T) { ) // Apply migration output, err := changeset.InitialDeployChangeSet(tenv.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: tokenConfig, - // Capreg/config and feeds already exist. - CCIPOnChainState: state, + HomeChainSel: tenv.HomeChainSel, + FeedChainSel: tenv.FeedChainSel, + ChainsToDeploy: tenv.Env.AllChainSelectors(), + TokenConfig: tokenConfig, + MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), + CapabilityRegistry: state.Chains[tenv.HomeChainSel].CapabilityRegistry.Address(), + FeeTokenContracts: tenv.FeeTokenContracts, }) require.NoError(t, err) + require.NoError(t, tenv.Ab.Merge(output.AddressBook)) // Get new state after migration. - state, err = ccdeploy.LoadOnchainState(e, output.AddressBook) + state, err = ccdeploy.LoadOnchainState(e, tenv.Ab) require.NoError(t, err) // Ensure capreg logs are up to date. From 620c5129ca804ef4a3d6dfc0701847519132d4b0 Mon Sep 17 00:00:00 2001 From: Jordan Krage Date: Thu, 3 Oct 2024 11:47:14 -0500 Subject: [PATCH 24/28] remove ineffectual replaces; consolidate blocks (#14640) --- core/scripts/go.mod | 7 ++--- dashboard-lib/go.mod | 7 +++-- go.mod | 8 +++-- integration-tests/go.mod | 13 +++----- integration-tests/load/go.mod | 58 ++++++----------------------------- integration-tests/load/go.sum | 46 ++++++++++++++------------- 6 files changed, 48 insertions(+), 91 deletions(-) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 0c3453fcf8e..69aba56208e 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -373,11 +373,8 @@ require ( ) replace ( - // until merged upstream: https://github.com/omissis/go-jsonschema/pull/264 - github.com/atombender/go-jsonschema => github.com/nolag/go-jsonschema v0.16.0-rtinianov - // replicating the replace directive on cosmos SDK github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 -) -replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 + github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 +) diff --git a/dashboard-lib/go.mod b/dashboard-lib/go.mod index a26c6ab454f..f67a618f44d 100644 --- a/dashboard-lib/go.mod +++ b/dashboard-lib/go.mod @@ -9,8 +9,6 @@ require ( github.com/rs/zerolog v1.32.0 ) -replace github.com/grafana/grafana-foundation-sdk/go => github.com/grafana/grafana-foundation-sdk/go v0.0.0-20240314112857-a7c9c6d0044c - require ( github.com/K-Phoen/sdk v0.12.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -24,4 +22,7 @@ require ( golang.org/x/sys v0.24.0 // indirect ) -replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 +replace ( + github.com/grafana/grafana-foundation-sdk/go => github.com/grafana/grafana-foundation-sdk/go v0.0.0-20240314112857-a7c9c6d0044c + github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 +) diff --git a/go.mod b/go.mod index e7f1b2c1761..ec4b5cb7b87 100644 --- a/go.mod +++ b/go.mod @@ -367,7 +367,9 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -// replicating the replace directive on cosmos SDK -replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 +replace ( + // replicating the replace directive on cosmos SDK + github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 -replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 + github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 +) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 7c0dad360f7..6f4f543e31b 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -522,21 +522,16 @@ require ( exclude github.com/chaos-mesh/chaos-mesh/api/v1alpha1 v0.0.0-20220226050744-799408773657 replace ( - // until merged upstream: https://github.com/omissis/go-jsonschema/pull/264 - github.com/atombender/go-jsonschema => github.com/nolag/go-jsonschema v0.16.0-rtinianov - github.com/go-kit/log => github.com/go-kit/log v0.2.1 // replicating the replace directive on cosmos SDK github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 + github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/common => github.com/prometheus/common v0.42.0 + // type func(a Label, b Label) bool of func(a, b Label) bool {…} does not match inferred type func(a Label, b Label) int for func(a E, b E) int github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.47.2-0.20231010075449-4b9c19fe5510 -) -replace ( - github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.14.0 - github.com/prometheus/common => github.com/prometheus/common v0.42.0 + github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 ) - -replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index f0b8fccdcd6..89c27daf3b2 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -27,6 +27,8 @@ require ( go.uber.org/ratelimit v0.3.0 ) +require github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.5 // indirect cosmossdk.io/api v0.3.1 // indirect @@ -156,7 +158,6 @@ require ( github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/esote/minmaxheap v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect @@ -164,7 +165,6 @@ require ( github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gagliardetto/binary v0.7.7 // indirect @@ -493,11 +493,11 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.31.0 // indirect + k8s.io/api v0.31.1 // indirect k8s.io/apiextensions-apiserver v0.31.0 // indirect - k8s.io/apimachinery v0.31.0 // indirect + k8s.io/apimachinery v0.31.1 // indirect k8s.io/cli-runtime v0.31.0 // indirect - k8s.io/client-go v1.5.2 // indirect + k8s.io/client-go v0.31.1 // indirect k8s.io/component-base v0.31.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f // indirect @@ -518,57 +518,17 @@ require ( exclude github.com/hashicorp/consul v1.2.1 replace ( - // until merged upstream: https://github.com/omissis/go-jsonschema/pull/264 - github.com/atombender/go-jsonschema => github.com/nolag/go-jsonschema v0.16.0-rtinianov - + github.com/chaos-mesh/chaos-mesh/api => github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240709130330-9f4feec7553f github.com/go-kit/log => github.com/go-kit/log v0.2.1 // replicating the replace directive on cosmos SDK github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - // type func(a Label, b Label) bool of func(a, b Label) bool {…} does not match inferred type func(a Label, b Label) int for func(a E, b E) int - github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.47.2-0.20231010075449-4b9c19fe5510 -) - -replace ( github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.14.0 github.com/prometheus/common => github.com/prometheus/common v0.42.0 -) -replace github.com/chaos-mesh/chaos-mesh/api => github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240709130330-9f4feec7553f + // type func(a Label, b Label) bool of func(a, b Label) bool {…} does not match inferred type func(a Label, b Label) int for func(a E, b E) int + github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.47.2-0.20231010075449-4b9c19fe5510 -replace ( - k8s.io/api => k8s.io/api v0.28.2 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.28.2 - k8s.io/apimachinery => k8s.io/apimachinery v0.28.2 - k8s.io/apiserver => k8s.io/apiserver v0.28.2 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.28.2 - k8s.io/client-go => k8s.io/client-go v0.28.2 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.28.2 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.28.2 - k8s.io/code-generator => k8s.io/code-generator v0.28.2 - k8s.io/component-base => k8s.io/component-base v0.28.2 - k8s.io/component-helpers => k8s.io/component-helpers v0.28.2 - k8s.io/controller-manager => k8s.io/controller-manager v0.28.2 - k8s.io/cri-api => k8s.io/cri-api v0.28.2 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.28.2 - k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.28.2 - k8s.io/endpointslice => k8s.io/endpointslice v0.28.2 - k8s.io/kms => k8s.io/kms v0.28.2 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.28.2 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.28.2 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.28.2 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.28.2 - k8s.io/kubectl => k8s.io/kubectl v0.28.2 - k8s.io/kubelet => k8s.io/kubelet v0.28.2 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.28.2 - k8s.io/metrics => k8s.io/metrics v0.28.2 - k8s.io/mount-utils => k8s.io/mount-utils v0.28.2 - k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.28.2 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.28.2 - k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.28.2 - k8s.io/sample-controller => k8s.io/sample-controller v0.28.2 - sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.16.2 + github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 ) - -replace github.com/sourcegraph/sourcegraph/lib => github.com/sourcegraph/sourcegraph-public-snapshot/lib v0.0.0-20240822153003-c864f15af264 diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index d401b11553c..64b1eb29e33 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -509,8 +509,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= -github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -569,8 +567,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -1088,6 +1086,8 @@ github.com/linode/linodego v1.19.0 h1:n4WJrcr9+30e9JGZ6DI0nZbm5SdAj1kSwvvt/998YU github.com/linode/linodego v1.19.0/go.mod h1:XZFR+yJ9mm2kwf6itZ6SCpu+6w3KnIevV0Uu5HNWJgQ= github.com/linxGnu/grocksdb v1.7.16 h1:Q2co1xrpdkr5Hx3Fp+f+f7fRGhQFQhvi/+226dtLmA8= github.com/linxGnu/grocksdb v1.7.16/go.mod h1:JkS7pl5qWpGpuVb3bPqTz8nC12X3YtPZT+Xq7+QfQo4= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -1210,6 +1210,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1aMrNhpgxHmow66XQtm0aQLY0AE= @@ -1229,8 +1231,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= -github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= @@ -2197,24 +2199,24 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= -k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= -k8s.io/apiextensions-apiserver v0.28.2 h1:J6/QRWIKV2/HwBhHRVITMLYoypCoPY1ftigDM0Kn+QU= -k8s.io/apiextensions-apiserver v0.28.2/go.mod h1:5tnkxLGa9nefefYzWuAlWZ7RZYuN/765Au8cWLA6SRg= -k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= -k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= -k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk= -k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA= -k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= -k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= -k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E= -k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= +k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/cli-runtime v0.31.0 h1:V2Q1gj1u3/WfhD475HBQrIYsoryg/LrhhK4RwpN+DhA= +k8s.io/cli-runtime v0.31.0/go.mod h1:vg3H94wsubuvWfSmStDbekvbla5vFGC+zLWqcf+bGDw= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= +k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f h1:2sXuKesAYbRHxL3aE2PN6zX/gcJr22cjrsej+W784Tc= k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= -k8s.io/kubectl v0.28.2 h1:fOWOtU6S0smdNjG1PB9WFbqEIMlkzU5ahyHkc7ESHgM= -k8s.io/kubectl v0.28.2/go.mod h1:6EQWTPySF1fn7yKoQZHYf9TPwIl2AygHEcJoxFekr64= +k8s.io/kubectl v0.31.0 h1:kANwAAPVY02r4U4jARP/C+Q1sssCcN/1p9Nk+7BQKVg= +k8s.io/kubectl v0.31.0/go.mod h1:pB47hhFypGsaHAPjlwrNbvhXgmuAr01ZBvAIIUaI8d4= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= @@ -2228,8 +2230,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= -sigs.k8s.io/controller-runtime v0.16.2 h1:mwXAVuEk3EQf478PQwQ48zGOXvW27UJc8NHktQVuIPU= -sigs.k8s.io/controller-runtime v0.16.2/go.mod h1:vpMu3LpI5sYWtujJOa2uPK61nB5rbwlN7BAB8aSLvGU= +sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= +sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= From 8f90746ee386ecde446826bc1200a3289d645dc2 Mon Sep 17 00:00:00 2001 From: Cedric Date: Thu, 3 Oct 2024 18:47:49 +0100 Subject: [PATCH 25/28] [chore] Update codeowners (#14650) * [chore] Update codeowners - Update integration-tests go.mod/sum so it needs the same codeowners as other mod/sum files. - Update keystone specific repositories so they are codeowned by keystone * Update .github/CODEOWNERS Co-authored-by: chainchad <96362174+chainchad@users.noreply.github.com> --------- Co-authored-by: chainchad <96362174+chainchad@users.noreply.github.com> --- .github/CODEOWNERS | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 49a0e35e61d..c72ad6d4dd2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,6 +13,7 @@ /core/services/directrequest @smartcontractkit/keepers /core/services/feeds @smartcontractkit/op-core @eutopian @yevshev /core/services/synchronization/telem @smartcontractkit/realtime +/core/capabilities/ @smartcontractkit/keystone /core/capabilities/ccip @smartcontractkit/ccip-offchain # To be deprecated in Chainlink V3 @@ -51,6 +52,12 @@ core/services/relay/evm/functions @smartcontractkit/functions core/scripts/functions @smartcontractkit/functions core/scripts/gateway @smartcontractkit/functions +# Keystone +/core/services/registrysyncer @smartcontractkit/keystone +/core/services/workflows @smartcontractkit/keystone +/core/services/standardcapabilities @smartcontractkit/keystone +/core/scripts/keystone @smartcontractkit/keystone + # Contracts /contracts/ @RensR @matYang @RayXpub @elatoskinas @@ -119,8 +126,8 @@ contracts/package.json @smartcontractkit/prodsec-public contracts/pnpm.lock @smartcontractkit/prodsec-public go.mod @smartcontractkit/prodsec-public @smartcontractkit/releng @smartcontractkit/foundations go.sum @smartcontractkit/prodsec-public @smartcontractkit/releng @smartcontractkit/foundations -integration-tests/go.mod @smartcontractkit/prodsec-public -integration-tests/go.sum @smartcontractkit/prodsec-public +integration-tests/go.mod @smartcontractkit/prodsec-public @smartcontractkit/test-tooling-team @smartcontractkit/foundations +integration-tests/go.sum @smartcontractkit/prodsec-public @smartcontractkit/test-tooling-team @smartcontractkit/foundations flake.nix @smartcontractkit/prodsec-public flake.lock @smartcontractkit/prodsec-public From 0dba7fb6723942269d48409f31599de912b0b087 Mon Sep 17 00:00:00 2001 From: Rens Rooimans Date: Thu, 3 Oct 2024 20:27:35 +0200 Subject: [PATCH 26/28] bump npm packages (#14614) * bump npm packages * prettier --- contracts/package.json | 24 +- contracts/pnpm-lock.yaml | 606 ++++++++---------- .../v0.8/functions/dev/v1_X/ocr/OCR2Base.sol | 6 +- .../v0.8/functions/v1_0_0/ocr/OCR2Base.sol | 6 +- .../v0.8/functions/v1_1_0/ocr/OCR2Base.sol | 6 +- .../v0.8/functions/v1_3_0/ocr/OCR2Base.sol | 6 +- .../src/v0.8/keystone/OCR3Capability.sol | 6 +- .../v0.8/liquiditymanager/ocr/OCR3Base.sol | 6 +- contracts/src/v0.8/shared/ocr2/OCR2Base.sol | 6 +- .../shared/test/testhelpers/GasConsumer.sol | 4 +- 10 files changed, 288 insertions(+), 388 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index d80e94159a9..a421925e40b 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -42,25 +42,25 @@ "@ethersproject/contracts": "~5.7.0", "@ethersproject/providers": "~5.7.2", "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", - "@nomicfoundation/hardhat-ethers": "^3.0.6", - "@nomicfoundation/hardhat-network-helpers": "^1.0.11", - "@nomicfoundation/hardhat-verify": "^2.0.9", + "@nomicfoundation/hardhat-ethers": "^3.0.8", + "@nomicfoundation/hardhat-network-helpers": "^1.0.12", + "@nomicfoundation/hardhat-verify": "^2.0.11", "@typechain/ethers-v5": "^7.2.0", "@typechain/hardhat": "^7.0.0", "@types/cbor": "~5.0.1", - "@types/chai": "^4.3.17", + "@types/chai": "^4.3.20", "@types/debug": "^4.1.12", "@types/deep-equal-in-any-order": "^1.0.3", - "@types/mocha": "^10.0.7", - "@types/node": "^20.14.15", + "@types/mocha": "^10.0.8", + "@types/node": "^20.16.10", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "abi-to-sol": "^0.6.6", "cbor": "^5.2.0", "chai": "^4.5.0", - "debug": "^4.3.6", + "debug": "^4.3.7", "deep-equal-in-any-order": "^2.0.6", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", "ethers": "~5.7.2", @@ -69,19 +69,19 @@ "hardhat-ignore-warnings": "^0.2.6", "moment": "^2.30.1", "prettier": "^3.3.3", - "prettier-plugin-solidity": "^1.3.1", - "solhint": "^5.0.1", + "prettier-plugin-solidity": "^1.4.1", + "solhint": "^5.0.3", "solhint-plugin-chainlink-solidity": "git+https://github.com/smartcontractkit/chainlink-solhint-rules.git#v1.2.1", "solhint-plugin-prettier": "^0.1.0", "ts-node": "^10.9.2", "typechain": "^8.2.1", - "typescript": "^5.5.4" + "typescript": "^5.6.2" }, "dependencies": { "@arbitrum/nitro-contracts": "1.1.1", "@arbitrum/token-bridge-contracts": "1.1.2", "@changesets/changelog-github": "^0.5.0", - "@changesets/cli": "~2.27.7", + "@changesets/cli": "~2.27.8", "@eth-optimism/contracts": "0.6.0", "@openzeppelin/contracts": "4.9.3", "@openzeppelin/contracts-upgradeable": "4.9.3", diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index f087dd7cd47..20fcd2e2eed 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: ^0.5.0 version: 0.5.0 '@changesets/cli': - specifier: ~2.27.7 - version: 2.27.7 + specifier: ~2.27.8 + version: 2.27.8 '@eth-optimism/contracts': specifier: 0.6.0 version: 0.6.0(ethers@5.7.2) @@ -59,28 +59,28 @@ importers: version: 5.7.2 '@nomicfoundation/hardhat-chai-matchers': specifier: ^1.0.6 - version: 1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)))(chai@4.5.0)(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) + version: 1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(chai@4.5.0)(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@nomicfoundation/hardhat-ethers': - specifier: ^3.0.6 - version: 3.0.6(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) + specifier: ^3.0.8 + version: 3.0.8(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@nomicfoundation/hardhat-network-helpers': - specifier: ^1.0.11 - version: 1.0.11(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) + specifier: ^1.0.12 + version: 1.0.12(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@nomicfoundation/hardhat-verify': - specifier: ^2.0.9 - version: 2.0.9(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) + specifier: ^2.0.11 + version: 2.0.11(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@typechain/ethers-v5': specifier: ^7.2.0 - version: 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4) + version: 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2) '@typechain/hardhat': specifier: ^7.0.0 - version: 7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4))(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))(typechain@8.3.2(typescript@5.5.4)) + version: 7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2))(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(typechain@8.3.2(typescript@5.6.2)) '@types/cbor': specifier: ~5.0.1 version: 5.0.1 '@types/chai': - specifier: ^4.3.17 - version: 4.3.17 + specifier: ^4.3.20 + version: 4.3.20 '@types/debug': specifier: ^4.1.12 version: 4.1.12 @@ -88,17 +88,17 @@ importers: specifier: ^1.0.3 version: 1.0.3 '@types/mocha': - specifier: ^10.0.7 - version: 10.0.7 + specifier: ^10.0.8 + version: 10.0.8 '@types/node': - specifier: ^20.14.15 - version: 20.14.15 + specifier: ^20.16.10 + version: 20.16.10 '@typescript-eslint/eslint-plugin': specifier: ^7.18.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/parser': specifier: ^7.18.0 - version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) + version: 7.18.0(eslint@8.57.1)(typescript@5.6.2) abi-to-sol: specifier: ^0.6.6 version: 0.6.6 @@ -109,29 +109,29 @@ importers: specifier: ^4.5.0 version: 4.5.0 debug: - specifier: ^4.3.6 - version: 4.3.6 + specifier: ^4.3.7 + version: 4.3.7 deep-equal-in-any-order: specifier: ^2.0.6 version: 2.0.6 eslint: - specifier: ^8.57.0 - version: 8.57.0 + specifier: ^8.57.1 + version: 8.57.1 eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.0) + version: 9.1.0(eslint@8.57.1) eslint-plugin-prettier: specifier: ^5.2.1 - version: 5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.3) + version: 5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3) ethers: specifier: ~5.7.2 version: 5.7.2 hardhat: specifier: ~2.20.1 - version: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) + version: 2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) hardhat-abi-exporter: specifier: ^2.10.1 - version: 2.10.1(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) + version: 2.10.1(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) hardhat-ignore-warnings: specifier: ^0.2.6 version: 0.2.11 @@ -142,26 +142,26 @@ importers: specifier: ^3.3.3 version: 3.3.3 prettier-plugin-solidity: - specifier: ^1.3.1 - version: 1.3.1(prettier@3.3.3) + specifier: ^1.4.1 + version: 1.4.1(prettier@3.3.3) solhint: - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^5.0.3 + version: 5.0.3 solhint-plugin-chainlink-solidity: specifier: git+https://github.com/smartcontractkit/chainlink-solhint-rules.git#v1.2.1 version: '@chainlink/solhint-plugin-chainlink-solidity@https://codeload.github.com/smartcontractkit/chainlink-solhint-rules/tar.gz/1b4c0c2663fcd983589d4f33a2e73908624ed43c' solhint-plugin-prettier: specifier: ^0.1.0 - version: 0.1.0(prettier-plugin-solidity@1.3.1(prettier@3.3.3))(prettier@3.3.3) + version: 0.1.0(prettier-plugin-solidity@1.4.1(prettier@3.3.3))(prettier@3.3.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) + version: 10.9.2(@types/node@20.16.10)(typescript@5.6.2) typechain: specifier: ^8.2.1 - version: 8.3.2(typescript@5.5.4) + version: 8.3.2(typescript@5.6.2) typescript: - specifier: ^5.5.4 - version: 5.5.4 + specifier: ^5.6.2 + version: 5.6.2 packages: @@ -195,11 +195,11 @@ packages: resolution: {tarball: https://codeload.github.com/smartcontractkit/chainlink-solhint-rules/tar.gz/1b4c0c2663fcd983589d4f33a2e73908624ed43c} version: 1.2.0 - '@changesets/apply-release-plan@7.0.4': - resolution: {integrity: sha512-HLFwhKWayKinWAul0Vj+76jVx1Pc2v55MGPVjZ924Y/ROeSsBMFutv9heHmCUj48lJyRfOTJG5+ar+29FUky/A==} + '@changesets/apply-release-plan@7.0.5': + resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} - '@changesets/assemble-release-plan@6.0.3': - resolution: {integrity: sha512-bLNh9/Lgl1VwkjWZTq8JmRqH+hj7/Yzfz0jsQ/zJJ+FTmVqmqPj3szeKOri8O/hEM8JmHW019vh2gTO9iq5Cuw==} + '@changesets/assemble-release-plan@6.0.4': + resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==} '@changesets/changelog-git@0.2.0': resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} @@ -207,45 +207,45 @@ packages: '@changesets/changelog-github@0.5.0': resolution: {integrity: sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==} - '@changesets/cli@2.27.7': - resolution: {integrity: sha512-6lr8JltiiXPIjDeYg4iM2MeePP6VN/JkmqBsVA5XRiy01hGS3y629LtSDvKcycj/w/5Eur1rEwby/MjcYS+e2A==} + '@changesets/cli@2.27.8': + resolution: {integrity: sha512-gZNyh+LdSsI82wBSHLQ3QN5J30P4uHKJ4fXgoGwQxfXwYFTJzDdvIJasZn8rYQtmKhyQuiBj4SSnLuKlxKWq4w==} hasBin: true - '@changesets/config@3.0.2': - resolution: {integrity: sha512-cdEhS4t8woKCX2M8AotcV2BOWnBp09sqICxKapgLHf9m5KdENpWjyrFNMjkLqGJtUys9U+w93OxWT0czorVDfw==} + '@changesets/config@3.0.3': + resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - '@changesets/get-dependents-graph@2.1.1': - resolution: {integrity: sha512-LRFjjvigBSzfnPU2n/AhFsuWR5DK++1x47aq6qZ8dzYsPtS/I5mNhIGAS68IAxh1xjO9BTtz55FwefhANZ+FCA==} + '@changesets/get-dependents-graph@2.1.2': + resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} '@changesets/get-github-info@0.6.0': resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==} - '@changesets/get-release-plan@4.0.3': - resolution: {integrity: sha512-6PLgvOIwTSdJPTtpdcr3sLtGatT+Jr22+cQwEBJBy6wP0rjB4yJ9lv583J9fVpn1bfQlBkDa8JxbS2g/n9lIyA==} + '@changesets/get-release-plan@4.0.4': + resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - '@changesets/git@3.0.0': - resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} + '@changesets/git@3.0.1': + resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==} - '@changesets/logger@0.1.0': - resolution: {integrity: sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g==} + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} '@changesets/parse@0.4.0': resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} - '@changesets/pre@2.0.0': - resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} + '@changesets/pre@2.0.1': + resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} - '@changesets/read@0.6.0': - resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} + '@changesets/read@0.6.1': + resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==} - '@changesets/should-skip-package@0.1.0': - resolution: {integrity: sha512-FxG6Mhjw7yFStlSM7Z0Gmg3RiyQ98d/9VpQAZ3Fzr59dCOM9G6ZdYbjiSAt0XtFr9JR5U2tBaJWPjrkGGc618g==} + '@changesets/should-skip-package@0.1.1': + resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} '@changesets/types@4.1.0': resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} @@ -253,8 +253,8 @@ packages: '@changesets/types@6.0.0': resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} - '@changesets/write@0.3.1': - resolution: {integrity: sha512-SyGtMXzH3qFqlHKcvFY2eX+6b0NGiFcNav8AFsYwy5l8hejOeoeTDemu5Yjmke2V5jpzY+pBvM0vCCQ3gdZpfw==} + '@changesets/write@0.3.2': + resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -274,8 +274,8 @@ packages: resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} '@eth-optimism/contracts@0.6.0': @@ -384,9 +384,10 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -394,6 +395,7 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@jridgewell/resolve-uri@3.1.1': resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} @@ -503,21 +505,21 @@ packages: ethers: ^5.0.0 hardhat: ^2.9.4 - '@nomicfoundation/hardhat-ethers@3.0.6': - resolution: {integrity: sha512-/xzkFQAaHQhmIAYOQmvHBPwL+NkwLzT9gRZBsgWUYeV+E6pzXsBQsHfRYbAZ3XEYare+T7S+5Tg/1KDJgepSkA==} + '@nomicfoundation/hardhat-ethers@3.0.8': + resolution: {integrity: sha512-zhOZ4hdRORls31DTOqg+GmEZM0ujly8GGIuRY7t7szEk2zW/arY1qDug/py8AEktT00v5K+b6RvbVog+va51IA==} peerDependencies: ethers: ^6.1.0 hardhat: ^2.0.0 - '@nomicfoundation/hardhat-network-helpers@1.0.11': - resolution: {integrity: sha512-uGPL7QSKvxrHRU69dx8jzoBvuztlLCtyFsbgfXIwIjnO3dqZRz2GNMHJoO3C3dIiUNM6jdNF4AUnoQKDscdYrA==} + '@nomicfoundation/hardhat-network-helpers@1.0.12': + resolution: {integrity: sha512-xTNQNI/9xkHvjmCJnJOTyqDSl8uq1rKb2WOVmixQxFtRd7Oa3ecO8zM0cyC2YmOK+jHB9WPZ+F/ijkHg1CoORA==} peerDependencies: hardhat: ^2.9.5 - '@nomicfoundation/hardhat-verify@2.0.9': - resolution: {integrity: sha512-7kD8hu1+zlnX87gC+UN4S0HTKBnIsDfXZ/pproq1gYsK94hgCk+exvzXbwR0X2giiY/RZPkqY9oKRi0Uev91hQ==} + '@nomicfoundation/hardhat-verify@2.0.11': + resolution: {integrity: sha512-lGIo4dNjVQFdsiEgZp3KP6ntLiF7xJEJsbNHfSyIiFCyI0Yv0518ElsFtMC5uCuHEChiBBMrib9jWQvHHT+X3Q==} peerDependencies: - hardhat: ^2.22.72.0.4 + hardhat: ^2.0.4 '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.0': resolution: {integrity: sha512-vEF3yKuuzfMHsZecHQcnkUrqm8mnTWfJeEVFHpg+cO+le96xQA4lAJYdUan8pXZohQxv1fSReQsn4QGNuBNuCw==} @@ -685,9 +687,6 @@ packages: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} - '@solidity-parser/parser@0.17.0': - resolution: {integrity: sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==} - '@solidity-parser/parser@0.18.0': resolution: {integrity: sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==} @@ -748,8 +747,8 @@ packages: '@types/chai-as-promised@7.1.8': resolution: {integrity: sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==} - '@types/chai@4.3.17': - resolution: {integrity: sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==} + '@types/chai@4.3.20': + resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -766,8 +765,8 @@ packages: '@types/lru-cache@5.1.1': resolution: {integrity: sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==} - '@types/mocha@10.0.7': - resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} + '@types/mocha@10.0.8': + resolution: {integrity: sha512-HfMcUmy9hTMJh66VNcmeC9iVErIZJli2bszuXc6julh5YGuRb/W5OnkHjwLNYdFlMis0sY3If5SEAp+PktdJjw==} '@types/ms@0.7.31': resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} @@ -775,8 +774,8 @@ packages: '@types/node@12.19.16': resolution: {integrity: sha512-7xHmXm/QJ7cbK2laF+YYD7gb5MggHIIQwqyjin3bpEGiSuvScMQ5JZZXPvRipi1MwckTQbJZROMns/JxdnIL1Q==} - '@types/node@20.14.15': - resolution: {integrity: sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==} + '@types/node@20.16.10': + resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} '@types/pbkdf2@3.1.0': resolution: {integrity: sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==} @@ -1220,7 +1219,7 @@ packages: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -1283,8 +1282,8 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1465,8 +1464,8 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true @@ -1582,9 +1581,6 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - find-yarn-workspace-root2@1.2.16: - resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} - find-yarn-workspace-root@2.0.0: resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} @@ -1700,6 +1696,7 @@ packages: glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -1834,10 +1831,6 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} - ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -2070,9 +2063,6 @@ packages: resolution: {integrity: sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==} engines: {node: '>=10.0.0'} - keyv@4.5.0: - resolution: {integrity: sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2097,10 +2087,6 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - load-yaml-file@0.2.0: - resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} - engines: {node: '>=6'} - locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -2392,6 +2378,9 @@ packages: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} + package-manager-detector@0.2.0: + resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} + param-case@2.1.1: resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} @@ -2452,6 +2441,9 @@ packages: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -2460,10 +2452,6 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -2472,10 +2460,6 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - preferred-pm@3.1.3: - resolution: {integrity: sha512-MkXsENfftWSRpzCzImcp4FRsCc3y1opwB73CfCNWyzMqArju2CrlMHlqB7VexKiPEOjGMbttv1r9fSCn5S610w==} - engines: {node: '>=10'} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2484,8 +2468,8 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier-plugin-solidity@1.3.1: - resolution: {integrity: sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA==} + prettier-plugin-solidity@1.4.1: + resolution: {integrity: sha512-Mq8EtfacVZ/0+uDKTtHZGW3Aa7vEbX/BNx63hmVg6YTiTXSiuKP0amj0G6pGwjmLaOfymWh3QgXEZkjQbU8QRg==} engines: {node: '>=16'} peerDependencies: prettier: '>=2.3.0' @@ -2608,6 +2592,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true ripemd160@2.0.2: @@ -2749,8 +2734,8 @@ packages: prettier: ^3.0.0 prettier-plugin-solidity: ^1.0.0 - solhint@5.0.1: - resolution: {integrity: sha512-QeQLS9HGCnIiibt+xiOa/+MuP7BWz9N7C5+Mj9pLHshdkNhuo3AzCpWmjfWVZBUuwIUO3YyCRVIcYLR3YOKGfg==} + solhint@5.0.3: + resolution: {integrity: sha512-OLCH6qm/mZTCpplTXzXTJGId1zrtNuDYP5c2e6snIv/hdRVxPfBBz/bAlL91bY/Accavkayp2Zp2BaDSrLVXTQ==} hasBin: true solidity-ast@0.4.56: @@ -2768,9 +2753,6 @@ packages: cpu: [x64] os: [darwin] - solidity-comments-extractor@0.0.8: - resolution: {integrity: sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==} - solidity-comments-freebsd-x64@0.0.2: resolution: {integrity: sha512-8Qe4mpjuAxFSwZJVk7B8gAoLCdbtS412bQzBwk63L8dmlHogvE39iT70aAk3RHUddAppT5RMBunlPUCFYJ3ZTw==} engines: {node: '>= 10'} @@ -3055,8 +3037,8 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} engines: {node: '>=14.17'} hasBin: true @@ -3071,8 +3053,8 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -3125,10 +3107,6 @@ packages: which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - which-pm@2.0.0: - resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} - engines: {node: '>=8.15'} - which-typed-array@1.1.13: resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} engines: {node: '>= 0.4'} @@ -3259,13 +3237,12 @@ snapshots: '@chainlink/solhint-plugin-chainlink-solidity@https://codeload.github.com/smartcontractkit/chainlink-solhint-rules/tar.gz/1b4c0c2663fcd983589d4f33a2e73908624ed43c': {} - '@changesets/apply-release-plan@7.0.4': + '@changesets/apply-release-plan@7.0.5': dependencies: - '@babel/runtime': 7.24.0 - '@changesets/config': 3.0.2 + '@changesets/config': 3.0.3 '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.0 - '@changesets/should-skip-package': 0.1.0 + '@changesets/git': 3.0.1 + '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 detect-indent: 6.1.0 @@ -3276,12 +3253,11 @@ snapshots: resolve-from: 5.0.0 semver: 7.6.3 - '@changesets/assemble-release-plan@6.0.3': + '@changesets/assemble-release-plan@6.0.4': dependencies: - '@babel/runtime': 7.24.0 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/should-skip-package': 0.1.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 semver: 7.6.3 @@ -3298,46 +3274,44 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/cli@2.27.7': + '@changesets/cli@2.27.8': dependencies: - '@babel/runtime': 7.24.0 - '@changesets/apply-release-plan': 7.0.4 - '@changesets/assemble-release-plan': 6.0.3 + '@changesets/apply-release-plan': 7.0.5 + '@changesets/assemble-release-plan': 6.0.4 '@changesets/changelog-git': 0.2.0 - '@changesets/config': 3.0.2 + '@changesets/config': 3.0.3 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/get-release-plan': 4.0.3 - '@changesets/git': 3.0.0 - '@changesets/logger': 0.1.0 - '@changesets/pre': 2.0.0 - '@changesets/read': 0.6.0 - '@changesets/should-skip-package': 0.1.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/get-release-plan': 4.0.4 + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 + '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 - '@changesets/write': 0.3.1 + '@changesets/write': 0.3.2 '@manypkg/get-packages': 1.1.3 '@types/semver': 7.5.0 ansi-colors: 4.1.3 - chalk: 2.4.2 ci-info: 3.9.0 enquirer: 2.3.6 external-editor: 3.1.0 fs-extra: 7.0.1 - human-id: 1.0.2 mri: 1.2.0 outdent: 0.5.0 p-limit: 2.3.0 - preferred-pm: 3.1.3 + package-manager-detector: 0.2.0 + picocolors: 1.1.0 resolve-from: 5.0.0 semver: 7.6.3 spawndamnit: 2.0.0 term-size: 2.2.1 - '@changesets/config@3.0.2': + '@changesets/config@3.0.3': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.1 - '@changesets/logger': 0.1.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/logger': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 @@ -3347,12 +3321,11 @@ snapshots: dependencies: extendable-error: 0.1.7 - '@changesets/get-dependents-graph@2.1.1': + '@changesets/get-dependents-graph@2.1.2': dependencies: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - chalk: 2.4.2 - fs-extra: 7.0.1 + picocolors: 1.1.0 semver: 7.6.3 '@changesets/get-github-info@0.6.0': @@ -3362,59 +3335,53 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/get-release-plan@4.0.3': + '@changesets/get-release-plan@4.0.4': dependencies: - '@babel/runtime': 7.24.0 - '@changesets/assemble-release-plan': 6.0.3 - '@changesets/config': 3.0.2 - '@changesets/pre': 2.0.0 - '@changesets/read': 0.6.0 + '@changesets/assemble-release-plan': 6.0.4 + '@changesets/config': 3.0.3 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 '@changesets/get-version-range-type@0.4.0': {} - '@changesets/git@3.0.0': + '@changesets/git@3.0.1': dependencies: - '@babel/runtime': 7.24.0 '@changesets/errors': 0.2.0 - '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 is-subdir: 1.2.0 micromatch: 4.0.5 spawndamnit: 2.0.0 - '@changesets/logger@0.1.0': + '@changesets/logger@0.1.1': dependencies: - chalk: 2.4.2 + picocolors: 1.1.0 '@changesets/parse@0.4.0': dependencies: '@changesets/types': 6.0.0 js-yaml: 3.14.1 - '@changesets/pre@2.0.0': + '@changesets/pre@2.0.1': dependencies: - '@babel/runtime': 7.24.0 '@changesets/errors': 0.2.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.6.0': + '@changesets/read@0.6.1': dependencies: - '@babel/runtime': 7.24.0 - '@changesets/git': 3.0.0 - '@changesets/logger': 0.1.0 + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 '@changesets/parse': 0.4.0 '@changesets/types': 6.0.0 - chalk: 2.4.2 fs-extra: 7.0.1 p-filter: 2.1.0 + picocolors: 1.1.0 - '@changesets/should-skip-package@0.1.0': + '@changesets/should-skip-package@0.1.1': dependencies: - '@babel/runtime': 7.24.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 @@ -3422,9 +3389,8 @@ snapshots: '@changesets/types@6.0.0': {} - '@changesets/write@0.3.1': + '@changesets/write@0.3.2': dependencies: - '@babel/runtime': 7.24.0 '@changesets/types': 6.0.0 fs-extra: 7.0.1 human-id: 1.0.2 @@ -3434,9 +3400,9 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': dependencies: - eslint: 8.57.0 + eslint: 8.57.1 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.10.0': {} @@ -3444,7 +3410,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.6 + debug: 4.3.7 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -3455,7 +3421,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.0': {} + '@eslint/js@8.57.1': {} '@eth-optimism/contracts@0.6.0(ethers@5.7.2)': dependencies: @@ -3754,10 +3720,10 @@ snapshots: '@fastify/busboy@2.1.1': {} - '@humanwhocodes/config-array@0.11.14': + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.6 + debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -3835,7 +3801,7 @@ snapshots: '@nomicfoundation/ethereumjs-trie': 6.0.4 '@nomicfoundation/ethereumjs-tx': 5.0.4 '@nomicfoundation/ethereumjs-util': 9.0.4 - debug: 4.3.6 + debug: 4.3.7 ethereum-cryptography: 0.1.3 lru-cache: 10.2.2 transitivePeerDependencies: @@ -3865,7 +3831,7 @@ snapshots: '@nomicfoundation/ethereumjs-tx': 5.0.4 '@nomicfoundation/ethereumjs-util': 9.0.4 '@types/debug': 4.1.12 - debug: 4.3.6 + debug: 4.3.7 ethereum-cryptography: 0.1.3 rustbn-wasm: 0.2.0 transitivePeerDependencies: @@ -3881,7 +3847,7 @@ snapshots: '@nomicfoundation/ethereumjs-rlp': 5.0.4 '@nomicfoundation/ethereumjs-trie': 6.0.4 '@nomicfoundation/ethereumjs-util': 9.0.4 - debug: 4.3.6 + debug: 4.3.7 ethereum-cryptography: 0.1.3 js-sdsl: 4.4.2 lru-cache: 10.2.2 @@ -3934,47 +3900,47 @@ snapshots: '@nomicfoundation/ethereumjs-trie': 6.0.4 '@nomicfoundation/ethereumjs-tx': 5.0.4 '@nomicfoundation/ethereumjs-util': 9.0.4 - debug: 4.3.6 + debug: 4.3.7 ethereum-cryptography: 0.1.3 transitivePeerDependencies: - '@nomicfoundation/ethereumjs-verkle' - c-kzg - supports-color - '@nomicfoundation/hardhat-chai-matchers@1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)))(chai@4.5.0)(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': + '@nomicfoundation/hardhat-chai-matchers@1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(chai@4.5.0)(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: '@ethersproject/abi': 5.7.0 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@types/chai-as-promised': 7.1.8 chai: 4.5.0 chai-as-promised: 7.1.1(chai@4.5.0) deep-eql: 4.1.3 ethers: 5.7.2 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) ordinal: 1.0.3 - '@nomicfoundation/hardhat-ethers@3.0.6(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': + '@nomicfoundation/hardhat-ethers@3.0.8(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: - debug: 4.3.6 + debug: 4.3.7 ethers: 5.7.2 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) lodash.isequal: 4.5.0 transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-network-helpers@1.0.11(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': + '@nomicfoundation/hardhat-network-helpers@1.0.12(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: ethereumjs-util: 7.1.5 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) - '@nomicfoundation/hardhat-verify@2.0.9(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': + '@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/address': 5.7.0 cbor: 8.1.0 chalk: 2.4.2 - debug: 4.3.6 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) + debug: 4.3.7 + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) lodash.clonedeep: 4.5.0 semver: 6.3.0 table: 6.8.1 @@ -4025,10 +3991,10 @@ snapshots: '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.0 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.0 - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: ethers: 5.7.2 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) '@offchainlabs/upgrade-executor@1.1.0-beta.0': dependencies: @@ -4056,7 +4022,7 @@ snapshots: cbor: 9.0.2 chalk: 4.1.2 compare-versions: 6.1.1 - debug: 4.3.6 + debug: 4.3.7 ethereumjs-util: 7.1.5 minimist: 1.2.8 proper-lockfile: 4.1.2 @@ -4149,8 +4115,6 @@ snapshots: '@sindresorhus/is@4.6.0': {} - '@solidity-parser/parser@0.17.0': {} - '@solidity-parser/parser@0.18.0': {} '@szmarczak/http-timer@5.0.1': @@ -4166,7 +4130,7 @@ snapshots: '@truffle/contract-schema@3.4.10': dependencies: ajv: 6.12.6 - debug: 4.3.6 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -4178,51 +4142,51 @@ snapshots: '@tsconfig/node16@1.0.3': {} - '@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4)': + '@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2)': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/bytes': 5.7.0 '@ethersproject/providers': 5.7.2 ethers: 5.7.2 lodash: 4.17.21 - ts-essentials: 7.0.3(typescript@5.5.4) - typechain: 8.3.2(typescript@5.5.4) - typescript: 5.5.4 + ts-essentials: 7.0.3(typescript@5.6.2) + typechain: 8.3.2(typescript@5.6.2) + typescript: 5.6.2 - '@typechain/hardhat@7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4))(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4))(typechain@8.3.2(typescript@5.5.4))': + '@typechain/hardhat@7.0.0(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@typechain/ethers-v5@7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2))(ethers@5.7.2)(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(typechain@8.3.2(typescript@5.6.2))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/providers': 5.7.2 - '@typechain/ethers-v5': 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.5.4))(typescript@5.5.4) + '@typechain/ethers-v5': 7.2.0(@ethersproject/abi@5.7.0)(@ethersproject/bytes@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2) ethers: 5.7.2 fs-extra: 9.1.0 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) - typechain: 8.3.2(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + typechain: 8.3.2(typescript@5.6.2) '@types/bn.js@4.11.6': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.10 '@types/bn.js@5.1.1': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.10 '@types/cacheable-request@6.0.2': dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 20.14.15 + '@types/node': 20.16.10 '@types/responselike': 1.0.0 '@types/cbor@5.0.1': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.10 '@types/chai-as-promised@7.1.8': dependencies: - '@types/chai': 4.3.17 + '@types/chai': 4.3.20 - '@types/chai@4.3.17': {} + '@types/chai@4.3.20': {} '@types/debug@4.1.12': dependencies: @@ -4234,69 +4198,69 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.10 '@types/lru-cache@5.1.1': {} - '@types/mocha@10.0.7': {} + '@types/mocha@10.0.8': {} '@types/ms@0.7.31': {} '@types/node@12.19.16': {} - '@types/node@20.14.15': + '@types/node@20.16.10': dependencies: - undici-types: 5.26.5 + undici-types: 6.19.8 '@types/pbkdf2@3.1.0': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.10 '@types/prettier@2.7.1': {} '@types/readable-stream@2.3.15': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.10 safe-buffer: 5.1.2 '@types/responselike@1.0.0': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.10 '@types/secp256k1@4.0.3': dependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.10 '@types/semver@7.5.0': {} - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 8.57.0 + eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.6 - eslint: 8.57.0 + debug: 4.3.7 + eslint: 8.57.1 optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color @@ -4305,42 +4269,42 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.6.2)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) - debug: 4.3.6 - eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.2) + debug: 4.3.7 + eslint: 8.57.1 + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.2)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.6 + debug: 4.3.7 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.6.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) - eslint: 8.57.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) + eslint: 8.57.1 transitivePeerDependencies: - supports-color - typescript @@ -4365,7 +4329,7 @@ snapshots: source-map-support: 0.5.21 optionalDependencies: prettier: 2.8.8 - prettier-plugin-solidity: 1.3.1(prettier@2.8.8) + prettier-plugin-solidity: 1.4.1(prettier@2.8.8) transitivePeerDependencies: - supports-color @@ -4385,7 +4349,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.6 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -4613,7 +4577,7 @@ snapshots: clone-response: 1.0.2 get-stream: 5.1.0 http-cache-semantics: 4.0.3 - keyv: 4.5.0 + keyv: 4.5.4 lowercase-keys: 2.0.0 normalize-url: 6.1.0 responselike: 2.0.1 @@ -4873,9 +4837,9 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - debug@4.3.6: + debug@4.3.7: dependencies: - ms: 2.1.2 + ms: 2.1.3 decamelize@4.0.0: {} @@ -5114,18 +5078,18 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@9.1.0(eslint@8.57.0): + eslint-config-prettier@9.1.0(eslint@8.57.1): dependencies: - eslint: 8.57.0 + eslint: 8.57.1 - eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.3): + eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3): dependencies: - eslint: 8.57.0 + eslint: 8.57.1 prettier: 3.3.3 prettier-linter-helpers: 1.0.0 synckit: 0.9.1 optionalDependencies: - eslint-config-prettier: 9.1.0(eslint@8.57.0) + eslint-config-prettier: 9.1.0(eslint@8.57.1) eslint-scope@7.2.2: dependencies: @@ -5134,20 +5098,20 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint@8.57.0: + eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.6 + debug: 4.3.7 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -5358,11 +5322,6 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - find-yarn-workspace-root2@1.2.16: - dependencies: - micromatch: 4.0.5 - pkg-dir: 4.2.0 - find-yarn-workspace-root@2.0.0: dependencies: micromatch: 4.0.5 @@ -5377,9 +5336,9 @@ snapshots: flatted@3.3.1: {} - follow-redirects@1.15.6(debug@4.3.6): + follow-redirects@1.15.6(debug@4.3.7): optionalDependencies: - debug: 4.3.6 + debug: 4.3.7 for-each@0.3.3: dependencies: @@ -5533,7 +5492,7 @@ snapshots: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.1 - ignore: 5.2.4 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 @@ -5564,11 +5523,11 @@ snapshots: graphemer@1.4.0: {} - hardhat-abi-exporter@2.10.1(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4)): + hardhat-abi-exporter@2.10.1(hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)): dependencies: '@ethersproject/abi': 5.7.0 delete-empty: 3.0.0 - hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4) + hardhat: 2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) hardhat-ignore-warnings@0.2.11: dependencies: @@ -5576,7 +5535,7 @@ snapshots: node-interval-tree: 2.1.2 solidity-comments: 0.0.2 - hardhat@2.20.1(ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4))(typescript@5.5.4): + hardhat@2.20.1(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 @@ -5602,7 +5561,7 @@ snapshots: chalk: 2.4.2 chokidar: 3.5.3 ci-info: 2.0.0 - debug: 4.3.6 + debug: 4.3.7 enquirer: 2.3.6 env-paths: 2.2.1 ethereum-cryptography: 1.1.2 @@ -5621,7 +5580,7 @@ snapshots: raw-body: 2.5.1 resolve: 1.17.0 semver: 6.3.0 - solc: 0.7.3(debug@4.3.6) + solc: 0.7.3(debug@4.3.7) source-map-support: 0.5.21 stacktrace-parser: 0.1.10 tsort: 0.0.1 @@ -5629,8 +5588,8 @@ snapshots: uuid: 8.3.2 ws: 7.5.9 optionalDependencies: - ts-node: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) - typescript: 5.5.4 + ts-node: 10.9.2(@types/node@20.16.10)(typescript@5.6.2) + typescript: 5.6.2 transitivePeerDependencies: - bufferutil - c-kzg @@ -5725,7 +5684,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.6 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -5735,8 +5694,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ignore@5.2.4: {} - ignore@5.3.1: {} immutable@4.1.0: {} @@ -5969,10 +5926,6 @@ snapshots: node-gyp-build: 4.5.0 readable-stream: 3.6.0 - keyv@4.5.0: - dependencies: - json-buffer: 3.0.1 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -5998,13 +5951,6 @@ snapshots: lines-and-columns@1.2.4: {} - load-yaml-file@0.2.0: - dependencies: - graceful-fs: 4.2.10 - js-yaml: 3.14.1 - pify: 4.0.1 - strip-bom: 3.0.0 - locate-path@2.0.0: dependencies: p-locate: 2.0.0 @@ -6277,6 +6223,8 @@ snapshots: registry-url: 6.0.1 semver: 7.6.3 + package-manager-detector@0.2.0: {} + param-case@2.1.1: dependencies: no-case: 2.3.2 @@ -6344,46 +6292,35 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 + picocolors@1.1.0: {} + picomatch@2.3.1: {} pify@4.0.1: {} - pkg-dir@4.2.0: - dependencies: - find-up: 4.1.0 - pluralize@8.0.0: {} possible-typed-array-names@1.0.0: optional: true - preferred-pm@3.1.3: - dependencies: - find-up: 5.0.0 - find-yarn-workspace-root2: 1.2.16 - path-exists: 4.0.0 - which-pm: 2.0.0 - prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: dependencies: fast-diff: 1.2.0 - prettier-plugin-solidity@1.3.1(prettier@2.8.8): + prettier-plugin-solidity@1.4.1(prettier@2.8.8): dependencies: - '@solidity-parser/parser': 0.17.0 + '@solidity-parser/parser': 0.18.0 prettier: 2.8.8 semver: 7.6.3 - solidity-comments-extractor: 0.0.8 optional: true - prettier-plugin-solidity@1.3.1(prettier@3.3.3): + prettier-plugin-solidity@1.4.1(prettier@3.3.3): dependencies: - '@solidity-parser/parser': 0.17.0 + '@solidity-parser/parser': 0.18.0 prettier: 3.3.3 semver: 7.6.3 - solidity-comments-extractor: 0.0.8 prettier@2.8.8: {} @@ -6651,11 +6588,11 @@ snapshots: dependencies: no-case: 2.3.2 - solc@0.7.3(debug@4.3.6): + solc@0.7.3(debug@4.3.7): dependencies: command-exists: 1.2.9 commander: 3.0.2 - follow-redirects: 1.15.6(debug@4.3.6) + follow-redirects: 1.15.6(debug@4.3.7) fs-extra: 0.30.0 js-sha3: 0.8.0 memorystream: 0.3.1 @@ -6665,14 +6602,14 @@ snapshots: transitivePeerDependencies: - debug - solhint-plugin-prettier@0.1.0(prettier-plugin-solidity@1.3.1(prettier@3.3.3))(prettier@3.3.3): + solhint-plugin-prettier@0.1.0(prettier-plugin-solidity@1.4.1(prettier@3.3.3))(prettier@3.3.3): dependencies: '@prettier/sync': 0.3.0(prettier@3.3.3) prettier: 3.3.3 prettier-linter-helpers: 1.0.0 - prettier-plugin-solidity: 1.3.1(prettier@3.3.3) + prettier-plugin-solidity: 1.4.1(prettier@3.3.3) - solhint@5.0.1: + solhint@5.0.3: dependencies: '@solidity-parser/parser': 0.18.0 ajv: 6.12.6 @@ -6683,7 +6620,7 @@ snapshots: cosmiconfig: 8.2.0 fast-diff: 1.2.0 glob: 8.1.0 - ignore: 5.2.4 + ignore: 5.3.1 js-yaml: 4.1.0 latest-version: 7.0.0 lodash: 4.17.21 @@ -6706,8 +6643,6 @@ snapshots: solidity-comments-darwin-x64@0.0.2: optional: true - solidity-comments-extractor@0.0.8: {} - solidity-comments-freebsd-x64@0.0.2: optional: true @@ -6896,9 +6831,9 @@ snapshots: tr46@0.0.3: {} - ts-api-utils@1.3.0(typescript@5.5.4): + ts-api-utils@1.3.0(typescript@5.6.2): dependencies: - typescript: 5.5.4 + typescript: 5.6.2 ts-command-line-args@2.5.1: dependencies: @@ -6907,25 +6842,25 @@ snapshots: command-line-usage: 6.1.3 string-format: 2.0.0 - ts-essentials@7.0.3(typescript@5.5.4): + ts-essentials@7.0.3(typescript@5.6.2): dependencies: - typescript: 5.5.4 + typescript: 5.6.2 - ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.9 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 20.14.15 + '@types/node': 20.16.10 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.4 + typescript: 5.6.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -6953,10 +6888,10 @@ snapshots: type-fest@0.7.1: {} - typechain@8.3.2(typescript@5.5.4): + typechain@8.3.2(typescript@5.6.2): dependencies: '@types/prettier': 2.7.1 - debug: 4.3.6 + debug: 4.3.7 fs-extra: 7.0.1 glob: 7.1.7 js-sha3: 0.8.0 @@ -6964,8 +6899,8 @@ snapshots: mkdirp: 1.0.4 prettier: 2.8.8 ts-command-line-args: 2.5.1 - ts-essentials: 7.0.3(typescript@5.5.4) - typescript: 5.5.4 + ts-essentials: 7.0.3(typescript@5.6.2) + typescript: 5.6.2 transitivePeerDependencies: - supports-color @@ -7036,7 +6971,7 @@ snapshots: possible-typed-array-names: 1.0.0 optional: true - typescript@5.5.4: {} + typescript@5.6.2: {} typical@4.0.0: {} @@ -7050,7 +6985,7 @@ snapshots: which-boxed-primitive: 1.0.2 optional: true - undici-types@5.26.5: {} + undici-types@6.19.8: {} undici@5.28.4: dependencies: @@ -7106,11 +7041,6 @@ snapshots: is-symbol: 1.0.3 optional: true - which-pm@2.0.0: - dependencies: - load-yaml-file: 0.2.0 - path-exists: 4.0.0 - which-typed-array@1.1.13: dependencies: available-typed-arrays: 1.0.5 diff --git a/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol b/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol index 4d159594b6d..28fa670650b 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol @@ -72,11 +72,7 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { */ // Reverts transaction if config args are invalid - modifier checkConfigValid( - uint256 numSigners, - uint256 numTransmitters, - uint256 f - ) { + modifier checkConfigValid(uint256 numSigners, uint256 numTransmitters, uint256 f) { if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); if (f == 0) revert InvalidConfig("f must be positive"); if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); diff --git a/contracts/src/v0.8/functions/v1_0_0/ocr/OCR2Base.sol b/contracts/src/v0.8/functions/v1_0_0/ocr/OCR2Base.sol index ba671d4468b..b7c3f03fea7 100644 --- a/contracts/src/v0.8/functions/v1_0_0/ocr/OCR2Base.sol +++ b/contracts/src/v0.8/functions/v1_0_0/ocr/OCR2Base.sol @@ -70,11 +70,7 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { */ // Reverts transaction if config args are invalid - modifier checkConfigValid( - uint256 numSigners, - uint256 numTransmitters, - uint256 f - ) { + modifier checkConfigValid(uint256 numSigners, uint256 numTransmitters, uint256 f) { if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); if (f == 0) revert InvalidConfig("f must be positive"); if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); diff --git a/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Base.sol b/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Base.sol index 5bee4360054..a4fd2bfa128 100644 --- a/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Base.sol +++ b/contracts/src/v0.8/functions/v1_1_0/ocr/OCR2Base.sol @@ -64,11 +64,7 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { */ // Reverts transaction if config args are invalid - modifier checkConfigValid( - uint256 numSigners, - uint256 numTransmitters, - uint256 f - ) { + modifier checkConfigValid(uint256 numSigners, uint256 numTransmitters, uint256 f) { if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); if (f == 0) revert InvalidConfig("f must be positive"); if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); diff --git a/contracts/src/v0.8/functions/v1_3_0/ocr/OCR2Base.sol b/contracts/src/v0.8/functions/v1_3_0/ocr/OCR2Base.sol index caa9c301a33..565e7d800dd 100644 --- a/contracts/src/v0.8/functions/v1_3_0/ocr/OCR2Base.sol +++ b/contracts/src/v0.8/functions/v1_3_0/ocr/OCR2Base.sol @@ -72,11 +72,7 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { */ // Reverts transaction if config args are invalid - modifier checkConfigValid( - uint256 numSigners, - uint256 numTransmitters, - uint256 f - ) { + modifier checkConfigValid(uint256 numSigners, uint256 numTransmitters, uint256 f) { if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); if (f == 0) revert InvalidConfig("f must be positive"); if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); diff --git a/contracts/src/v0.8/keystone/OCR3Capability.sol b/contracts/src/v0.8/keystone/OCR3Capability.sol index c7ab8299b73..22ab9394cc2 100644 --- a/contracts/src/v0.8/keystone/OCR3Capability.sol +++ b/contracts/src/v0.8/keystone/OCR3Capability.sol @@ -31,11 +31,7 @@ contract OCR3Capability is ConfirmedOwner, OCR2Abstract { */ // Reverts transaction if config args are invalid - modifier checkConfigValid( - uint256 numSigners, - uint256 numTransmitters, - uint256 f - ) { + modifier checkConfigValid(uint256 numSigners, uint256 numTransmitters, uint256 f) { if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); if (f == 0) revert InvalidConfig("f must be positive"); if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); diff --git a/contracts/src/v0.8/liquiditymanager/ocr/OCR3Base.sol b/contracts/src/v0.8/liquiditymanager/ocr/OCR3Base.sol index b856f734e7b..8c31ba5521a 100644 --- a/contracts/src/v0.8/liquiditymanager/ocr/OCR3Base.sol +++ b/contracts/src/v0.8/liquiditymanager/ocr/OCR3Base.sol @@ -90,11 +90,7 @@ abstract contract OCR3Base is OwnerIsCreator, OCR3Abstract { } // Reverts transaction if config args are invalid - modifier checkConfigValid( - uint256 numSigners, - uint256 numTransmitters, - uint256 f - ) { + modifier checkConfigValid(uint256 numSigners, uint256 numTransmitters, uint256 f) { if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers"); if (f == 0) revert InvalidConfig("f must be positive"); if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration"); diff --git a/contracts/src/v0.8/shared/ocr2/OCR2Base.sol b/contracts/src/v0.8/shared/ocr2/OCR2Base.sol index 7a90f2abf93..1dfc21fcae2 100644 --- a/contracts/src/v0.8/shared/ocr2/OCR2Base.sol +++ b/contracts/src/v0.8/shared/ocr2/OCR2Base.sol @@ -67,11 +67,7 @@ abstract contract OCR2Base is OwnerIsCreator, OCR2Abstract { address[] internal s_transmitters; /// @dev Reverts transaction if config args are invalid - modifier checkConfigValid( - uint256 _numSigners, - uint256 _numTransmitters, - uint256 _f - ) { + modifier checkConfigValid(uint256 _numSigners, uint256 _numTransmitters, uint256 _f) { require(_numSigners <= MAX_NUM_ORACLES, "too many signers"); require(_f > 0, "f must be positive"); require(_numSigners == _numTransmitters, "oracle addresses out of registration"); diff --git a/contracts/src/v0.8/shared/test/testhelpers/GasConsumer.sol b/contracts/src/v0.8/shared/test/testhelpers/GasConsumer.sol index efac2d15c1d..daf847985e3 100644 --- a/contracts/src/v0.8/shared/test/testhelpers/GasConsumer.sol +++ b/contracts/src/v0.8/shared/test/testhelpers/GasConsumer.sol @@ -6,9 +6,7 @@ contract GasConsumer { // While loop that operates indefinitely, written in yul to ensure better granularity over exactly how much gas is spent for { // Loop will run forever since 0 < 1 - } lt(0, 1) { - - } { + } lt(0, 1) {} { // If 100 gas is remaining, then exit the loop by returning. 100 was determined by manual binary search to be // the minimal amount of gas needed but less than the cost of another loop if lt(gas(), 100) { From fdbd9da849ae96725b960d9db94ac1b8e9457085 Mon Sep 17 00:00:00 2001 From: Ryan Tinianov Date: Thu, 3 Oct 2024 15:45:54 -0400 Subject: [PATCH 27/28] Json schema for the webapi trigger (#14627) * Json schema for the webapi trigger * fix go mod * update common to try to fix the lint not finding a struct...? * latest chainlink-common * backout chainlink-common updates: * PR review, combine 2 requires in integration-test/load/go.mod * make gomodtidy --------- Co-authored-by: David Orchard --- core/capabilities/webapi/trigger.go | 28 +-- .../webapicap/event_trigger-schema.json | 89 +++++++++ .../webapicap/event_trigger_generated.go | 147 ++++++++++++++ core/capabilities/webapi/webapicap/gen.go | 5 + .../webapicap/trigger_builders_generated.go | 189 ++++++++++++++++++ .../webapicaptest/trigger_mock_generated.go | 17 ++ core/scripts/go.mod | 6 + core/scripts/go.sum | 15 ++ integration-tests/go.mod | 5 + integration-tests/go.sum | 13 ++ integration-tests/load/go.mod | 5 + integration-tests/load/go.sum | 13 ++ 12 files changed, 518 insertions(+), 14 deletions(-) create mode 100644 core/capabilities/webapi/webapicap/event_trigger-schema.json create mode 100644 core/capabilities/webapi/webapicap/event_trigger_generated.go create mode 100644 core/capabilities/webapi/webapicap/gen.go create mode 100644 core/capabilities/webapi/webapicap/trigger_builders_generated.go create mode 100644 core/capabilities/webapi/webapicap/webapicaptest/trigger_mock_generated.go diff --git a/core/capabilities/webapi/trigger.go b/core/capabilities/webapi/trigger.go index 611879c7a0a..5dbb38500e4 100644 --- a/core/capabilities/webapi/trigger.go +++ b/core/capabilities/webapi/trigger.go @@ -13,6 +13,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types/core" "github.com/smartcontractkit/chainlink-common/pkg/values" + + "github.com/smartcontractkit/chainlink/v2/core/capabilities/webapi/webapicap" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/api" "github.com/smartcontractkit/chainlink/v2/core/services/gateway/connector" @@ -30,21 +32,11 @@ var webapiTriggerInfo = capabilities.MustNewCapabilityInfo( "A trigger to start workflow execution from a web api call", ) -type Input struct { -} -type Config struct { - AllowedSenders []string `toml:"allowedSenders"` - AllowedTopics []string `toml:"allowedTopics"` - RateLimiter common.RateLimiterConfig `toml:"rateLimiter"` - // RequiredParams is advisory to the web trigger message sender it is not enforced. - RequiredParams []string `toml:"requiredParams"` -} - type webapiTrigger struct { allowedSenders map[string]bool allowedTopics map[string]bool ch chan<- capabilities.TriggerResponse - config Config + config webapicap.TriggerConfig rateLimiter *common.RateLimiter } @@ -52,7 +44,7 @@ type triggerConnectorHandler struct { services.StateMachine capabilities.CapabilityInfo - capabilities.Validator[Config, Input, capabilities.TriggerResponse] + capabilities.Validator[webapicap.TriggerConfig, struct{}, capabilities.TriggerResponse] connector connector.GatewayConnector lggr logger.Logger mu sync.Mutex @@ -67,7 +59,7 @@ func NewTrigger(config string, registry core.CapabilitiesRegistry, connector con return nil, errors.New("missing connector") } handler := &triggerConnectorHandler{ - Validator: capabilities.NewValidator[Config, Input, capabilities.TriggerResponse](capabilities.ValidatorArgs{Info: webapiTriggerInfo}), + Validator: capabilities.NewValidator[webapicap.TriggerConfig, struct{}, capabilities.TriggerResponse](capabilities.ValidatorArgs{Info: webapiTriggerInfo}), connector: connector, registeredWorkflows: map[string]webapiTrigger{}, lggr: lggr.Named("WorkflowConnectorHandler"), @@ -198,7 +190,15 @@ func (h *triggerConnectorHandler) RegisterTrigger(ctx context.Context, req capab return nil, fmt.Errorf("triggerId %s already registered", req.TriggerID) } - rateLimiter, err := common.NewRateLimiter(reqConfig.RateLimiter) + rateLimiterConfig := reqConfig.RateLimiter + commonRateLimiter := common.RateLimiterConfig{ + GlobalRPS: rateLimiterConfig.GlobalRPS, + GlobalBurst: int(rateLimiterConfig.GlobalBurst), + PerSenderRPS: rateLimiterConfig.PerSenderRPS, + PerSenderBurst: int(rateLimiterConfig.PerSenderBurst), + } + + rateLimiter, err := common.NewRateLimiter(commonRateLimiter) if err != nil { return nil, err } diff --git a/core/capabilities/webapi/webapicap/event_trigger-schema.json b/core/capabilities/webapi/webapicap/event_trigger-schema.json new file mode 100644 index 00000000000..e17d2dbad57 --- /dev/null +++ b/core/capabilities/webapi/webapicap/event_trigger-schema.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink/v2/core/capabilities/webapi/webapicap/web-trigger@1.0.0", + "$defs": { + "TriggerConfig": { + "type": "object", + "properties": { + "allowedSenders": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowedTopics": { + "type": "array", + "items": { + "type": "string" + } + }, + "rateLimiter": { + "$ref": "#/$defs/RateLimiterConfig" + }, + "requiredParams": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["allowedSenders", "allowedTopics", "rateLimiter", "requiredParams"], + "additionalProperties": false + }, + "RateLimiterConfig": { + "type": "object", + "properties": { + "globalRPS": { + "type": "number" + }, + "globalBurst": { + "type": "integer" + }, + "perSenderRPS": { + "type": "number" + }, + "perSenderBurst": { + "type": "integer" + } + }, + "required": ["globalRPS", "globalBurst", "perSenderRPS", "perSenderBurst"], + "additionalProperties": false + }, + "TriggerRequestPayload": { + "type": "object", + "properties": { + "trigger_id": { + "type": "string" + }, + "trigger_event_id": { + "type": "string" + }, + "timestamp": { + "type": "integer", + "format": "int64" + }, + "topics": { + "type": "array", + "items": { + "type": "string" + } + }, + "params": { + "type": "object", + "additionalProperties": true + } + }, + "required": ["trigger_id", "trigger_event_id", "timestamp", "topics", "params"], + "additionalProperties": false + } + }, + "type": "object", + "properties": { + "Config": { + "$ref": "#/$defs/TriggerConfig" + }, + "Outputs": { + "$ref": "#/$defs/TriggerRequestPayload" + } + } + } \ No newline at end of file diff --git a/core/capabilities/webapi/webapicap/event_trigger_generated.go b/core/capabilities/webapi/webapicap/event_trigger_generated.go new file mode 100644 index 00000000000..4cc34acb11a --- /dev/null +++ b/core/capabilities/webapi/webapicap/event_trigger_generated.go @@ -0,0 +1,147 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package webapicap + +import ( + "encoding/json" + "fmt" +) + +type RateLimiterConfig struct { + // GlobalBurst corresponds to the JSON schema field "globalBurst". + GlobalBurst int64 `json:"globalBurst" yaml:"globalBurst" mapstructure:"globalBurst"` + + // GlobalRPS corresponds to the JSON schema field "globalRPS". + GlobalRPS float64 `json:"globalRPS" yaml:"globalRPS" mapstructure:"globalRPS"` + + // PerSenderBurst corresponds to the JSON schema field "perSenderBurst". + PerSenderBurst int64 `json:"perSenderBurst" yaml:"perSenderBurst" mapstructure:"perSenderBurst"` + + // PerSenderRPS corresponds to the JSON schema field "perSenderRPS". + PerSenderRPS float64 `json:"perSenderRPS" yaml:"perSenderRPS" mapstructure:"perSenderRPS"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *RateLimiterConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["globalBurst"]; raw != nil && !ok { + return fmt.Errorf("field globalBurst in RateLimiterConfig: required") + } + if _, ok := raw["globalRPS"]; raw != nil && !ok { + return fmt.Errorf("field globalRPS in RateLimiterConfig: required") + } + if _, ok := raw["perSenderBurst"]; raw != nil && !ok { + return fmt.Errorf("field perSenderBurst in RateLimiterConfig: required") + } + if _, ok := raw["perSenderRPS"]; raw != nil && !ok { + return fmt.Errorf("field perSenderRPS in RateLimiterConfig: required") + } + type Plain RateLimiterConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = RateLimiterConfig(plain) + return nil +} + +type Trigger struct { + // Config corresponds to the JSON schema field "Config". + Config *TriggerConfig `json:"Config,omitempty" yaml:"Config,omitempty" mapstructure:"Config,omitempty"` + + // Outputs corresponds to the JSON schema field "Outputs". + Outputs *TriggerRequestPayload `json:"Outputs,omitempty" yaml:"Outputs,omitempty" mapstructure:"Outputs,omitempty"` +} + +type TriggerConfig struct { + // AllowedSenders corresponds to the JSON schema field "allowedSenders". + AllowedSenders []string `json:"allowedSenders" yaml:"allowedSenders" mapstructure:"allowedSenders"` + + // AllowedTopics corresponds to the JSON schema field "allowedTopics". + AllowedTopics []string `json:"allowedTopics" yaml:"allowedTopics" mapstructure:"allowedTopics"` + + // RateLimiter corresponds to the JSON schema field "rateLimiter". + RateLimiter RateLimiterConfig `json:"rateLimiter" yaml:"rateLimiter" mapstructure:"rateLimiter"` + + // RequiredParams corresponds to the JSON schema field "requiredParams". + RequiredParams []string `json:"requiredParams" yaml:"requiredParams" mapstructure:"requiredParams"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TriggerConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["allowedSenders"]; raw != nil && !ok { + return fmt.Errorf("field allowedSenders in TriggerConfig: required") + } + if _, ok := raw["allowedTopics"]; raw != nil && !ok { + return fmt.Errorf("field allowedTopics in TriggerConfig: required") + } + if _, ok := raw["rateLimiter"]; raw != nil && !ok { + return fmt.Errorf("field rateLimiter in TriggerConfig: required") + } + if _, ok := raw["requiredParams"]; raw != nil && !ok { + return fmt.Errorf("field requiredParams in TriggerConfig: required") + } + type Plain TriggerConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = TriggerConfig(plain) + return nil +} + +type TriggerRequestPayload struct { + // Params corresponds to the JSON schema field "params". + Params TriggerRequestPayloadParams `json:"params" yaml:"params" mapstructure:"params"` + + // Timestamp corresponds to the JSON schema field "timestamp". + Timestamp int64 `json:"timestamp" yaml:"timestamp" mapstructure:"timestamp"` + + // Topics corresponds to the JSON schema field "topics". + Topics []string `json:"topics" yaml:"topics" mapstructure:"topics"` + + // TriggerEventId corresponds to the JSON schema field "trigger_event_id". + TriggerEventId string `json:"trigger_event_id" yaml:"trigger_event_id" mapstructure:"trigger_event_id"` + + // TriggerId corresponds to the JSON schema field "trigger_id". + TriggerId string `json:"trigger_id" yaml:"trigger_id" mapstructure:"trigger_id"` +} + +type TriggerRequestPayloadParams map[string]interface{} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TriggerRequestPayload) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["params"]; raw != nil && !ok { + return fmt.Errorf("field params in TriggerRequestPayload: required") + } + if _, ok := raw["timestamp"]; raw != nil && !ok { + return fmt.Errorf("field timestamp in TriggerRequestPayload: required") + } + if _, ok := raw["topics"]; raw != nil && !ok { + return fmt.Errorf("field topics in TriggerRequestPayload: required") + } + if _, ok := raw["trigger_event_id"]; raw != nil && !ok { + return fmt.Errorf("field trigger_event_id in TriggerRequestPayload: required") + } + if _, ok := raw["trigger_id"]; raw != nil && !ok { + return fmt.Errorf("field trigger_id in TriggerRequestPayload: required") + } + type Plain TriggerRequestPayload + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = TriggerRequestPayload(plain) + return nil +} diff --git a/core/capabilities/webapi/webapicap/gen.go b/core/capabilities/webapi/webapicap/gen.go new file mode 100644 index 00000000000..ac1b7d5272c --- /dev/null +++ b/core/capabilities/webapi/webapicap/gen.go @@ -0,0 +1,5 @@ +package webapicap + +import _ "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd" // Required so that the tool is available to be run in go generate below. + +//go:generate go run github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/generate-types --dir $GOFILE diff --git a/core/capabilities/webapi/webapicap/trigger_builders_generated.go b/core/capabilities/webapi/webapicap/trigger_builders_generated.go new file mode 100644 index 00000000000..525fb78179b --- /dev/null +++ b/core/capabilities/webapi/webapicap/trigger_builders_generated.go @@ -0,0 +1,189 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package webapicap + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk" +) + +func (cfg TriggerConfig) New(w *sdk.WorkflowSpecFactory) TriggerRequestPayloadCap { + ref := "trigger" + def := sdk.StepDefinition{ + ID: "web-trigger@1.0.0", Ref: ref, + Inputs: sdk.StepInputs{}, + Config: map[string]any{ + "allowedSenders": cfg.AllowedSenders, + "allowedTopics": cfg.AllowedTopics, + "rateLimiter": cfg.RateLimiter, + "requiredParams": cfg.RequiredParams, + }, + CapabilityType: capabilities.CapabilityTypeTrigger, + } + + step := sdk.Step[TriggerRequestPayload]{Definition: def} + return TriggerRequestPayloadCapFromStep(w, step) +} + +type RateLimiterConfigCap interface { + sdk.CapDefinition[RateLimiterConfig] + GlobalBurst() sdk.CapDefinition[int64] + GlobalRPS() sdk.CapDefinition[float64] + PerSenderBurst() sdk.CapDefinition[int64] + PerSenderRPS() sdk.CapDefinition[float64] + private() +} + +// RateLimiterConfigCapFromStep should only be called from generated code to assure type safety +func RateLimiterConfigCapFromStep(w *sdk.WorkflowSpecFactory, step sdk.Step[RateLimiterConfig]) RateLimiterConfigCap { + raw := step.AddTo(w) + return &rateLimiterConfig{CapDefinition: raw} +} + +type rateLimiterConfig struct { + sdk.CapDefinition[RateLimiterConfig] +} + +func (*rateLimiterConfig) private() {} +func (c *rateLimiterConfig) GlobalBurst() sdk.CapDefinition[int64] { + return sdk.AccessField[RateLimiterConfig, int64](c.CapDefinition, "globalBurst") +} +func (c *rateLimiterConfig) GlobalRPS() sdk.CapDefinition[float64] { + return sdk.AccessField[RateLimiterConfig, float64](c.CapDefinition, "globalRPS") +} +func (c *rateLimiterConfig) PerSenderBurst() sdk.CapDefinition[int64] { + return sdk.AccessField[RateLimiterConfig, int64](c.CapDefinition, "perSenderBurst") +} +func (c *rateLimiterConfig) PerSenderRPS() sdk.CapDefinition[float64] { + return sdk.AccessField[RateLimiterConfig, float64](c.CapDefinition, "perSenderRPS") +} + +func NewRateLimiterConfigFromFields( + globalBurst sdk.CapDefinition[int64], + globalRPS sdk.CapDefinition[float64], + perSenderBurst sdk.CapDefinition[int64], + perSenderRPS sdk.CapDefinition[float64]) RateLimiterConfigCap { + return &simpleRateLimiterConfig{ + CapDefinition: sdk.ComponentCapDefinition[RateLimiterConfig]{ + "globalBurst": globalBurst.Ref(), + "globalRPS": globalRPS.Ref(), + "perSenderBurst": perSenderBurst.Ref(), + "perSenderRPS": perSenderRPS.Ref(), + }, + globalBurst: globalBurst, + globalRPS: globalRPS, + perSenderBurst: perSenderBurst, + perSenderRPS: perSenderRPS, + } +} + +type simpleRateLimiterConfig struct { + sdk.CapDefinition[RateLimiterConfig] + globalBurst sdk.CapDefinition[int64] + globalRPS sdk.CapDefinition[float64] + perSenderBurst sdk.CapDefinition[int64] + perSenderRPS sdk.CapDefinition[float64] +} + +func (c *simpleRateLimiterConfig) GlobalBurst() sdk.CapDefinition[int64] { + return c.globalBurst +} +func (c *simpleRateLimiterConfig) GlobalRPS() sdk.CapDefinition[float64] { + return c.globalRPS +} +func (c *simpleRateLimiterConfig) PerSenderBurst() sdk.CapDefinition[int64] { + return c.perSenderBurst +} +func (c *simpleRateLimiterConfig) PerSenderRPS() sdk.CapDefinition[float64] { + return c.perSenderRPS +} + +func (c *simpleRateLimiterConfig) private() {} + +type TriggerRequestPayloadCap interface { + sdk.CapDefinition[TriggerRequestPayload] + Params() TriggerRequestPayloadParamsCap + Timestamp() sdk.CapDefinition[int64] + Topics() sdk.CapDefinition[[]string] + TriggerEventId() sdk.CapDefinition[string] + TriggerId() sdk.CapDefinition[string] + private() +} + +// TriggerRequestPayloadCapFromStep should only be called from generated code to assure type safety +func TriggerRequestPayloadCapFromStep(w *sdk.WorkflowSpecFactory, step sdk.Step[TriggerRequestPayload]) TriggerRequestPayloadCap { + raw := step.AddTo(w) + return &triggerRequestPayload{CapDefinition: raw} +} + +type triggerRequestPayload struct { + sdk.CapDefinition[TriggerRequestPayload] +} + +func (*triggerRequestPayload) private() {} +func (c *triggerRequestPayload) Params() TriggerRequestPayloadParamsCap { + return TriggerRequestPayloadParamsCap(sdk.AccessField[TriggerRequestPayload, TriggerRequestPayloadParams](c.CapDefinition, "params")) +} +func (c *triggerRequestPayload) Timestamp() sdk.CapDefinition[int64] { + return sdk.AccessField[TriggerRequestPayload, int64](c.CapDefinition, "timestamp") +} +func (c *triggerRequestPayload) Topics() sdk.CapDefinition[[]string] { + return sdk.AccessField[TriggerRequestPayload, []string](c.CapDefinition, "topics") +} +func (c *triggerRequestPayload) TriggerEventId() sdk.CapDefinition[string] { + return sdk.AccessField[TriggerRequestPayload, string](c.CapDefinition, "trigger_event_id") +} +func (c *triggerRequestPayload) TriggerId() sdk.CapDefinition[string] { + return sdk.AccessField[TriggerRequestPayload, string](c.CapDefinition, "trigger_id") +} + +func NewTriggerRequestPayloadFromFields( + params TriggerRequestPayloadParamsCap, + timestamp sdk.CapDefinition[int64], + topics sdk.CapDefinition[[]string], + triggerEventId sdk.CapDefinition[string], + triggerId sdk.CapDefinition[string]) TriggerRequestPayloadCap { + return &simpleTriggerRequestPayload{ + CapDefinition: sdk.ComponentCapDefinition[TriggerRequestPayload]{ + "params": params.Ref(), + "timestamp": timestamp.Ref(), + "topics": topics.Ref(), + "trigger_event_id": triggerEventId.Ref(), + "trigger_id": triggerId.Ref(), + }, + params: params, + timestamp: timestamp, + topics: topics, + triggerEventId: triggerEventId, + triggerId: triggerId, + } +} + +type simpleTriggerRequestPayload struct { + sdk.CapDefinition[TriggerRequestPayload] + params TriggerRequestPayloadParamsCap + timestamp sdk.CapDefinition[int64] + topics sdk.CapDefinition[[]string] + triggerEventId sdk.CapDefinition[string] + triggerId sdk.CapDefinition[string] +} + +func (c *simpleTriggerRequestPayload) Params() TriggerRequestPayloadParamsCap { + return c.params +} +func (c *simpleTriggerRequestPayload) Timestamp() sdk.CapDefinition[int64] { + return c.timestamp +} +func (c *simpleTriggerRequestPayload) Topics() sdk.CapDefinition[[]string] { + return c.topics +} +func (c *simpleTriggerRequestPayload) TriggerEventId() sdk.CapDefinition[string] { + return c.triggerEventId +} +func (c *simpleTriggerRequestPayload) TriggerId() sdk.CapDefinition[string] { + return c.triggerId +} + +func (c *simpleTriggerRequestPayload) private() {} + +type TriggerRequestPayloadParamsCap sdk.CapDefinition[TriggerRequestPayloadParams] diff --git a/core/capabilities/webapi/webapicap/webapicaptest/trigger_mock_generated.go b/core/capabilities/webapi/webapicap/webapicaptest/trigger_mock_generated.go new file mode 100644 index 00000000000..4eb384174d8 --- /dev/null +++ b/core/capabilities/webapi/webapicap/webapicaptest/trigger_mock_generated.go @@ -0,0 +1,17 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package webapicaptest + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/workflows/sdk/testutils" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/webapi/webapicap" +) + +// Trigger registers a new capability mock with the runner +func Trigger(runner *testutils.Runner, fn func() (webapicap.TriggerRequestPayload, error)) *testutils.TriggerMock[webapicap.TriggerRequestPayload] { + mock := testutils.MockTrigger[webapicap.TriggerRequestPayload]("web-trigger@1.0.0", fn) + runner.MockCapability("web-trigger@1.0.0", nil, mock) + return mock +} diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 69aba56208e..d6cb823c7bd 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -60,6 +60,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/XSAM/otelsql v0.27.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect + github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/avast/retry-go/v4 v4.6.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect @@ -154,6 +155,7 @@ require ( github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.3 // indirect @@ -198,6 +200,7 @@ require ( github.com/holiman/uint256 v1.2.4 // indirect github.com/huandu/skiplist v1.2.0 // indirect github.com/huin/goupnp v1.3.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect @@ -234,6 +237,7 @@ require ( github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -264,6 +268,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sanity-io/litter v1.5.5 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/scylladb/go-reflectx v1.0.1 // indirect @@ -351,6 +356,7 @@ require ( golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.25.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.15.0 // indirect google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index d71289f8bdc..afdf6cf8d2f 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -124,6 +124,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c h1:cxQVoh6kY+c4b0HUchHjGWBI8288VhH50qxKG3hdEg0= +github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c/go.mod h1:3XzxudkrYVUvbduN/uI2fl4lSrMSzU0+3RCu2mpnfx8= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -291,6 +293,7 @@ github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuA github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e h1:5jVSh2l/ho6ajWhSPNN84eHEdq3dp0T7+f6r3Tc6hsk= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e/go.mod h1:IJgIiGUARc4aOr4bOQ85klmjsShkEEfiRc6q/yBSfo8= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -480,6 +483,8 @@ github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= @@ -696,6 +701,8 @@ github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -880,6 +887,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -978,6 +987,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1050,6 +1060,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= @@ -1143,6 +1155,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1623,6 +1636,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 6f4f543e31b..82395b01e17 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -106,6 +106,7 @@ require ( github.com/alexflint/go-scalar v1.0.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect github.com/aws/aws-sdk-go-v2 v1.30.4 // indirect @@ -239,6 +240,7 @@ require ( github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -302,6 +304,7 @@ require ( github.com/huandu/skiplist v1.2.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/huin/goupnp v1.3.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect @@ -397,6 +400,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sanity-io/litter v1.5.5 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect @@ -488,6 +492,7 @@ require ( golang.org/x/term v0.24.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.25.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gonum.org/v1/gonum v0.15.0 // indirect google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 14fcfcdb26f..08ff8e9dbc7 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -200,6 +200,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c h1:cxQVoh6kY+c4b0HUchHjGWBI8288VhH50qxKG3hdEg0= +github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c/go.mod h1:3XzxudkrYVUvbduN/uI2fl4lSrMSzU0+3RCu2mpnfx8= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= @@ -429,6 +431,7 @@ github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuA github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e h1:5jVSh2l/ho6ajWhSPNN84eHEdq3dp0T7+f6r3Tc6hsk= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e/go.mod h1:IJgIiGUARc4aOr4bOQ85klmjsShkEEfiRc6q/yBSfo8= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -680,6 +683,8 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -960,6 +965,8 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -1311,6 +1318,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1377,6 +1385,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= @@ -1501,6 +1511,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -2067,6 +2078,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 89c27daf3b2..19647dbc280 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -66,6 +66,7 @@ require ( github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/avast/retry-go/v4 v4.6.0 // indirect github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect @@ -203,6 +204,7 @@ require ( github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -271,6 +273,7 @@ require ( github.com/huandu/skiplist v1.2.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/huin/goupnp v1.3.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect @@ -369,6 +372,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sanity-io/litter v1.5.5 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/scylladb/go-reflectx v1.0.1 // indirect @@ -479,6 +483,7 @@ require ( golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.25.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gonum.org/v1/gonum v0.15.0 // indirect google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 64b1eb29e33..97ebcec1c28 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -196,6 +196,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c h1:cxQVoh6kY+c4b0HUchHjGWBI8288VhH50qxKG3hdEg0= +github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c/go.mod h1:3XzxudkrYVUvbduN/uI2fl4lSrMSzU0+3RCu2mpnfx8= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= @@ -413,6 +415,7 @@ github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuA github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e h1:5jVSh2l/ho6ajWhSPNN84eHEdq3dp0T7+f6r3Tc6hsk= github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e/go.mod h1:IJgIiGUARc4aOr4bOQ85klmjsShkEEfiRc6q/yBSfo8= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -662,6 +665,8 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -944,6 +949,8 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -1289,6 +1296,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1355,6 +1363,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= @@ -1477,6 +1487,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -2041,6 +2052,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= From f905a4b5665fe139d86aebd018784fec5fb4785a Mon Sep 17 00:00:00 2001 From: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:55:53 -0700 Subject: [PATCH 28/28] Few Updates to make it deployments compatible (#14658) * few updates to make it deployment compatible * updates --- integration-tests/deployment/devenv/don.go | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/integration-tests/deployment/devenv/don.go b/integration-tests/deployment/devenv/don.go index 0b10bd091a8..555e2ce41dd 100644 --- a/integration-tests/deployment/devenv/don.go +++ b/integration-tests/deployment/devenv/don.go @@ -32,6 +32,7 @@ type NodeInfo struct { IsBootstrap bool // denotes if the node is a bootstrap node Name string // name of the node, used to identify the node, helpful in logs AdminAddr string // admin address to send payments to, applicable only for non-bootstrap nodes + MultiAddr string // multi address denoting node's FQN (needed for deriving P2PBootstrappers in OCR), applicable only for bootstrap nodes } type DON struct { @@ -72,7 +73,14 @@ func (don *DON) CreateSupportedChains(ctx context.Context, chains []ChainConfig, var err error for i := range don.Nodes { node := &don.Nodes[i] - if err1 := node.CreateCCIPOCRSupportedChains(ctx, chains, jd); err1 != nil { + var jdChains []JDChainConfigInput + for _, chain := range chains { + jdChains = append(jdChains, JDChainConfigInput{ + ChainID: chain.ChainID, + ChainType: chain.ChainType, + }) + } + if err1 := node.CreateCCIPOCRSupportedChains(ctx, jdChains, jd); err1 != nil { err = multierror.Append(err, err1) } don.Nodes[i] = *node @@ -97,8 +105,11 @@ func NewRegisteredDON(ctx context.Context, nodeInfo []NodeInfo, jd JobDistributo // node Labels so that it's easier to query them if info.IsBootstrap { // create multi address for OCR2, applicable only for bootstrap nodes - - node.multiAddr = fmt.Sprintf("%s:%s", info.CLConfig.InternalIP, info.P2PPort) + if info.MultiAddr == "" { + node.multiAddr = fmt.Sprintf("%s:%s", info.CLConfig.InternalIP, info.P2PPort) + } else { + node.multiAddr = info.MultiAddr + } // no need to set admin address for bootstrap nodes, as there will be no payment node.adminAddr = "" node.labels = append(node.labels, &ptypes.Label{ @@ -157,11 +168,16 @@ type Node struct { multiAddr string // multi address denoting node's FQN (needed for deriving P2PBootstrappers in OCR), applicable only for bootstrap nodes } +type JDChainConfigInput struct { + ChainID uint64 + ChainType string +} + // CreateCCIPOCRSupportedChains creates a JobDistributorChainConfig for the node. // It works under assumption that the node is already registered with the job distributor. // It expects bootstrap nodes to have label with key "type" and value as "bootstrap". // It fetches the account address, peer id, and OCR2 key bundle id and creates the JobDistributorChainConfig. -func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []ChainConfig, jd JobDistributor) error { +func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []JDChainConfigInput, jd JobDistributor) error { for i, chain := range chains { chainId := strconv.FormatUint(chain.ChainID, 10) accountAddr, err := n.gqlClient.FetchAccountAddress(ctx, chainId)