Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
54 changes: 54 additions & 0 deletions build/devenv/Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,57 @@ rebuild-all:
@just cli
@just build-docker-dev
ccv u

# Install and authenticate with GH CLI
setup-gh:
#!/usr/bin/env bash
set -euo pipefail

required="2.50.0"

# Convert semver to a zero-padded comparable number, e.g. 2.50.0 -> 002050000
ver() {
local v="${1#v}"
# Ensure we always have 3 fields by appending ".0.0"
echo "${v}.0.0" \
| awk -F. '{printf "%03d%03d%03d\n", ($1+0), ($2+0), ($3+0)}'
}

# 1) Ensure gh is installed (prefer Homebrew if available)
if ! command -v gh >/dev/null 2>&1; then
echo "GitHub CLI not found."
if command -v brew >/dev/null 2>&1; then
echo "Installing gh via Homebrew..."
brew install gh
else
echo "Homebrew not found. Please install gh manually (>= v${required})." >&2
exit 1
fi
fi

# 2) Check version
have_raw="$(gh --version | head -n1 | awk '{print $3}')"
have="${have_raw#v}"

if [[ "$(ver "$have")" -lt "$(ver "$required")" ]]; then
echo "Found gh v${have}, which is older than required v${required}."

# 3) If installed by Homebrew, upgrade; else error out
if command -v brew >/dev/null 2>&1 && brew list gh >/dev/null 2>&1; then
echo "gh appears to be managed by Homebrew. Upgrading..."
brew upgrade gh
else
echo "gh is not managed by Homebrew. Please upgrade manually to at least v${required}." >&2
exit 1
fi
else
echo "gh v${have} meets requirement (>= v${required})."
fi

# 4) Ensure authentication
if ! gh auth status >/dev/null 2>&1; then
echo "GitHub CLI not authenticated. Running gh auth login..."
gh auth login
else
echo "GitHub CLI is already authenticated."
fi
9 changes: 9 additions & 0 deletions build/devenv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ brew install just # click the link above if you are not on OS X
cd build/devenv
just clean-docker-dev # needed in case you have old JD image
just build-docker-dev
just setup-gh
just cli
```

Expand Down Expand Up @@ -136,3 +137,11 @@ Or by exporting the `DOCKER_HOST` variable:
```bash
export DOCKER_HOST unix://$HOME/.docker/desktop/docker.sock
```

## getDX tracking

getDX is used for tracking:
- success/failure rate of environment startup with:
- configuration files used
- truncated error message
- startup time
85 changes: 63 additions & 22 deletions build/devenv/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
chainsel "github.com/smartcontractkit/chain-selectors"
"github.com/smartcontractkit/chainlink-ccv/devenv/services"
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
"github.com/smartcontractkit/chainlink-deployments-framework/deployment"

"github.com/smartcontractkit/chainlink-testing-framework/framework"
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
Expand Down Expand Up @@ -95,19 +96,49 @@ func NewProductConfigurationFromNetwork(typ string) (cciptestinterfaces.CCIP17Pr
}

// NewEnvironment creates a new CCIP CCV environment either locally in Docker or remotely in K8s.
func NewEnvironment() (*Cfg, error) {
func NewEnvironment() (in *Cfg, err error) {
ctx := context.Background()
track := NewTimeTracker(Plog)
timeTrack := NewTimeTracker(Plog)
dxTracker := initDxTracker()

// track environment startup result and time using getDX app
defer func() {
duration := timeTrack.SinceStart().Seconds()
metaData := map[string]any{}
if err != nil {
metaData["result"] = "failure"
metaData["error"] = oneLineErrorMessage(err)
} else {
metaData["result"] = "success"
}
metaData["version"] = "1.7"
metaData["config_paths"] = os.Getenv(EnvVarTestConfigs)

resultErr := dxTracker.Track("ccip.startup.result", metaData)
if resultErr != nil {
fmt.Fprintf(os.Stderr, "failed to track environment startup result: %s\n", resultErr)
}

// send start up duration only if there was no error during startup
if err == nil {
metaData["duration_seconds"] = duration
timeErr := dxTracker.Track("ccip.startup.time", metaData)
if timeErr != nil {
fmt.Fprintf(os.Stderr, "failed to track environment startup time: %s\n", timeErr)
}
}
}()

ctx = L.WithContext(ctx)
if err := framework.DefaultNetwork(nil); err != nil {
if err = framework.DefaultNetwork(nil); err != nil {
return nil, err
}

in, err := Load[Cfg](strings.Split(os.Getenv(EnvVarTestConfigs), ","))
in, err = Load[Cfg](strings.Split(os.Getenv(EnvVarTestConfigs), ","))
if err != nil {
return nil, fmt.Errorf("failed to load configuration: %w", err)
}
if err := checkKeys(in); err != nil {
if err = checkKeys(in); err != nil {
return nil, err
}

Expand All @@ -118,14 +149,15 @@ func NewEnvironment() (*Cfg, error) {

impls := make([]cciptestinterfaces.CCIP17ProductConfiguration, 0)
for _, bc := range in.Blockchains {
impl, err := NewProductConfigurationFromNetwork(bc.Type)
var impl cciptestinterfaces.CCIP17ProductConfiguration
impl, err = NewProductConfigurationFromNetwork(bc.Type)
if err != nil {
return nil, err
}
impls = append(impls, impl)
}
for i, impl := range impls {
_, err := impl.DeployLocalNetwork(ctx, in.Blockchains[i])
_, err = impl.DeployLocalNetwork(ctx, in.Blockchains[i])
if err != nil {
return nil, fmt.Errorf("failed to deploy local networks: %w", err)
}
Expand All @@ -141,12 +173,13 @@ func NewEnvironment() (*Cfg, error) {
return nil, fmt.Errorf("failed to create aggregator service: %w", err)
}

track.Record("[infra] deploying blockchains")
timeTrack.Record("[infra] deploying blockchains")

clChainConfigs := make([]string, 0)
clChainConfigs = append(clChainConfigs, CommonCLNodesConfig)
for i, impl := range impls {
clChainConfig, err := impl.ConfigureNodes(ctx, in.Blockchains[i])
var clChainConfig string
clChainConfig, err = impl.ConfigureNodes(ctx, in.Blockchains[i])
if err != nil {
return nil, fmt.Errorf("failed to deploy local networks: %w", err)
}
Expand All @@ -158,52 +191,59 @@ func NewEnvironment() (*Cfg, error) {
}
Plog.Info().Msg("Nodes network configuration is generated")

track.Record("[changeset] configured nodes network")
timeTrack.Record("[changeset] configured nodes network")
_, err = ns.NewSharedDBNodeSet(in.NodeSets[0], nil)
if err != nil {
return nil, fmt.Errorf("failed to create new shared db node set: %w", err)
}

var selectors []uint64
var e *deployment.Environment
// the CLDF datastore is not initialized at this point because contracts are not deployed yet.
// it will get populated in the loop below.
in.CLDF.Init()
selectors, e, err := NewCLDFOperationsEnvironment(in.Blockchains, in.CLDF.DataStore)
selectors, e, err = NewCLDFOperationsEnvironment(in.Blockchains, in.CLDF.DataStore)
if err != nil {
return nil, fmt.Errorf("creating CLDF operations environment: %w", err)
}
L.Info().Any("Selectors", selectors).Msg("Deploying for chain selectors")

ds := datastore.NewMemoryDataStore()
for i, impl := range impls {
if err := impl.FundNodes(ctx, in.NodeSets, in.Blockchains[i], big.NewInt(1), big.NewInt(5)); err != nil {
if err = impl.FundNodes(ctx, in.NodeSets, in.Blockchains[i], big.NewInt(1), big.NewInt(5)); err != nil {
return nil, err
}
networkInfo, err := chainsel.GetChainDetailsByChainIDAndFamily(in.Blockchains[i].ChainID, chainsel.FamilyEVM)
var networkInfo chainsel.ChainDetails
networkInfo, err = chainsel.GetChainDetailsByChainIDAndFamily(in.Blockchains[i].ChainID, chainsel.FamilyEVM)
if err != nil {
return nil, err
}
L.Info().Uint64("Selector", networkInfo.ChainSelector).Msg("Deployed chain selector")
dsi, err := impl.DeployContractsForSelector(ctx, e, networkInfo.ChainSelector)
var dsi datastore.DataStore
dsi, err = impl.DeployContractsForSelector(ctx, e, networkInfo.ChainSelector)
if err != nil {
return nil, err
}
addresses, err := dsi.Addresses().Fetch()
var addresses []datastore.AddressRef
addresses, err = dsi.Addresses().Fetch()
if err != nil {
return nil, err
}
a, err := json.Marshal(addresses)
var a []byte
a, err = json.Marshal(addresses)
if err != nil {
return nil, err
}
in.CLDF.AddAddresses(string(a))
if err := ds.Merge(dsi); err != nil {
if err = ds.Merge(dsi); err != nil {
return nil, err
}
}
e.DataStore = ds.Seal()

for i, impl := range impls {
networkInfo, err := chainsel.GetChainDetailsByChainIDAndFamily(in.Blockchains[i].ChainID, chainsel.FamilyEVM)
var networkInfo chainsel.ChainDetails
networkInfo, err = chainsel.GetChainDetailsByChainIDAndFamily(in.Blockchains[i].ChainID, chainsel.FamilyEVM)
if err != nil {
return nil, err
}
Expand All @@ -219,8 +259,8 @@ func NewEnvironment() (*Cfg, error) {
}
}

track.Record("[infra] deployed CL nodes")
track.Record("[changeset] deployed product contracts")
timeTrack.Record("[infra] deployed CL nodes")
timeTrack.Record("[changeset] deployed product contracts")

_, err = services.NewExecutor(in.Executor)
if err != nil {
Expand All @@ -241,9 +281,10 @@ func NewEnvironment() (*Cfg, error) {
Plog.Info().Str("Node", n.Node.ExternalURL).Send()
}

track.Print()
if err := PrintCLDFAddresses(in); err != nil {
timeTrack.Print()
if err = PrintCLDFAddresses(in); err != nil {
return nil, err
}

return in, Store(in)
}
2 changes: 1 addition & 1 deletion build/devenv/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/smartcontractkit/chainlink-ccv/protocol v0.0.0-20251003202113-d3a7c6246cfc
github.com/smartcontractkit/chainlink-common v0.9.6-0.20250929154511-1f5fbda7ae76
github.com/smartcontractkit/chainlink-deployments-framework v0.54.1
github.com/smartcontractkit/chainlink-testing-framework/framework v0.10.33
github.com/smartcontractkit/chainlink-testing-framework/framework v0.10.36-0.20251013100933-b81e7d3a1ae9
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.11.1
github.com/testcontainers/testcontainers-go v0.39.0
Expand Down
4 changes: 2 additions & 2 deletions build/devenv/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1061,8 +1061,8 @@ github.com/smartcontractkit/chainlink-protos/job-distributor v0.13.1 h1:PWwLGimB
github.com/smartcontractkit/chainlink-protos/job-distributor v0.13.1/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE=
github.com/smartcontractkit/chainlink-sui v0.0.0-20250916193659-4becc28a467f h1:7saUNbu+edzDgRPedNFfTsx5+5RL40r1r0pgISoh8Hs=
github.com/smartcontractkit/chainlink-sui v0.0.0-20250916193659-4becc28a467f/go.mod h1:CTR5agBB07sCpRltBkHmnkCZ+g8sXRafCJge/Hqr7aM=
github.com/smartcontractkit/chainlink-testing-framework/framework v0.10.33 h1:1QSTIKM4zVEVSUJq89NKstJpiiQuFlJlRY2A3TZS5r8=
github.com/smartcontractkit/chainlink-testing-framework/framework v0.10.33/go.mod h1:SoCjdzeZHP500QtKAjJ9I6rHD03SkQmRL4dNkOoe6yk=
github.com/smartcontractkit/chainlink-testing-framework/framework v0.10.36-0.20251013100933-b81e7d3a1ae9 h1:1wQGIhRRkcibgr79iU++ao0zKrSGagJ+qwoHAU6X6dE=
github.com/smartcontractkit/chainlink-testing-framework/framework v0.10.36-0.20251013100933-b81e7d3a1ae9/go.mod h1:ssfyl4ynbxSyASGztjuAxhsum5i6uZSHM7Dd0v2p8sc=
github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 h1:VIxK8u0Jd0Q/VuhmsNm6Bls6Tb31H/sA3A/rbc5hnhg=
github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0/go.mod h1:lyAu+oMXdNUzEDScj2DXB2IueY+SDXPPfyl/kb63tMM=
github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 h1:ZJ/8Jx6Be5//TyjPi1pS1uotnmcYq5vVkSyISIymSj8=
Expand Down
4 changes: 4 additions & 0 deletions build/devenv/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func (t *TimeTracker) Print() {
Msg("Total environment boot up time")
}

func (t *TimeTracker) SinceStart() time.Duration {
return time.Since(t.start)
}

func PrintCLDFAddresses(in *Cfg) error {
for _, addr := range in.CLDF.Addresses {
var refs []datastore.AddressRef
Expand Down
28 changes: 28 additions & 0 deletions build/devenv/tracking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ccv

import (
"fmt"
"os"
"strings"

"github.com/smartcontractkit/chainlink-testing-framework/framework/tracking"
)

func initDxTracker() tracking.Tracker {
var trackerErr error
var dxTracker tracking.Tracker
dxTracker, trackerErr = tracking.NewDxTracker("API_TOKEN_CCIP", "ccip")
if trackerErr != nil {
fmt.Fprintf(os.Stderr, "failed to create getDX tracker: %s\n", trackerErr)
dxTracker = &tracking.NoOpTracker{}
}
return dxTracker
}

func oneLineErrorMessage(errOrPanic any) string {
if err, ok := errOrPanic.(error); ok {
return strings.SplitN(err.Error(), "\n", 1)[0]
}

return strings.SplitN(fmt.Sprintf("%v", errOrPanic), "\n", 1)[0]
}
Loading