Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fine-cups-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink-deployments-framework": patch
---

fix(mcms): select public rpc for fork tests
40 changes: 34 additions & 6 deletions engine/cld/environment/anvil.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math/big"
"math/rand/v2"
"os"
"regexp"
"slices"
"strconv"
"sync"
Expand All @@ -30,9 +31,7 @@ import (
cfgnet "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config/network"
)

var (
oneEth = big.NewInt(0).Exp(big.NewInt(10), big.NewInt(18), nil)
)
var oneEth = big.NewInt(0).Exp(big.NewInt(10), big.NewInt(18), nil)

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

if err := selectPublicRPC(lggr, &metadata, network.ChainSelector, network.RPCs); err != nil {
lggr.Infof("Excluding chain with ID %d from environment: %s", chainID, err.Error())
continue
}

// Skip chains that are not included in the address book
if _, ok := addressesByChain[chainSelector]; !ok {
lggr.Infof("Excluding chain with selector %d from environment, does not have addresses defined in the address book", chainSelector)
Expand Down Expand Up @@ -301,3 +304,28 @@ func newAnvilChains(
ChainConfigs: chainConfigsBySelector,
}, nil
}

func selectPublicRPC(
lggr logger.Logger, metadata *cfgnet.EVMMetadata, chainSelector uint64, rpcs []cfgnet.RPC,
) error {
if isPublicRPC(metadata.AnvilConfig.ArchiveHTTPURL) {
return nil
}

for _, rpc := range rpcs {
if isPublicRPC(rpc.HTTPURL) {
metadata.AnvilConfig.ArchiveHTTPURL = rpc.HTTPURL
lggr.Infow("selected rpc for fork environment", "url", rpc.HTTPURL, "chainSelector", chainSelector)

return nil
}
}

return fmt.Errorf("no public RPCs found for chain %d", chainSelector)
}

var privateRpcRegexp = regexp.MustCompile(`^https?://(rpcs|gap\-.*\.(prod|stage))\.cldev\.sh/`)
Comment thread
gustavogama-cll marked this conversation as resolved.
Comment thread
gustavogama-cll marked this conversation as resolved.

func isPublicRPC(url string) bool {
return !privateRpcRegexp.MatchString(url)
}
102 changes: 102 additions & 0 deletions engine/cld/environment/anvil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"github.com/go-resty/resty/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

cfgnet "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config/network"
"github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger"
)

// JSONRPCRequest represents a JSON-RPC request
Expand Down Expand Up @@ -126,3 +129,102 @@ func Test_AnvilClient_SendTransaction(t *testing.T) {
})
}
}

func Test_isPublicRPC(t *testing.T) {
t.Parallel()
tests := []struct {
url string
want bool
}{
{"http://rpcs.cldev.sh/", false},
{"https://rpcs.cldev.sh/", false},
{"https://rpcs.cldev.sh/anything", false},
{"https://gap-rpcs.stage.cldev.sh/anything", false},
{"https://gap-rpcs.prod.cldev.sh/anything", false},
{"https://gap-other.prod.cldev.sh/anything", false},
{"https://gap-other.stage.cldev.sh/anything", false},
{"https://gap-other.stage.cldev.sh/anything", false},
{"https://gap-grpc-job-distributor.public.main.prod.cldev.sh/", false},
{"https://gap-ws-job-distributor.public.main.prod.cldev.sh/", false},
{"https://gap-rpc-proxy.public.main.prod.cldev.sh/", false},
{"https://gap-grpc-job-distributor.public.main.stage.cldev.sh/", false},
{"https://gap-ws-job-distributor.public.main.stage.cldev.sh/", false},
{"https://gap-grpc-chainlink-catalog.public.main.stage.cldev.sh/", false},
{"", true},
{"http://", true},
{"https://", true},
{"https://rpcs.cldev.sh", true},
{"https://rpcs.prod.cldev.sh/anything", true},
{"https://rpcs.stage.cldev.sh/anything", true},
{"https://gap.stage.cldev.sh/anything", true},
}
for _, tt := range tests {
t.Run(tt.url, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, isPublicRPC(tt.url))
})
}
}

func Test_selectPublicRPC(t *testing.T) {
t.Parallel()

lggr := logger.Test(t)
tests := []struct {
name string
metadata *cfgnet.EVMMetadata
chainSelector uint64
rpcs []cfgnet.RPC
want *cfgnet.EVMMetadata
wantErr string
}{
{
name: "success: metadata has url",
metadata: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
ArchiveHTTPURL: "http://metadata.url",
}},
rpcs: []cfgnet.RPC{
{HTTPURL: "http://other.url"},
},
want: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
ArchiveHTTPURL: "http://metadata.url",
}},
},
{
name: "success: private rpc in metadata is replaced public url from parameters",
metadata: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
ArchiveHTTPURL: "http://gap-rpc.prod.cldev.sh/ethereum/sepolia",
}},
rpcs: []cfgnet.RPC{
{HTTPURL: "http://rpcs.cldev.sh/ethereum/sepolia"},
{HTTPURL: "http://public.rpc.url"},
},
want: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
ArchiveHTTPURL: "http://public.rpc.url",
}},
},
{
name: "failure: no public rpcs found",
metadata: &cfgnet.EVMMetadata{AnvilConfig: &cfgnet.AnvilConfig{
ArchiveHTTPURL: "http://gap-rpc.prod.cldev.sh/ethereum/sepolia",
}},
rpcs: []cfgnet.RPC{
{HTTPURL: "http://rpcs.cldev.sh/ethereum/sepolia"},
},
wantErr: "no public RPCs found for chain 0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := selectPublicRPC(lggr, tt.metadata, tt.chainSelector, tt.rpcs)
if tt.wantErr == "" {
require.NoError(t, err)
require.Equal(t, tt.want, tt.metadata)
} else {
require.ErrorContains(t, err, tt.wantErr)
}
})
}
}
4 changes: 4 additions & 0 deletions engine/cld/legacy/cli/mcmsv2/mcms_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,10 @@ func buildExecuteForkCommand(lggr logger.Logger, domain cldf_domain.Domain, prop
return fmt.Errorf("error creating config: %w", err)
}

if len(cfg.forkedEnv.ChainConfigs[cfg.chainSelector].HTTPRPCs) == 0 {
return fmt.Errorf("no rpcs loaded in forked environment for chain %d (fork tests require public RPCs)", cfg.chainSelector)
}

// get the chain URL, chain ID and MCM contract address
url := cfg.forkedEnv.ChainConfigs[cfg.chainSelector].HTTPRPCs[0].External
anvilClient := rpc.New(url, nil)
Expand Down
Loading