Skip to content

Commit 3b9fc53

Browse files
fix(mcms): select public rpc for fork tests
1 parent 1b9e949 commit 3b9fc53

4 files changed

Lines changed: 145 additions & 6 deletions

File tree

.changeset/fine-cups-battle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": patch
3+
---
4+
5+
fix(mcms): select public rpc for fork tests

engine/cld/environment/anvil.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"math/big"
99
"math/rand/v2"
1010
"os"
11+
"regexp"
1112
"slices"
1213
"strconv"
1314
"sync"
@@ -30,9 +31,7 @@ import (
3031
cfgnet "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config/network"
3132
)
3233

33-
var (
34-
oneEth = big.NewInt(0).Exp(big.NewInt(10), big.NewInt(18), nil)
35-
)
34+
var oneEth = big.NewInt(0).Exp(big.NewInt(10), big.NewInt(18), nil)
3635

3736
// anvilClient operates the methods exposed by the Anvil node related to forking.
3837
// For more information, see https://book.getfoundry.sh/reference/anvil/#custom-methods.
@@ -203,9 +202,8 @@ func newAnvilChains(
203202
}
204203
network.Metadata = cfgnet.EVMMetadata{
205204
AnvilConfig: &cfgnet.AnvilConfig{
206-
Image: "f4hrenh9it/foundry:latest",
207-
Port: uint64(ports[0]), //nolint:gosec // G115: int to uint64 conversion is safe here (port numbers are always in valid range)
208-
ArchiveHTTPURL: network.RPCs[0].HTTPURL,
205+
Image: "f4hrenh9it/foundry:latest",
206+
Port: uint64(ports[0]), //nolint:gosec // G115: int to uint64 conversion is safe here (port numbers are always in valid range)
209207
},
210208
}
211209
}
@@ -222,6 +220,11 @@ func newAnvilChains(
222220
continue
223221
}
224222

223+
if err := selectPublicRPC(lggr, &metadata, network.ChainSelector, network.RPCs); err != nil {
224+
lggr.Infof("Excluding chain with ID %d from environment: %s", chainID, err.Error())
225+
continue
226+
}
227+
225228
// Skip chains that are not included in the address book
226229
if _, ok := addressesByChain[chainSelector]; !ok {
227230
lggr.Infof("Excluding chain with selector %d from environment, does not have addresses defined in the address book", chainSelector)
@@ -301,3 +304,28 @@ func newAnvilChains(
301304
ChainConfigs: chainConfigsBySelector,
302305
}, nil
303306
}
307+
308+
func selectPublicRPC(
309+
lggr logger.Logger, metadata *cfgnet.EVMMetadata, chainSelector uint64, rpcs []cfgnet.RPC,
310+
) error {
311+
if isPublicRPC(metadata.AnvilConfig.ArchiveHTTPURL) {
312+
return nil
313+
}
314+
315+
for _, rpc := range rpcs {
316+
if isPublicRPC(rpc.HTTPURL) {
317+
metadata.AnvilConfig.ArchiveHTTPURL = rpc.HTTPURL
318+
lggr.Infow("selected rpc for fork environment", "url", rpc.HTTPURL, "chainSelector", chainSelector)
319+
320+
return nil
321+
}
322+
}
323+
324+
return fmt.Errorf("no public RPCs found for chain %d", chainSelector)
325+
}
326+
327+
var privateRpcRegexp = regexp.MustCompile(`^https?://(rpcs|gap\-.*\.(prod|stage))\.cldev\.sh/`)
328+
329+
func isPublicRPC(url string) bool {
330+
return !privateRpcRegexp.MatchString(url)
331+
}

engine/cld/environment/anvil_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"github.com/go-resty/resty/v2"
1212
"github.com/stretchr/testify/assert"
1313
"github.com/stretchr/testify/require"
14+
15+
cfgnet "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config/network"
16+
"github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger"
1417
)
1518

1619
// JSONRPCRequest represents a JSON-RPC request
@@ -126,3 +129,102 @@ func Test_AnvilClient_SendTransaction(t *testing.T) {
126129
})
127130
}
128131
}
132+
133+
func Test_isPublicRPC(t *testing.T) {
134+
t.Parallel()
135+
tests := []struct {
136+
url string
137+
want bool
138+
}{
139+
{"http://rpcs.cldev.sh/", false},
140+
{"https://rpcs.cldev.sh/", false},
141+
{"https://rpcs.cldev.sh/anything", false},
142+
{"https://gap-rpcs.stage.cldev.sh/anything", false},
143+
{"https://gap-rpcs.prod.cldev.sh/anything", false},
144+
{"https://gap-other.prod.cldev.sh/anything", false},
145+
{"https://gap-other.stage.cldev.sh/anything", false},
146+
{"https://gap-other.stage.cldev.sh/anything", false},
147+
{"https://gap-grpc-job-distributor.public.main.prod.cldev.sh/", false},
148+
{"https://gap-ws-job-distributor.public.main.prod.cldev.sh/", false},
149+
{"https://gap-rpc-proxy.public.main.prod.cldev.sh/", false},
150+
{"https://gap-grpc-job-distributor.public.main.stage.cldev.sh/", false},
151+
{"https://gap-ws-job-distributor.public.main.stage.cldev.sh/", false},
152+
{"https://gap-grpc-chainlink-catalog.public.main.stage.cldev.sh/", false},
153+
{"", true},
154+
{"http://", true},
155+
{"https://", true},
156+
{"https://rpcs.cldev.sh", true},
157+
{"https://rpcs.prod.cldev.sh/anything", true},
158+
{"https://rpcs.stage.cldev.sh/anything", true},
159+
{"https://gap.stage.cldev.sh/anything", true},
160+
}
161+
for _, tt := range tests {
162+
t.Run(tt.url, func(t *testing.T) {
163+
t.Parallel()
164+
require.Equal(t, tt.want, isPublicRPC(tt.url))
165+
})
166+
}
167+
}
168+
169+
func Test_selectPublicRPC(t *testing.T) {
170+
t.Parallel()
171+
172+
lggr := logger.Test(t)
173+
tests := []struct {
174+
name string
175+
metadata *cfgnet.EVMMetadata
176+
chainSelector uint64
177+
rpcs []cfgnet.RPC
178+
want *cfgnet.EVMMetadata
179+
wantErr string
180+
}{
181+
{
182+
name: "success: metadata has url",
183+
metadata: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
184+
ArchiveHTTPURL: "http://metadata.url",
185+
}},
186+
rpcs: []cfgnet.RPC{
187+
{HTTPURL: "http://other.url"},
188+
},
189+
want: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
190+
ArchiveHTTPURL: "http://metadata.url",
191+
}},
192+
},
193+
{
194+
name: "success: private rpc in metadata is replaced public url from parameters",
195+
metadata: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
196+
ArchiveHTTPURL: "http://gap-rpc.prod.cldev.sh/ethereum/sepolia",
197+
}},
198+
rpcs: []cfgnet.RPC{
199+
{HTTPURL: "http://rpcs.cldev.sh/ethereum/sepolia"},
200+
{HTTPURL: "http://public.rpc.url"},
201+
},
202+
want: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
203+
ArchiveHTTPURL: "http://public.rpc.url",
204+
}},
205+
},
206+
{
207+
name: "failure: no public rpcs found",
208+
metadata: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
209+
ArchiveHTTPURL: "http://gap-rpc.prod.cldev.sh/ethereum/sepolia",
210+
}},
211+
rpcs: []cfgnet.RPC{
212+
{HTTPURL: "http://rpcs.cldev.sh/ethereum/sepolia"},
213+
},
214+
wantErr: "no public RPCs found for chain 0",
215+
},
216+
}
217+
for _, tt := range tests {
218+
t.Run(tt.name, func(t *testing.T) {
219+
t.Parallel()
220+
221+
err := selectPublicRPC(lggr, tt.metadata, tt.chainSelector, tt.rpcs)
222+
if tt.wantErr == "" {
223+
require.NoError(t, err)
224+
require.Equal(t, tt.want, tt.metadata)
225+
} else {
226+
require.ErrorContains(t, err, tt.wantErr)
227+
}
228+
})
229+
}
230+
}

engine/cld/legacy/cli/mcmsv2/mcms_v2.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,10 @@ func buildExecuteForkCommand(lggr logger.Logger, domain cldf_domain.Domain, prop
620620
return fmt.Errorf("error creating config: %w", err)
621621
}
622622

623+
if len(cfg.forkedEnv.ChainConfigs[cfg.chainSelector].HTTPRPCs) == 0 {
624+
return fmt.Errorf("no rpcs loaded in forked environment for chain %d (fork tests require public RPCs)", cfg.chainSelector)
625+
}
626+
623627
// get the chain URL, chain ID and MCM contract address
624628
url := cfg.forkedEnv.ChainConfigs[cfg.chainSelector].HTTPRPCs[0].External
625629
anvilClient := rpc.New(url, nil)

0 commit comments

Comments
 (0)