Skip to content

Commit 3dcfd1c

Browse files
authored
deployment/memory: Solana support (develop) (#15889)
* deployment: memory: Generate more transmitter key types, expose in JD * deployment: memory: Configure nodes with solana config too * Use CTF to spin up the solana validator for in-memory tests * Use autopatchelf on solana binaries to make them usable on NixOS * memory: solana: Shut down the container when test terminates * go mod tidy * Use latest upstream CTF * Add missing import * make modgraph * Use framework.DefaultNetwork()
1 parent c57f910 commit 3dcfd1c

File tree

16 files changed

+497
-186
lines changed

16 files changed

+497
-186
lines changed

Diff for: core/scripts/go.mod

+6-6
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ require (
8282
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
8383
github.com/buger/jsonparser v1.1.1 // indirect
8484
github.com/bytecodealliance/wasmtime-go/v23 v23.0.0 // indirect
85-
github.com/bytedance/sonic v1.11.6 // indirect
86-
github.com/bytedance/sonic/loader v0.1.1 // indirect
85+
github.com/bytedance/sonic v1.12.3 // indirect
86+
github.com/bytedance/sonic/loader v0.2.0 // indirect
8787
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
8888
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
8989
github.com/cespare/xxhash v1.1.0 // indirect
@@ -135,7 +135,7 @@ require (
135135
github.com/felixge/httpsnoop v1.0.4 // indirect
136136
github.com/fsnotify/fsnotify v1.7.0 // indirect
137137
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
138-
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
138+
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
139139
github.com/gagliardetto/binary v0.8.0 // indirect
140140
github.com/gagliardetto/solana-go v1.12.0 // indirect
141141
github.com/gagliardetto/treeout v0.1.4 // indirect
@@ -164,7 +164,7 @@ require (
164164
github.com/go-openapi/swag v0.23.0 // indirect
165165
github.com/go-playground/locales v0.14.1 // indirect
166166
github.com/go-playground/universal-translator v0.18.1 // indirect
167-
github.com/go-playground/validator/v10 v10.22.0 // indirect
167+
github.com/go-playground/validator/v10 v10.22.1 // indirect
168168
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
169169
github.com/go-webauthn/webauthn v0.9.4 // indirect
170170
github.com/go-webauthn/x v0.1.5 // indirect
@@ -251,7 +251,7 @@ require (
251251
github.com/maruel/natural v1.1.1 // indirect
252252
github.com/mattn/go-colorable v0.1.13 // indirect
253253
github.com/mattn/go-isatty v0.0.20 // indirect
254-
github.com/mattn/go-runewidth v0.0.14 // indirect
254+
github.com/mattn/go-runewidth v0.0.16 // indirect
255255
github.com/mfridman/interpolate v0.0.2 // indirect
256256
github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
257257
github.com/mitchellh/go-homedir v1.1.0 // indirect
@@ -284,7 +284,7 @@ require (
284284
github.com/prometheus/procfs v0.15.1 // indirect
285285
github.com/prometheus/prometheus v0.54.1 // indirect
286286
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
287-
github.com/rivo/uniseg v0.4.4 // indirect
287+
github.com/rivo/uniseg v0.4.7 // indirect
288288
github.com/robfig/cron/v3 v3.0.1 // indirect
289289
github.com/rogpeppe/go-internal v1.13.1 // indirect
290290
github.com/rs/cors v1.10.1 // indirect

Diff for: core/scripts/go.sum

+60-13
Large diffs are not rendered by default.

Diff for: deployment/environment/memory/chain.go

+84-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package memory
22

33
import (
4+
"encoding/json"
45
"math/big"
6+
"os"
7+
"path"
8+
"strconv"
9+
"sync"
510
"testing"
11+
"time"
612

713
"github.com/ethereum/go-ethereum/accounts/abi/bind"
814
"github.com/ethereum/go-ethereum/common"
@@ -11,14 +17,20 @@ import (
1117
"github.com/ethereum/go-ethereum/ethclient/simulated"
1218
"github.com/gagliardetto/solana-go"
1319
solRpc "github.com/gagliardetto/solana-go/rpc"
14-
20+
"github.com/hashicorp/consul/sdk/freeport"
1521
"github.com/stretchr/testify/require"
22+
"github.com/testcontainers/testcontainers-go"
1623

1724
solTestUtil "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/testutils"
1825

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

2128
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
29+
30+
chainselectors "github.com/smartcontractkit/chain-selectors"
31+
32+
"github.com/smartcontractkit/chainlink-testing-framework/framework"
33+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
2234
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
2335
)
2436

@@ -30,7 +42,9 @@ type EVMChain struct {
3042

3143
type SolanaChain struct {
3244
Client *solRpc.Client
33-
DeployerKey *solana.PrivateKey
45+
URL string
46+
WSURL string
47+
DeployerKey solana.PrivateKey
3448
}
3549

3650
func fundAddress(t *testing.T, from *bind.TransactOpts, to common.Address, amount *big.Int, backend *simulated.Backend) {
@@ -80,13 +94,12 @@ func GenerateChainsSol(t *testing.T, numChains int) map[uint64]SolanaChain {
8094
chains := make(map[uint64]SolanaChain)
8195
for i := 0; i < numChains; i++ {
8296
chainID := testSolanaChainSelectors[i]
83-
url, _ := solTestUtil.SetupLocalSolNodeWithFlags(t)
84-
admin, gerr := solana.NewRandomPrivateKey()
85-
solTestUtil.FundTestAccounts(t, []solana.PublicKey{admin.PublicKey()}, url)
86-
require.NoError(t, gerr)
97+
solChain := solChain(t)
98+
admin := solChain.DeployerKey
99+
solTestUtil.FundTestAccounts(t, []solana.PublicKey{admin.PublicKey()}, solChain.URL)
87100
chains[chainID] = SolanaChain{
88-
Client: solRpc.New(url),
89-
DeployerKey: &admin,
101+
Client: solChain.Client,
102+
DeployerKey: solChain.DeployerKey,
90103
}
91104
}
92105
return chains
@@ -126,3 +139,66 @@ func evmChain(t *testing.T, numUsers int) EVMChain {
126139
Users: users,
127140
}
128141
}
142+
143+
var once = &sync.Once{}
144+
145+
func solChain(t *testing.T) SolanaChain {
146+
t.Helper()
147+
148+
// initialize the docker network used by CTF
149+
err := framework.DefaultNetwork(once)
150+
require.NoError(t, err)
151+
152+
deployerKey, err := solana.NewRandomPrivateKey()
153+
require.NoError(t, err)
154+
155+
t.TempDir()
156+
// store the generated keypair somewhere
157+
bytes, err := json.Marshal([]byte(deployerKey))
158+
require.NoError(t, err)
159+
keypairPath := path.Join(t.TempDir(), "solana-keypair.json")
160+
err = os.WriteFile(keypairPath, bytes, 0600)
161+
require.NoError(t, err)
162+
163+
port := freeport.GetOne(t)
164+
165+
bcInput := &blockchain.Input{
166+
Type: "solana",
167+
ChainID: chainselectors.SOLANA_DEVNET.ChainID,
168+
PublicKey: deployerKey.PublicKey().String(),
169+
Port: strconv.Itoa(port),
170+
// TODO: ContractsDir & SolanaPrograms via env vars
171+
}
172+
output, err := blockchain.NewBlockchainNetwork(bcInput)
173+
require.NoError(t, err)
174+
testcontainers.CleanupContainer(t, output.Container)
175+
176+
url := output.Nodes[0].HostHTTPUrl
177+
wsURL := output.Nodes[0].HostWSUrl
178+
179+
// Wait for api server to boot
180+
client := solRpc.New(url)
181+
var ready bool
182+
for i := 0; i < 30; i++ {
183+
time.Sleep(time.Second)
184+
out, err := client.GetHealth(tests.Context(t))
185+
if err != nil || out != solRpc.HealthOk {
186+
t.Logf("API server not ready yet (attempt %d)\n", i+1)
187+
continue
188+
}
189+
ready = true
190+
break
191+
}
192+
if !ready {
193+
t.Logf("solana-test-validator is not ready after 30 attempts")
194+
}
195+
require.True(t, ready)
196+
t.Logf("solana-test-validator is ready at %s", url)
197+
198+
return SolanaChain{
199+
Client: client,
200+
URL: url,
201+
WSURL: wsURL,
202+
DeployerKey: deployerKey,
203+
}
204+
}

Diff for: deployment/environment/memory/environment.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,10 @@ func generateMemoryChainSol(t *testing.T, inputs map[uint64]SolanaChain) map[uin
128128
chains[cid] = deployment.SolChain{
129129
Selector: cid,
130130
Client: chain.Client,
131-
DeployerKey: chain.DeployerKey,
131+
DeployerKey: &chain.DeployerKey,
132132
Confirm: func(instructions []solana.Instruction, opts ...solCommomUtil.TxModifier) error {
133133
_, err := solCommomUtil.SendAndConfirm(
134-
context.Background(), chain.Client, instructions, *chain.DeployerKey, solRpc.CommitmentConfirmed, opts...,
134+
context.Background(), chain.Client, instructions, chain.DeployerKey, solRpc.CommitmentConfirmed, opts...,
135135
)
136136
if err != nil {
137137
return err
@@ -153,13 +153,13 @@ func NewNodes(t *testing.T, logLevel zapcore.Level, chains map[uint64]deployment
153153
// since we won't run a bootstrapper and a plugin oracle on the same
154154
// chainlink node in production.
155155
for i := 0; i < numBootstraps; i++ {
156-
node := NewNode(t, ports[i], chains, logLevel, true /* bootstrap */, registryConfig)
156+
node := NewNode(t, ports[i], chains, nil, logLevel, true /* bootstrap */, registryConfig)
157157
nodesByPeerID[node.Keys.PeerID.String()] = *node
158158
// Note in real env, this ID is allocated by JD.
159159
}
160160
for i := 0; i < numNodes; i++ {
161161
// grab port offset by numBootstraps, since above loop also takes some ports.
162-
node := NewNode(t, ports[numBootstraps+i], chains, logLevel, false /* bootstrap */, registryConfig)
162+
node := NewNode(t, ports[numBootstraps+i], chains, nil, logLevel, false /* bootstrap */, registryConfig)
163163
nodesByPeerID[node.Keys.PeerID.String()] = *node
164164
// Note in real env, this ID is allocated by JD.
165165
}

Diff for: deployment/environment/memory/job_client.go

+4-42
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"fmt"
77
"slices"
8-
"strconv"
98
"strings"
109

1110
"github.com/ethereum/go-ethereum/common"
@@ -153,49 +152,12 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode
153152
if !ok {
154153
return nil, fmt.Errorf("node id not found: %s", in.Filter.NodeIds[0])
155154
}
156-
evmBundle := n.Keys.OCRKeyBundles[chaintype.EVM]
157-
offpk := evmBundle.OffchainPublicKey()
158-
cpk := evmBundle.ConfigEncryptionPublicKey()
159-
160-
evmKeyBundle := &nodev1.OCR2Config_OCRKeyBundle{
161-
BundleId: evmBundle.ID(),
162-
ConfigPublicKey: common.Bytes2Hex(cpk[:]),
163-
OffchainPublicKey: common.Bytes2Hex(offpk[:]),
164-
OnchainSigningAddress: evmBundle.OnChainPublicKey(),
165-
}
166-
167155
var chainConfigs []*nodev1.ChainConfig
168-
for evmChainID, transmitter := range n.Keys.TransmittersByEVMChainID {
169-
chainConfigs = append(chainConfigs, &nodev1.ChainConfig{
170-
Chain: &nodev1.Chain{
171-
Id: strconv.Itoa(int(evmChainID)),
172-
Type: nodev1.ChainType_CHAIN_TYPE_EVM,
173-
},
174-
AccountAddress: transmitter.String(),
175-
AdminAddress: transmitter.String(), // TODO: custom address
176-
Ocr1Config: nil,
177-
Ocr2Config: &nodev1.OCR2Config{
178-
Enabled: true,
179-
IsBootstrap: n.IsBoostrap,
180-
P2PKeyBundle: &nodev1.OCR2Config_P2PKeyBundle{
181-
PeerId: n.Keys.PeerID.String(),
182-
},
183-
OcrKeyBundle: evmKeyBundle,
184-
Multiaddr: n.Addr.String(),
185-
Plugins: nil,
186-
ForwarderAddress: ptr(""),
187-
},
188-
})
189-
}
190156
for _, selector := range n.Chains {
191157
family, err := chainsel.GetSelectorFamily(selector)
192158
if err != nil {
193159
return nil, err
194160
}
195-
if family == chainsel.FamilyEVM {
196-
// already handled above
197-
continue
198-
}
199161

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

222184
bundle := n.Keys.OCRKeyBundles[ocrtype]
223-
224185
offpk := bundle.OffchainPublicKey()
225186
cpk := bundle.ConfigEncryptionPublicKey()
226187

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

209+
transmitter := n.Keys.Transmitters[selector]
210+
248211
chainConfigs = append(chainConfigs, &nodev1.ChainConfig{
249212
Chain: &nodev1.Chain{
250213
Id: chainID,
251214
Type: ctype,
252215
},
253-
AccountAddress: "", // TODO: support AccountAddress
254-
AdminAddress: "",
216+
AccountAddress: transmitter,
217+
AdminAddress: transmitter,
255218
Ocr1Config: nil,
256219
Ocr2Config: &nodev1.OCR2Config{
257220
Enabled: true,
@@ -266,7 +229,6 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode
266229
},
267230
})
268231
}
269-
// TODO: I think we can pull it from the feeds manager.
270232
return &nodev1.ListNodeChainConfigsResponse{
271233
ChainConfigs: chainConfigs,
272234
}, nil

0 commit comments

Comments
 (0)