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

deployment/memory: Solana support #15850

Closed
wants to merge 11 commits into from
2 changes: 1 addition & 1 deletion deployment/ccip/changeset/cs_deploy_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ func deployChainContractsSolana(
if chainState.CcipRouter.IsZero() {
// deploy and initialize router

programID, err := deployment.DeploySolProgramCLI("ccip_router")
programID, err := deployment.DeploySolProgramCLI(chain, "ccip_router")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should probably turn this into chain.DeployProgram("ccip_router")

if err != nil {
return fmt.Errorf("failed to deploy program: %v", err)
}
Expand Down
7 changes: 5 additions & 2 deletions deployment/common/changeset/deploy_link_token_sol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,12 @@ func setDevNet(keypairPath string) error {

// TestDeployProgram is a test for deploying the Solana program.
func TestDeployProgram(t *testing.T) {
// TODO: spin up proper env remove setDevNet/spinUpDevNet

// Path to your .so file and keypair file
// programFile := "/Users/yashvardhan/chainlink-internal-integrations/solana/contracts/target/deploy/external_program_cpi_stub.so"
keypairPath := "/Users/yashvardhan/.config/solana/id.json" //wallet
// TODO:
// programKeyPair := "/Users/yashvardhan/chainlink-internal-integrations/solana/contracts/target/deploy/external_program_cpi_stub-keypair.json"
// keypairPath := "/Users/yashvardhan/chainlink-internal-integrations/solana/contracts/target/deploy/external_program_cpi_stub-keypair.json"

Expand All @@ -105,7 +108,7 @@ func TestDeployProgram(t *testing.T) {
} else {
fmt.Println("Program does not exist or is not executable.")
// Deploy the program
programID, err := deployment.DeploySolProgramCLI("external_program_cpi_stub")
programID, err := deployment.DeploySolProgramCLI(chain, "external_program_cpi_stub")
if err != nil {
t.Fatalf("Failed to deploy program: %v", err)
}
Expand Down Expand Up @@ -245,7 +248,7 @@ func TestCcipRouterDeploy(t *testing.T) {
// })
// require.ErrorAs(t, err, &rpc.ErrNotFound)
// Deploy the program
programID, err := deployment.DeploySolProgramCLI("ccip_router")
programID, err := deployment.DeploySolProgramCLI(chain, "ccip_router")
CcipRouterProgram := solana.MustPublicKeyFromBase58(programID)
if err != nil {
t.Fatalf("Failed to deploy program: %v", err)
Expand Down
8 changes: 4 additions & 4 deletions deployment/environment/devenv/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,15 @@ func NewSolChains(logger logger.Logger, configs []ChainConfig) (map[uint64]deplo
if ec == nil {
return nil, fmt.Errorf("failed to connect to chain %s", chainCfg.ChainName)
}
// chainInfo, err := deployment.ChainInfo(selector)
if err != nil {
return nil, fmt.Errorf("failed to get chain info for chain %s: %w", chainCfg.ChainName, err)
}
// TODO: fetch this from chainConfig, together with KeypairPath
adminPrivateKey := deployment.GetSolanaDeployerKey()
chains[selector] = deployment.SolChain{
Selector: selector,
Client: ec,
URL: chainCfg.HTTPRPCs[0],
WSURL: chainCfg.WSRPCs[0],
DeployerKey: &adminPrivateKey,
KeypairPath: "TODO",
Confirm: func(instructions []solana.Instruction, opts ...solCommomUtil.TxModifier) error {
_, err := solCommomUtil.SendAndConfirm(
context.Background(), ec, instructions, adminPrivateKey, solRpc.CommitmentConfirmed, opts...,
Expand Down
69 changes: 34 additions & 35 deletions deployment/environment/memory/chain.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package memory

import (
"bytes"
"math/big"
"os/exec"
"strconv"
"testing"
"time"

Expand All @@ -13,14 +10,16 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient/simulated"
"github.com/gagliardetto/solana-go"
solRpc "github.com/gagliardetto/solana-go/rpc"
"github.com/stretchr/testify/require"
"github.com/test-go/testify/assert"

chainsel "github.com/smartcontractkit/chain-selectors"

"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"

chainselectors "github.com/smartcontractkit/chain-selectors"
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
)

Expand All @@ -30,6 +29,13 @@ type EVMChain struct {
Users []*bind.TransactOpts
}

type SolChain struct {
Backend *solRpc.Client
URL string
WSURL string
DeployerKey solana.PrivateKey
}

func fundAddress(t *testing.T, from *bind.TransactOpts, to common.Address, amount *big.Int, backend *simulated.Backend) {
ctx := tests.Context(t)
nonce, err := backend.Client().PendingNonceAt(ctx, from.From)
Expand Down Expand Up @@ -96,43 +102,31 @@ func evmChain(t *testing.T, numUsers int) EVMChain {

// TODO: make it random port to support multiple chains
// TODO: add dynamic users and admin like done in evmChain
func solChain(t *testing.T) (string, string) {
func solChain(t *testing.T) SolChain {
t.Helper()
port := "8899"
portInt, _ := strconv.Atoi(port)

faucetPort := "8877"
url := "http://127.0.0.1:" + port
wsURL := "ws://127.0.0.1:" + strconv.Itoa(portInt+1)

args := []string{
"--reset",
"--rpc-port", port,
"--faucet-port", faucetPort,
"--ledger", t.TempDir(),

deployerKey, err := solana.NewRandomPrivateKey()
require.NoError(t, err)
// TODO: fund this key

bcInput := &blockchain.Input{
Type: "solana",
// TODO: randomize port
ChainID: chainselectors.SOLANA_DEVNET.ChainID,
PublicKey: deployerKey.PublicKey().String(),
// TODO: ContractsDir & SolanaPrograms via env vars
}
output, err := blockchain.NewBlockchainNetwork(bcInput)
url := output.Nodes[0].HostHTTPUrl
wsURL := output.Nodes[0].HostWSUrl

cmd := exec.Command("solana-test-validator", args...)

var stdErr bytes.Buffer
cmd.Stderr = &stdErr
var stdOut bytes.Buffer
cmd.Stdout = &stdOut
require.NoError(t, cmd.Start())
t.Cleanup(func() {
assert.NoError(t, cmd.Process.Kill())
if err2 := cmd.Wait(); assert.Error(t, err2) {
if !assert.Contains(t, err2.Error(), "signal: killed", cmd.ProcessState.String()) {
t.Logf("solana-test-validator\n stdout: %s\n stderr: %s", stdOut.String(), stdErr.String())
}
}
})
require.NoError(t, err)

// Wait for api server to boot
client := solRpc.New(url)
var ready bool
for i := 0; i < 30; i++ {
time.Sleep(time.Second)
client := solRpc.New(url)
out, err := client.GetHealth(tests.Context(t))
if err != nil || out != solRpc.HealthOk {
t.Logf("API server not ready yet (attempt %d)\n", i+1)
Expand All @@ -142,10 +136,15 @@ func solChain(t *testing.T) (string, string) {
break
}
if !ready {
t.Logf("Cmd output: %s\nCmd error: %s\n", stdOut.String(), stdErr.String())
t.Logf("solana-test-validator is not ready after 30 attempts")
}
require.True(t, ready)
t.Logf("solana-test-validator is ready at %s", url)

return url, wsURL
return SolChain{
Backend: client,
URL: url,
WSURL: wsURL,
DeployerKey: deployerKey,
}
}
24 changes: 17 additions & 7 deletions deployment/environment/memory/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package memory

import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"strconv"
"testing"
"time"
Expand Down Expand Up @@ -195,17 +198,24 @@ func NewMemoryChainsSol(t *testing.T) map[uint64]deployment.SolChain {
solChainSelector := deployment.SolanaChainSelector
solChains := make(map[uint64]deployment.SolChain)
t.Logf("Spinning up devnet")
url, _ := solChain(t)
// url := "http://127.0.0.1:8899"
client := solRpc.New(url)
adminPrivateKey := deployment.GetSolanaDeployerKey()
chain := solChain(t)
t.TempDir()
// store the generated keypair somewhere
bytes, err := json.Marshal([]byte(chain.DeployerKey))
require.NoError(t, err)
keypairPath := path.Join(t.TempDir(), "solana-keypair.json")
os.WriteFile(keypairPath, bytes, 0644)

newSolChain := deployment.SolChain{
Selector: solChainSelector,
Client: client,
DeployerKey: &adminPrivateKey,
Client: chain.Backend,
URL: chain.URL,
WSURL: chain.WSURL,
DeployerKey: &chain.DeployerKey,
KeypairPath: keypairPath,
Confirm: func(instructions []solana.Instruction, opts ...solCommomUtil.TxModifier) error {
_, err := solCommomUtil.SendAndConfirm(
context.Background(), client, instructions, adminPrivateKey, solRpc.CommitmentConfirmed, opts...,
context.Background(), chain.Backend, instructions, chain.DeployerKey, solRpc.CommitmentConfirmed, opts...,
)
if err != nil {
return err
Expand Down
46 changes: 4 additions & 42 deletions deployment/environment/memory/job_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"slices"
"strconv"
"strings"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -153,49 +152,12 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode
if !ok {
return nil, fmt.Errorf("node id not found: %s", in.Filter.NodeIds[0])
}
evmBundle := n.Keys.OCRKeyBundles[chaintype.EVM]
offpk := evmBundle.OffchainPublicKey()
cpk := evmBundle.ConfigEncryptionPublicKey()

evmKeyBundle := &nodev1.OCR2Config_OCRKeyBundle{
BundleId: evmBundle.ID(),
ConfigPublicKey: common.Bytes2Hex(cpk[:]),
OffchainPublicKey: common.Bytes2Hex(offpk[:]),
OnchainSigningAddress: evmBundle.OnChainPublicKey(),
}

var chainConfigs []*nodev1.ChainConfig
for evmChainID, transmitter := range n.Keys.TransmittersByEVMChainID {
chainConfigs = append(chainConfigs, &nodev1.ChainConfig{
Chain: &nodev1.Chain{
Id: strconv.Itoa(int(evmChainID)),
Type: nodev1.ChainType_CHAIN_TYPE_EVM,
},
AccountAddress: transmitter.String(),
AdminAddress: transmitter.String(), // TODO: custom address
Ocr1Config: nil,
Ocr2Config: &nodev1.OCR2Config{
Enabled: true,
IsBootstrap: n.IsBoostrap,
P2PKeyBundle: &nodev1.OCR2Config_P2PKeyBundle{
PeerId: n.Keys.PeerID.String(),
},
OcrKeyBundle: evmKeyBundle,
Multiaddr: n.Addr.String(),
Plugins: nil,
ForwarderAddress: ptr(""),
},
})
}
for _, selector := range n.Chains {
family, err := chainsel.GetSelectorFamily(selector)
if err != nil {
return nil, err
}
if family == chainsel.FamilyEVM {
// already handled above
continue
}

// NOTE: this supports non-EVM too
chainID, err := chainsel.GetChainIDFromSelector(selector)
Expand All @@ -220,7 +182,6 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode
}

bundle := n.Keys.OCRKeyBundles[ocrtype]

offpk := bundle.OffchainPublicKey()
cpk := bundle.ConfigEncryptionPublicKey()

Expand All @@ -245,13 +206,15 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode
panic(fmt.Sprintf("Unsupported chain family %v", family))
}

transmitter := n.Keys.Transmitters[selector]

chainConfigs = append(chainConfigs, &nodev1.ChainConfig{
Chain: &nodev1.Chain{
Id: chainID,
Type: ctype,
},
AccountAddress: "", // TODO: support AccountAddress
AdminAddress: "",
AccountAddress: transmitter,
AdminAddress: transmitter,
Ocr1Config: nil,
Ocr2Config: &nodev1.OCR2Config{
Enabled: true,
Expand All @@ -266,7 +229,6 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode
},
})
}
// TODO: I think we can pull it from the feeds manager.
return &nodev1.ListNodeChainConfigsResponse{
ChainConfigs: chainConfigs,
}, nil
Expand Down
Loading
Loading