Skip to content

[CLD-148]: fix(chains): migration evm,sol,aptos chains #16

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

Closed
wants to merge 3 commits into from
Closed
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
14 changes: 0 additions & 14 deletions .github/workflows/push-tag-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,3 @@ jobs:
goreleaser-version: '~> v2'
goreleaser-dist: goreleaser-pro
goreleaser-key: ${{ secrets.GORELEASER_KEY }}

# todo: enable once i have the secret setup (pending security ticket)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer needed as we use github bot in slack to achieve the same thing

# - name: Set tag for Slack notification
# run: echo "TAG=${{ github.ref_name }}" >> "$GITHUB_ENV"
# shell: bash
#
# - name: Notify Slack
# uses: smartcontractkit/.github/actions/slack-notify-git-ref@eeb76b5870e3c17856d5a60fd064a053c023b5f5 # slack-notify-git-ref@1.0.0
# with:
# slack-channel-id: ${{ secrets.SLACK_CHANNEL_CLDF}}
# slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN_RELENG }} # Releng Bot
# git-ref: ${{ env.TAG }}
# git-ref-type: tag
# changelog-url: 'https://github.com/${{ github.repository }}/releases/tag/${{ env.TAG }}'
13 changes: 13 additions & 0 deletions deployment/aptos_chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package deployment

import (
"github.com/aptos-labs/aptos-go-sdk"
)

// AptosChain represents an Aptos chain.
type AptosChain struct {
Selector uint64
Client aptos.AptosRpcClient
DeployerSigner aptos.TransactionSigner
URL string
}
37 changes: 37 additions & 0 deletions deployment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"errors"
"fmt"
"math/big"
"strconv"

"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/rpc"
csav1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/csa"
jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job"
Expand All @@ -24,6 +26,7 @@ type OnchainClient interface {
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
}


// OffchainClient interacts with the job-distributor
// which is a family agnostic interface for performing
// DON operations.
Expand All @@ -33,6 +36,40 @@ type OffchainClient interface {
csav1.CSAServiceClient
}

// Chain represents an EVM chain.
type Chain struct {
// Selectors used as canonical chain identifier.
Selector uint64
Client OnchainClient
// Note the Sign function can be abstract supporting a variety of key storage mechanisms (e.g. KMS etc).
DeployerKey *bind.TransactOpts
Confirm func(tx *types.Transaction) (uint64, error)
// Users are a set of keys that can be used to interact with the chain.
// These are distinct from the deployer key.
Users []*bind.TransactOpts
}

func (c Chain) String() string {
chainInfo, err := ChainInfo(c.Selector)
if err != nil {
// we should never get here, if the selector is invalid it should not be in the environment
panic(err)
}
return fmt.Sprintf("%s (%d)", chainInfo.ChainName, chainInfo.ChainSelector)
}

func (c Chain) Name() string {
chainInfo, err := ChainInfo(c.Selector)
if err != nil {
// we should never get here, if the selector is invalid it should not be in the environment
panic(err)
}
if chainInfo.ChainName == "" {
return strconv.FormatUint(c.Selector, 10)
}
return chainInfo.ChainName
}

func MaybeDataErr(err error) error {
//revive:disable
var d rpc.DataError
Expand Down
19 changes: 19 additions & 0 deletions deployment/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package deployment

import chain_selectors "github.com/smartcontractkit/chain-selectors"

func ChainInfo(cs uint64) (chain_selectors.ChainDetails, error) {
id, err := chain_selectors.GetChainIDFromSelector(cs)
if err != nil {
return chain_selectors.ChainDetails{}, err
}
family, err := chain_selectors.GetSelectorFamily(cs)
if err != nil {
return chain_selectors.ChainDetails{}, err
}
info, err := chain_selectors.GetChainDetailsByChainIDAndFamily(id, family)
if err != nil {
return chain_selectors.ChainDetails{}, err
}
return info, nil
}
178 changes: 178 additions & 0 deletions deployment/solana_chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package deployment

import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/gagliardetto/solana-go"
solRpc "github.com/gagliardetto/solana-go/rpc"
"github.com/pkg/errors"

"github.com/gagliardetto/solana-go/rpc"

solCommonUtil "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
)

const (
ProgramIDPrefix = "Program Id: "
BufferIDPrefix = "Buffer: "
SolDefaultCommitment = rpc.CommitmentConfirmed
RouterProgramName = "ccip_router"
OffRampProgramName = "ccip_offramp"
FeeQuoterProgramName = "fee_quoter"
BurnMintTokenPoolProgramName = "burnmint_token_pool"
LockReleaseTokenPoolProgramName = "lockrelease_token_pool"
AccessControllerProgramName = "access_controller"
TimelockProgramName = "timelock"
McmProgramName = "mcm"
RMNRemoteProgramName = "rmn_remote"
ReceiverProgramName = "test_ccip_receiver"
)

// SolChain represents a Solana chain.
type SolChain struct {
// Selectors used as canonical chain identifier.
Selector uint64
// RPC client
Client *solRpc.Client
URL string
WSURL string
// TODO: raw private key for now, need to replace with a more secure way
DeployerKey *solana.PrivateKey
Confirm func(instructions []solana.Instruction, opts ...solCommonUtil.TxModifier) error

// deploy uses the solana CLI which needs a keyfile
KeypairPath string
ProgramsPath string
}

func (c SolChain) String() string {
chainInfo, err := ChainInfo(c.Selector)
if err != nil {
// we should never get here, if the selector is invalid it should not be in the environment
panic(err)
}
return fmt.Sprintf("%s (%d)", chainInfo.ChainName, chainInfo.ChainSelector)
}

func (c SolChain) Name() string {
chainInfo, err := ChainInfo(c.Selector)
if err != nil {
// we should never get here, if the selector is invalid it should not be in the environment
panic(err)
}
if chainInfo.ChainName == "" {
return strconv.FormatUint(c.Selector, 10)
}
return chainInfo.ChainName
}

// https://docs.google.com/document/d/1Fk76lOeyS2z2X6MokaNX_QTMFAn5wvSZvNXJluuNV1E/edit?tab=t.0#heading=h.uij286zaarkz
// https://docs.google.com/document/d/1nCNuam0ljOHiOW0DUeiZf4ntHf_1Bw94Zi7ThPGoKR4/edit?tab=t.0#heading=h.hju45z55bnqd
func GetSolanaProgramBytes() map[string]int {
return map[string]int{
RouterProgramName: 5 * 1024 * 1024,
OffRampProgramName: 1.5 * 1024 * 1024, // router should be redeployed but it does support upgrades if required (big fixes etc.)
FeeQuoterProgramName: 5 * 1024 * 1024,
BurnMintTokenPoolProgramName: 3 * 1024 * 1024,
LockReleaseTokenPoolProgramName: 3 * 1024 * 1024,
AccessControllerProgramName: 1 * 1024 * 1024,
TimelockProgramName: 1 * 1024 * 1024,
McmProgramName: 1 * 1024 * 1024,
RMNRemoteProgramName: 3 * 1024 * 1024,
}
}

func (c SolChain) DeployProgram(logger logger.Logger, programName string, isUpgrade bool) (string, error) {
programFile := filepath.Join(c.ProgramsPath, programName+".so")
if _, err := os.Stat(programFile); err != nil {
return "", fmt.Errorf("program file not found: %w", err)
}
programKeyPair := filepath.Join(c.ProgramsPath, programName+"-keypair.json")

cliCommand := "deploy"
prefix := ProgramIDPrefix
if isUpgrade {
cliCommand = "write-buffer"
prefix = BufferIDPrefix
}

// Base command with required args
baseArgs := []string{
"program", cliCommand,
programFile, // .so file
"--keypair", c.KeypairPath, // deployer keypair
"--url", c.URL, // rpc url
"--use-rpc", // use rpc for deployment
}

var cmd *exec.Cmd
// We need to specify the program ID on the initial deploy but not on upgrades
// Upgrades happen in place so we don't need to supply the keypair
// It will write the .so file to a buffer and then deploy it to the existing keypair
if !isUpgrade {
logger.Infow("Deploying program with existing keypair",
"programFile", programFile,
"programKeyPair", programKeyPair)
baseArgs = append(baseArgs, "--program-id", programKeyPair)
totalBytes := GetSolanaProgramBytes()[programName]
if totalBytes > 0 {
baseArgs = append(baseArgs, "--max-len", strconv.Itoa(totalBytes))
}
cmd = exec.Command("solana", baseArgs...) // #nosec G204
} else {
// Keypairs wont be created for devenvs
logger.Infow("Deploying new program",
"programFile", programFile)
cmd = exec.Command("solana", baseArgs...) // #nosec G204
}

// Capture the command output
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr

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

// Parse and return the program ID
output := stdout.String()

// TODO: obviously need to do this better
time.Sleep(5 * time.Second)
return parseProgramID(output, prefix)
}

func (c SolChain) GetAccountDataBorshInto(ctx context.Context, pubkey solana.PublicKey, accountState interface{}) error {
err := solCommonUtil.GetAccountDataBorshInto(ctx, c.Client, pubkey, SolDefaultCommitment, accountState)
if err != nil {
return err
}
return nil
}

// parseProgramID parses the program ID from the deploy output.
func parseProgramID(output string, prefix string) (string, error) {
// Look for the program ID in the CLI output
// Example output: "Program Id: <PROGRAM_ID>"
startIdx := strings.Index(output, prefix)
if startIdx == -1 {
return "", errors.New("failed to find program ID in output")
}
startIdx += len(prefix)
endIdx := strings.Index(output[startIdx:], "\n")
if endIdx == -1 {
endIdx = len(output)
}
return output[startIdx : startIdx+endIdx], nil
}
11 changes: 8 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ go 1.24.1

require (
github.com/Masterminds/semver/v3 v3.3.1
github.com/aptos-labs/aptos-go-sdk v1.5.0
github.com/avast/retry-go/v4 v4.6.1
github.com/ethereum/go-ethereum v1.15.3
github.com/gagliardetto/solana-go v1.12.0
github.com/google/uuid v1.6.0
github.com/pkg/errors v0.9.1
github.com/smartcontractkit/chain-selectors v1.0.50
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250425163923-16aa375957b7
github.com/smartcontractkit/chainlink-common v0.7.0
github.com/smartcontractkit/chainlink-protos/job-distributor v0.9.0
github.com/smartcontractkit/mcms v0.16.1
Expand All @@ -18,11 +21,13 @@ require (
)

require (
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/bits-and-blooms/bitset v1.17.0 // indirect
github.com/blendle/zapdriver v1.3.1 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/consensys/bavard v0.1.22 // indirect
github.com/consensys/gnark-crypto v0.14.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
Expand All @@ -36,13 +41,14 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gagliardetto/binary v0.8.0 // indirect
github.com/gagliardetto/solana-go v1.12.0 // indirect
github.com/gagliardetto/treeout v0.1.4 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hasura/go-graphql-client v0.13.1 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52 // indirect
Expand All @@ -59,7 +65,6 @@ require (
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250226104101-11778f2ead98 // indirect
github.com/smartcontractkit/libocr v0.0.0-20250220133800-f3b940c4f298 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect
Expand Down
Loading
Loading