From 1d1332234fa108036e83a0cda6b05fd436bd64de Mon Sep 17 00:00:00 2001 From: yashnevatia Date: Wed, 8 Jan 2025 22:21:12 +0000 Subject: [PATCH] moving over from deployment-memory-solana --- deployment/environment/memory/environment.go | 4 +- deployment/environment/memory/job_client.go | 46 +---- deployment/environment/memory/node.go | 205 ++++++++++++++----- deployment/environment/memory/node_test.go | 2 +- shell.nix | 56 ++++- 5 files changed, 219 insertions(+), 94 deletions(-) diff --git a/deployment/environment/memory/environment.go b/deployment/environment/memory/environment.go index ca003976dcd..74517a3929d 100644 --- a/deployment/environment/memory/environment.go +++ b/deployment/environment/memory/environment.go @@ -156,13 +156,13 @@ func NewNodes(t *testing.T, logLevel zapcore.Level, chains map[uint64]deployment // since we won't run a bootstrapper and a plugin oracle on the same // chainlink node in production. for i := 0; i < numBootstraps; i++ { - node := NewNode(t, ports[i], chains, logLevel, true /* bootstrap */, registryConfig) + node := NewNode(t, ports[i], chains, nil, logLevel, true /* bootstrap */, registryConfig) nodesByPeerID[node.Keys.PeerID.String()] = *node // Note in real env, this ID is allocated by JD. } for i := 0; i < numNodes; i++ { // grab port offset by numBootstraps, since above loop also takes some ports. - node := NewNode(t, ports[numBootstraps+i], chains, logLevel, false /* bootstrap */, registryConfig) + node := NewNode(t, ports[numBootstraps+i], chains, nil, logLevel, false /* bootstrap */, registryConfig) nodesByPeerID[node.Keys.PeerID.String()] = *node // Note in real env, this ID is allocated by JD. } diff --git a/deployment/environment/memory/job_client.go b/deployment/environment/memory/job_client.go index a3cfee41608..fc4cd3234cd 100644 --- a/deployment/environment/memory/job_client.go +++ b/deployment/environment/memory/job_client.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "slices" - "strconv" "strings" "github.com/ethereum/go-ethereum/common" @@ -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) @@ -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() @@ -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, @@ -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 diff --git a/deployment/environment/memory/node.go b/deployment/environment/memory/node.go index 84f0d2e443f..606c080be94 100644 --- a/deployment/environment/memory/node.go +++ b/deployment/environment/memory/node.go @@ -6,6 +6,7 @@ import ( "math/big" "net" "net/http" + "slices" "strconv" "testing" "time" @@ -22,6 +23,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/mailbox" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/capabilities" @@ -72,14 +75,19 @@ func NewNode( t *testing.T, port int, // Port for the P2P V2 listener. chains map[uint64]deployment.Chain, + solchains map[uint64]deployment.SolChain, logLevel zapcore.Level, bootstrap bool, registryConfig deployment.CapabilityRegistryConfig, ) *Node { evmchains := make(map[uint64]EVMChain) for _, chain := range chains { - // we're only mapping evm chains here - if family, err := chainsel.GetSelectorFamily(chain.Selector); err != nil || family != chainsel.FamilyEVM { + family, err := chainsel.GetSelectorFamily(chain.Selector) + if err != nil { + t.Fatal(err) + } + // we're only mapping evm chains here, currently this list could also contain non-EVMs, e.g. Aptos + if family != chainsel.FamilyEVM { continue } evmChainID, err := chainsel.ChainIdFromSelector(chain.Selector) @@ -120,11 +128,21 @@ func NewNode( c.Log.Level = ptr(configv2.LogLevel(logLevel)) - var chainConfigs v2toml.EVMConfigs + var evmConfigs v2toml.EVMConfigs for chainID := range evmchains { - chainConfigs = append(chainConfigs, createConfigV2Chain(chainID)) + evmConfigs = append(evmConfigs, createConfigV2Chain(chainID)) } - c.EVM = chainConfigs + c.EVM = evmConfigs + + var solConfigs solcfg.TOMLConfigs + for chainID, chain := range solchains { + solanaChainID, err := chainsel.GetChainIDFromSelector(chainID) + if err != nil { + t.Fatal(err) + } + solConfigs = append(solConfigs, createSolanaChainConfig(solanaChainID, chain)) + } + c.Solana = solConfigs }) // Set logging. @@ -164,6 +182,12 @@ func NewNode( CSAETHKeystore: kStore, } + solanaOpts := chainlink.SolanaFactoryConfig{ + Keystore: master.Solana(), + TOMLConfigs: cfg.SolanaConfigs(), + DS: db, + } + // Build Beholder auth ctx := tests.Context(t) require.NoError(t, master.Unlock(ctx, "password")) @@ -171,14 +195,19 @@ func NewNode( beholderAuthHeaders, csaPubKeyHex, err := keystore.BuildBeholderAuth(master) require.NoError(t, err) - // Build relayer factory with EVM. + loopRegistry := plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), cfg.Tracing(), cfg.Telemetry(), beholderAuthHeaders, csaPubKeyHex) + + // Build relayer factory relayerFactory := chainlink.RelayerFactory{ Logger: lggr, - LoopRegistry: plugins.NewLoopRegistry(lggr.Named("LoopRegistry"), cfg.Tracing(), cfg.Telemetry(), beholderAuthHeaders, csaPubKeyHex), + LoopRegistry: loopRegistry, GRPCOpts: loop.GRPCOpts{}, CapabilitiesRegistry: capabilities.NewRegistry(lggr), } - initOps := []chainlink.CoreRelayerChainInitFunc{chainlink.InitEVM(context.Background(), relayerFactory, evmOpts)} + initOps := []chainlink.CoreRelayerChainInitFunc{ + chainlink.InitEVM(context.Background(), relayerFactory, evmOpts), + chainlink.InitSolana(context.Background(), relayerFactory, solanaOpts), + } rci, err := chainlink.NewCoreRelayerChainInteroperators(initOps...) require.NoError(t, err) @@ -194,17 +223,20 @@ func NewNode( RestrictedHTTPClient: &http.Client{}, AuditLogger: audit.NoopLogger, MailMon: mailMon, - LoopRegistry: plugins.NewLoopRegistry(lggr, cfg.Tracing(), cfg.Telemetry(), beholderAuthHeaders, csaPubKeyHex), + LoopRegistry: loopRegistry, }) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, db.Close()) }) - keys := CreateKeys(t, app, chains) + keys := CreateKeys(t, app, chains, solchains) return &Node{ - App: app, - Chains: maps.Keys(chains), + App: app, + Chains: slices.Concat( + maps.Keys(chains), + maps.Keys(solchains), + ), Keys: keys, Addr: net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: port}, IsBoostrap: bootstrap, @@ -212,14 +244,17 @@ func NewNode( } type Keys struct { - PeerID p2pkey.PeerID - CSA csakey.KeyV2 - TransmittersByEVMChainID map[uint64]common.Address - OCRKeyBundles map[chaintype.ChainType]ocr2key.KeyBundle + PeerID p2pkey.PeerID + CSA csakey.KeyV2 + Transmitters map[uint64]string // chainSelector => address + OCRKeyBundles map[chaintype.ChainType]ocr2key.KeyBundle } func CreateKeys(t *testing.T, - app chainlink.Application, chains map[uint64]deployment.Chain) Keys { + app chainlink.Application, + chains map[uint64]deployment.Chain, + solchains map[uint64]deployment.SolChain, +) Keys { ctx := tests.Context(t) _, err := app.GetKeyStore().P2P().Create(ctx) require.NoError(t, err) @@ -235,7 +270,7 @@ func CreateKeys(t *testing.T, require.Len(t, p2pIDs, 1) peerID := p2pIDs[0].PeerID() // create a transmitter for each chain - transmitters := make(map[uint64]common.Address) + transmitters := make(map[uint64]string) keybundles := make(map[chaintype.ChainType]ocr2key.KeyBundle) for _, chain := range chains { family, err := chainsel.GetSelectorFamily(chain.Selector) @@ -257,44 +292,100 @@ func CreateKeys(t *testing.T, panic(fmt.Sprintf("Unsupported chain family %v", family)) } - keybundle, err := app.GetKeyStore().OCR2().Create(ctx, ctype) + err = app.GetKeyStore().OCR2().EnsureKeys(ctx, ctype) + require.NoError(t, err) + keys, err := app.GetKeyStore().OCR2().GetAllOfType(ctype) require.NoError(t, err) + require.Len(t, keys, 1) + keybundle := keys[0] + keybundles[ctype] = keybundle - if family != chainsel.FamilyEVM { - // TODO: only support EVM transmission keys for now - continue + switch family { + case chainsel.FamilyEVM: + evmChainID, err := chainsel.ChainIdFromSelector(chain.Selector) + require.NoError(t, err) + + cid := new(big.Int).SetUint64(evmChainID) + addrs, err2 := app.GetKeyStore().Eth().EnabledAddressesForChain(ctx, cid) + require.NoError(t, err2) + var transmitter common.Address + if len(addrs) == 1 { + // just fund the address + transmitter = addrs[0] + } else { + // create key and fund it + _, err3 := app.GetKeyStore().Eth().Create(ctx, cid) + require.NoError(t, err3, "failed to create key for chain", evmChainID) + sendingKeys, err3 := app.GetKeyStore().Eth().EnabledAddressesForChain(ctx, cid) + require.NoError(t, err3) + require.Len(t, sendingKeys, 1) + transmitter = sendingKeys[0] + } + transmitters[chain.Selector] = transmitter.String() + + backend := chain.Client.(*Backend).Sim + fundAddress(t, chain.DeployerKey, transmitter, assets.Ether(1000).ToInt(), backend) + // need to look more into it, but it seems like with sim chains nodes are sending txs with 0x from address + fundAddress(t, chain.DeployerKey, common.Address{}, assets.Ether(1000).ToInt(), backend) + case chainsel.FamilyAptos: + err = app.GetKeyStore().Aptos().EnsureKey(ctx) + require.NoError(t, err, "failed to create key for aptos") + + keys, err := app.GetKeyStore().Aptos().GetAll() + require.NoError(t, err) + require.Len(t, keys, 1) + + transmitter := keys[0] + transmitters[chain.Selector] = transmitter.ID() + + // TODO: funding + case chainsel.FamilyStarknet: + err = app.GetKeyStore().StarkNet().EnsureKey(ctx) + require.NoError(t, err, "failed to create key for starknet") + + keys, err := app.GetKeyStore().StarkNet().GetAll() + require.NoError(t, err) + require.Len(t, keys, 1) + + transmitter := keys[0] + transmitters[chain.Selector] = transmitter.ID() + + // TODO: funding + default: + // TODO: other transmission keys unsupported for now } + } - evmChainID, err := chainsel.ChainIdFromSelector(chain.Selector) + for chain := range solchains { + ctype := chaintype.Solana + err = app.GetKeyStore().OCR2().EnsureKeys(ctx, ctype) + require.NoError(t, err) + keys, err := app.GetKeyStore().OCR2().GetAllOfType(ctype) require.NoError(t, err) + require.Len(t, keys, 1) + keybundle := keys[0] - cid := big.NewInt(int64(evmChainID)) - addrs, err2 := app.GetKeyStore().Eth().EnabledAddressesForChain(ctx, cid) - require.NoError(t, err2) - if len(addrs) == 1 { - // just fund the address - transmitters[evmChainID] = addrs[0] - } else { - // create key and fund it - _, err3 := app.GetKeyStore().Eth().Create(ctx, cid) - require.NoError(t, err3, "failed to create key for chain", evmChainID) - sendingKeys, err3 := app.GetKeyStore().Eth().EnabledAddressesForChain(ctx, cid) - require.NoError(t, err3) - require.Len(t, sendingKeys, 1) - transmitters[evmChainID] = sendingKeys[0] - } - backend := chain.Client.(*Backend).Sim - fundAddress(t, chain.DeployerKey, transmitters[evmChainID], assets.Ether(1000).ToInt(), backend) - // need to look more into it, but it seems like with sim chains nodes are sending txs with 0x from address - fundAddress(t, chain.DeployerKey, common.Address{}, assets.Ether(1000).ToInt(), backend) + keybundles[ctype] = keybundle + + err = app.GetKeyStore().Solana().EnsureKey(ctx) + require.NoError(t, err, "failed to create key for solana") + + solkeys, err := app.GetKeyStore().Solana().GetAll() + require.NoError(t, err) + require.Len(t, solkeys, 1) + + transmitter := solkeys[0] + transmitters[chain] = transmitter.ID() + + // TODO: funding } return Keys{ - PeerID: peerID, - CSA: csaKey, - TransmittersByEVMChainID: transmitters, - OCRKeyBundles: keybundles, + PeerID: peerID, + CSA: csaKey, + Transmitters: transmitters, + OCRKeyBundles: keybundles, } } @@ -313,6 +404,28 @@ func createConfigV2Chain(chainID uint64) *v2toml.EVMConfig { } } +func createSolanaChainConfig(chainID string, chain deployment.SolChain) *solcfg.TOMLConfig { + chainConfig := solcfg.Chain{} + chainConfig.SetDefaults() + + url, err := config.ParseURL(chain.URL) + if err != nil { + panic(err) + } + + return &solcfg.TOMLConfig{ + ChainID: &chainID, + Enabled: ptr(true), + Chain: chainConfig, + MultiNode: solcfg.MultiNodeConfig{}, + Nodes: []*solcfg.Node{{ + Name: ptr("primary"), + URL: url, + SendOnly: false, + }}, + } +} + func ptr[T any](v T) *T { return &v } var _ keystore.Eth = &EthKeystoreSim{} diff --git a/deployment/environment/memory/node_test.go b/deployment/environment/memory/node_test.go index 78bc2db90e5..b9562f0290a 100644 --- a/deployment/environment/memory/node_test.go +++ b/deployment/environment/memory/node_test.go @@ -15,7 +15,7 @@ import ( func TestNode(t *testing.T) { chains, _ := NewMemoryChains(t, 3, 5) ports := freeport.GetN(t, 1) - node := NewNode(t, ports[0], chains, zapcore.DebugLevel, false, deployment.CapabilityRegistryConfig{}) + node := NewNode(t, ports[0], chains, nil, zapcore.DebugLevel, false, deployment.CapabilityRegistryConfig{}) // We expect 3 transmitter keys keys, err := node.App.GetKeyStore().Eth().GetAll(tests.Context(t)) require.NoError(t, err) diff --git a/shell.nix b/shell.nix index 4065e7e3def..ee0187701c1 100644 --- a/shell.nix +++ b/shell.nix @@ -6,6 +6,53 @@ with pkgs; let nodePackages = pkgs.nodePackages.override {inherit nodejs;}; pnpm = pnpm_9; + version = "v2.0.18"; + getBinDerivation = + { + name, + filename, + sha256, + }: + pkgs.stdenv.mkDerivation rec { + inherit name; + url = "https://github.com/anza-xyz/agave/releases/download/${version}/${filename}"; + + nativeBuildInputs = [ + autoPatchelfHook + ]; + + autoPatchelfIgnoreMissingDeps = true; + + buildInputs = with pkgs; [stdenv.cc.cc.libgcc stdenv.cc.cc.lib] ++ lib.optionals stdenv.isLinux [ libudev-zero ]; + + src = pkgs.fetchzip { + inherit url sha256; + }; + + installPhase = '' + mkdir -p $out/bin + ls -lah $src + cp -r $src/bin/* $out/bin + ''; + }; + + solanaBinaries = { + x86_64-linux = getBinDerivation { + name = "solana-cli-x86_64-linux"; + filename = "solana-release-x86_64-unknown-linux-gnu.tar.bz2"; + ### BEGIN_LINUX_SHA256 ### + sha256 = "sha256-3FW6IMZeDtyU4GTsRIwT9BFLNzLPEuP+oiQdur7P13s="; + ### END_LINUX_SHA256 ### + }; + aarch64-apple-darwin = getBinDerivation { + name = "solana-cli-aarch64-apple-darwin"; + filename = "solana-release-aarch64-apple-darwin.tar.bz2"; + ### BEGIN_DARWIN_SHA256 ### + sha256 = "sha256-6VjycYU0NU0evXoqtGAZMYGHQEKijofnFQnBJNVsb6Q="; + ### END_DARWIN_SHA256 ### + }; + }; + mkShell' = mkShell.override { # The current nix default sdk for macOS fails to compile go projects, so we use a newer one for now. stdenv = @@ -18,7 +65,7 @@ in nativeBuildInputs = [ go - postgresql + postgresql_17 python3 python3Packages.pip @@ -40,7 +87,7 @@ in gopls delve golangci-lint - github-cli + git jq # gofuzz @@ -50,9 +97,12 @@ in pkg-config libudev-zero libusb1 + solanaBinaries.x86_64-linux ] ++ lib.optionals isCrib [ nur.repos.goreleaser.goreleaser-pro patchelf + ] ++ pkgs.lib.optionals (pkgs.stdenv.isDarwin && pkgs.stdenv.hostPlatform.isAarch64) [ + solanaBinaries.aarch64-apple-darwin ]; shellHook = '' @@ -68,4 +118,4 @@ in GOROOT = "${go}/share/go"; PGDATA = "db"; CL_DATABASE_URL = "postgresql://chainlink:chainlink@localhost:5432/chainlink_test?sslmode=disable"; - } + } \ No newline at end of file