Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to deploy CCIP Solana to staging #16160

Closed
wants to merge 13 commits into from
5 changes: 1 addition & 4 deletions deployment/ccip/changeset/cs_deploy_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,8 @@ func deployChainContractsForChains(
deployGrp := errgroup.Group{}

for chainSel, contractParams := range contractParamsPerChain {
if _, exists := existingState.SupportedChains()[chainSel]; !exists {
return fmt.Errorf("chain %d not supported", chainSel)
}
// already validated family
family, _ := chainsel.GetSelectorFamily(chainSel)

var deployFn func() error
switch family {
case chainsel.FamilyEVM:
Expand Down
9 changes: 5 additions & 4 deletions deployment/common/changeset/deploy_link_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,13 @@

// DeployLinkToken deploys a link token contract to the chain identified by the ChainSelector.
func DeployLinkToken(e deployment.Environment, chains []uint64) (deployment.ChangesetOutput, error) {
err := deployment.ValidateSelectorsInEnvironment(e, chains)
if err != nil {
return deployment.ChangesetOutput{}, err
}
newAddresses := deployment.NewMemoryAddressBook()
for _, chain := range chains {
family, err := chainsel.GetSelectorFamily(chain)
if err != nil {
return deployment.ChangesetOutput{AddressBook: newAddresses}, err
}

switch family {
case chainsel.FamilyEVM:
// Deploy EVM LINK token
Expand Down Expand Up @@ -123,6 +120,10 @@
chain deployment.SolChain,
ab deployment.AddressBook,
) error {
if chain.DeployerKey == nil {
return fmt.Errorf("deployer key must be set")

Check failure on line 124 in deployment/common/changeset/deploy_link_token.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (deployment)

fmt.Errorf can be replaced with errors.New (perfsprint)
}

tokenAdminPubKey := chain.DeployerKey.PublicKey()
mint, _ := solana.NewRandomPrivateKey()
mintPublicKey := mint.PublicKey() // this is the token address
Expand Down
25 changes: 13 additions & 12 deletions deployment/environment/memory/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,10 @@ func getTestSolanaChainSelectors() []uint64 {
return result
}

func generateSolanaKeypair(t testing.TB) (solana.PrivateKey, string, error) {
// Create a temporary directory that will be cleaned up after the test
tmpDir := t.TempDir()

func generateSolanaKeypair(dir string) (solana.PrivateKey, error) {
privateKey, err := solana.NewRandomPrivateKey()
if err != nil {
return solana.PrivateKey{}, "", fmt.Errorf("failed to generate private key: %w", err)
return solana.PrivateKey{}, fmt.Errorf("failed to generate private key: %w", err)
}

// Convert private key bytes to JSON array
Expand All @@ -107,27 +104,31 @@ func generateSolanaKeypair(t testing.TB) (solana.PrivateKey, string, error) {

keypairJSON, err := json.Marshal(intArray)
if err != nil {
return solana.PrivateKey{}, "", fmt.Errorf("failed to marshal keypair: %w", err)
return solana.PrivateKey{}, fmt.Errorf("failed to marshal keypair: %w", err)
}

// Create the keypair file in the temporary directory
keypairPath := filepath.Join(tmpDir, "solana-keypair.json")
// Create the keypair file in the directory
keypairPath := filepath.Join(dir, "solana-keypair.json")
if err := os.WriteFile(keypairPath, keypairJSON, 0600); err != nil {
return solana.PrivateKey{}, "", fmt.Errorf("failed to write keypair to file: %w", err)
return solana.PrivateKey{}, fmt.Errorf("failed to write keypair to file: %w", err)
}

return privateKey, keypairPath, nil
return privateKey, nil
}

func GenerateChainsSol(t *testing.T, numChains int) map[uint64]SolanaChain {
testSolanaChainSelectors := getTestSolanaChainSelectors()
if len(testSolanaChainSelectors) < numChains {
t.Fatalf("not enough test solana chain selectors available")
}

// Create a temporary directory that will be cleaned up after the test
tmpDir := t.TempDir()

chains := make(map[uint64]SolanaChain)
for i := 0; i < numChains; i++ {
chainID := testSolanaChainSelectors[i]
admin, keypairPath, err := generateSolanaKeypair(t)
admin, err := generateSolanaKeypair(tmpDir)
require.NoError(t, err)
url, wsURL, err := solChain(t, chainID, &admin)
require.NoError(t, err)
Expand All @@ -140,7 +141,7 @@ func GenerateChainsSol(t *testing.T, numChains int) map[uint64]SolanaChain {
DeployerKey: admin,
URL: url,
WSURL: wsURL,
KeypairPath: keypairPath,
KeypairPath: tmpDir,
}
}
return chains
Expand Down
54 changes: 30 additions & 24 deletions deployment/environment/memory/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,36 @@ func NewMemoryChains(t *testing.T, numChains int, numUsers int) (map[uint64]depl

func NewMemoryChainsSol(t *testing.T, numChains int) map[uint64]deployment.SolChain {
mchains := GenerateChainsSol(t, numChains)
return generateMemoryChainSol(mchains)

chains := make(map[uint64]deployment.SolChain)

for cid, chainConfig := range mchains {
chains[cid] = deployment.SolChain{
Selector: cid,
Client: chainConfig.Client,
DeployerKey: &chainConfig.DeployerKey,
URL: chainConfig.URL,
WSURL: chainConfig.WSURL,
KeypairPath: chainConfig.KeypairPath,
ProgramsPath: ProgramsPath,
Confirm: func(instructions []solana.Instruction, opts ...solCommonUtil.TxModifier) error {
_, err := solCommonUtil.SendAndConfirm(
context.Background(),
chainConfig.Client,
instructions,
chainConfig.DeployerKey,
solRpc.CommitmentConfirmed,
opts...,
)
if err != nil {
return err
}
return nil
},
}
}

return chains
}

func NewMemoryChainsWithChainIDs(t *testing.T, chainIDs []uint64, numUsers int) (map[uint64]deployment.Chain, map[uint64][]*bind.TransactOpts) {
Expand Down Expand Up @@ -137,29 +166,6 @@ func generateMemoryChain(t *testing.T, inputs map[uint64]EVMChain) map[uint64]de
return chains
}

func generateMemoryChainSol(inputs map[uint64]SolanaChain) map[uint64]deployment.SolChain {
chains := make(map[uint64]deployment.SolChain)
for cid, chain := range inputs {
chain := chain
chains[cid] = deployment.SolChain{
Selector: cid,
Client: chain.Client,
DeployerKey: &chain.DeployerKey,
URL: chain.URL,
WSURL: chain.WSURL,
KeypairPath: chain.KeypairPath,
ProgramsPath: ProgramsPath,
Confirm: func(instructions []solana.Instruction, opts ...solCommonUtil.TxModifier) error {
_, err := solCommonUtil.SendAndConfirm(
context.Background(), chain.Client, instructions, chain.DeployerKey, solRpc.CommitmentConfirmed, opts...,
)
return err
},
}
}
return chains
}

func NewNodes(t *testing.T, logLevel zapcore.Level, chains map[uint64]deployment.Chain, solChains map[uint64]deployment.SolChain, numNodes, numBootstraps int, registryConfig deployment.CapabilityRegistryConfig) map[string]Node {
nodesByPeerID := make(map[string]Node)
if numNodes+numBootstraps == 0 {
Expand Down
98 changes: 80 additions & 18 deletions deployment/solana_chain.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package deployment

import (
"bufio"
"bytes"
"context"
"fmt"
Expand All @@ -9,6 +10,7 @@
"path/filepath"
"strconv"
"strings"
"syscall"
"time"

"github.com/gagliardetto/solana-go"
Expand Down Expand Up @@ -83,36 +85,96 @@
"--url", c.URL, // rpc url
}

var cmd *exec.Cmd
if _, err := os.Stat(programKeyPair); err == nil {
// Keypair exists, include program-id
baseArgs = append(baseArgs, "--program-id", programKeyPair)
logger.Infow("Deploying program with existing keypair",
"programFile", programFile,
"programKeyPair", programKeyPair)
cmd = exec.Command("solana", append(baseArgs, "--program-id", programKeyPair)...) // #nosec G204
} else {
// Keypairs wont be created for devenvs
logger.Infow("Deploying new program",
"programFile", programFile)
cmd = exec.Command("solana", baseArgs...) // #nosec G204
logger.Infow("Deploying new program", "programFile", programFile)
}

// Capture the command output
// Create context with timeout to run the deploy command
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

// Create command without context
cmd := exec.Command("solana", baseArgs...)
logger.Infow("Running deploy program command", "cmd", strings.Join(cmd.Args, " "))

// Connect standard streams with both buffer and real-time logging
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Stdin = os.Stdin

// Set up pipe for stdout
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return "", fmt.Errorf("error creating stdout pipe: %w", err)
}
// Set up pipe for stderr
stderrPipe, err := cmd.StderrPipe()
if err != nil {
return "", fmt.Errorf("error creating stderr pipe: %w", err)
}

// Run the command
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("error deploying program: %s: %s", err.Error(), stderr.String())
// Start the command
if err := cmd.Start(); err != nil {
return "", fmt.Errorf("error starting program deployment: %w", err)
}

// Parse and return the program ID
output := stdout.String()
// Create a channel to signal command completion
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()

// Copy output to both buffer and logger in real-time
go func() {
scanner := bufio.NewScanner(stdoutPipe)
for scanner.Scan() {
line := scanner.Text()
logger.Infow("Program deployment stdout", "line", line)
stdout.WriteString(line + "\n")
}
}()

go func() {
scanner := bufio.NewScanner(stderrPipe)
for scanner.Scan() {
line := scanner.Text()
logger.Infow("Program deployment stderr", "line", line)
stderr.WriteString(line + "\n")
}
}()

// Wait for either completion or timeout
select {
case <-ctx.Done():
logger.Errorw("Program deployment timed out",
"stdout", stdout.String(),
"stderr", stderr.String())
// Try to kill the process and its children
pgid, err := syscall.Getpgid(cmd.Process.Pid)
if err == nil {
syscall.Kill(-pgid, syscall.SIGTERM)

Check failure on line 159 in deployment/solana_chain.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (deployment)

Error return value of `syscall.Kill` is not checked (errcheck)
}
cmd.Process.Kill()

Check failure on line 161 in deployment/solana_chain.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (deployment)

Error return value of `cmd.Process.Kill` is not checked (errcheck)
return "", fmt.Errorf("deployment timed out after 5 minutes")

Check failure on line 162 in deployment/solana_chain.go

View workflow job for this annotation

GitHub Actions / GolangCI Lint (deployment)

fmt.Errorf can be replaced with errors.New (perfsprint)
case err := <-done:
if err != nil {
logger.Errorw("Program deployment failed",
"error", err,
"stdout", stdout.String(),
"stderr", stderr.String())
return "", fmt.Errorf("error deploying program: %s: %s", err.Error(), stderr.String())
}
}

// TODO: obviously need to do this better
time.Sleep(5 * time.Second)
return parseProgramID(output)
outputStr := stdout.String()
logger.Infow("Program deployment successful",
"stdout", outputStr,
"stderr", stderr.String())
return parseProgramID(outputStr)
}

func (c SolChain) GetAccountDataBorshInto(ctx context.Context, pubkey solana.PublicKey, accountState interface{}) error {
Expand Down
Loading