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

feat: Solana relayer (fee payer) key importer, encryption and decryption #2673

Merged
merged 17 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
Next Next commit
configure observer relayer key for Solana; remove hardcoded solana te…
…st key from zetaclient code
  • Loading branch information
ws4charlie committed Aug 7, 2024
commit 2d225c8fadb8d20c1d6d46838d0408f00e6cd309
16 changes: 4 additions & 12 deletions cmd/zetaclientd/init.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package main

import (
"path"

"github.com/rs/zerolog"
"github.com/spf13/cobra"

Expand Down Expand Up @@ -38,7 +36,7 @@ type initArguments struct {
KeyringBackend string
HsmMode bool
HsmHotKey string
SolanaKey string
RelayerKeyPath string
}

func init() {
Expand Down Expand Up @@ -72,7 +70,8 @@ func init() {
InitCmd.Flags().BoolVar(&initArgs.HsmMode, "hsm-mode", false, "enable hsm signer, default disabled")
InitCmd.Flags().
StringVar(&initArgs.HsmHotKey, "hsm-hotkey", "hsm-hotkey", "name of hotkey associated with hardware security module")
InitCmd.Flags().StringVar(&initArgs.SolanaKey, "solana-key", "solana-key.json", "solana key file name")
InitCmd.Flags().
StringVar(&initArgs.RelayerKeyPath, "relayer-key-path", "~/.zetacored/relayer-keys", "path to relayer keys")
}

func Initialize(_ *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -110,16 +109,9 @@ func Initialize(_ *cobra.Command, _ []string) error {
configData.KeyringBackend = config.KeyringBackend(initArgs.KeyringBackend)
configData.HsmMode = initArgs.HsmMode
configData.HsmHotKey = initArgs.HsmHotKey
configData.SolanaKeyFile = initArgs.SolanaKey
configData.RelayerKeyPath = initArgs.RelayerKeyPath
configData.ComplianceConfig = testutils.ComplianceConfigTest()

// Save solana test fee payer key file
keyFile := path.Join(rootArgs.zetaCoreHome, initArgs.SolanaKey)
err = createSolanaTestKeyFile(keyFile)
if err != nil {
return err
}

// Save config file
return config.Save(&configData, rootArgs.zetaCoreHome)
}
37 changes: 0 additions & 37 deletions cmd/zetaclientd/solana_test_key.go

This file was deleted.

7 changes: 7 additions & 0 deletions cmd/zetae2e/config/localnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ policy_accounts:
bech32_address: "zeta142ds9x7raljv2qz9euys93e64gjmgdfnc47dwq"
evm_address: "0xAa9b029BC3EFe4c50045Cf0902c73aAa25b43533"
private_key: "0595CB0CD9BF5264A85A603EC8E43C30ADBB5FD2D9E2EF84C374EA4A65BB616C"
observer_relayer_accounts:
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
relayer_account_0:
solana_address: "2qBVcNBZCubcnSR3NyCnFjCfkCVUB3G7ECPoaW5rxVjx"
solana_private_key: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ"
relayer_account_1:
solana_address: "4kkCV8H38xirwQTkE5kL6FHNtYGHnMQQ7SkCjAxibHFK"
solana_private_key: "5SSv7jWzamtjWNKGiKf3gvCPHcq9mE5x6LhYgzJCKNSxoQ83gFpmMgmg2JS2zdKcBEdwy7y9bvWgX4LBiUpvnrPf"
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
rpcs:
zevm: "http://zetacore0:8545"
evm: "http://eth:8545"
Expand Down
2 changes: 2 additions & 0 deletions contrib/localnet/orchestrator/Dockerfile.fastbuild
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
FROM zetanode:latest as zeta
FROM ghcr.io/zeta-chain/ethereum-client-go:v1.10.26 as geth
FROM ghcr.io/zeta-chain/solana-docker:1.18.15 as solana
FROM ghcr.io/zeta-chain/golang:1.22.5-bookworm as orchestrator

RUN apt update && \
apt install -yq jq yq curl tmux python3 openssh-server iputils-ping iproute2 && \
rm -rf /var/lib/apt/lists/*

COPY --from=geth /usr/local/bin/geth /usr/local/bin/
COPY --from=solana /usr/bin/solana /usr/local/bin/
COPY --from=zeta /usr/local/bin/zetacored /usr/local/bin/zetaclientd /usr/local/bin/zetae2e /usr/local/bin/

COPY contrib/localnet/orchestrator/start-zetae2e.sh /work/
Expand Down
12 changes: 12 additions & 0 deletions contrib/localnet/orchestrator/start-zetae2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ address=$(yq -r '.additional_accounts.user_migration.evm_address' config.yml)
echo "funding migration tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545

# unlock local solana relayer accounts
solana_url=$(yq -r '.rpcs.solana' config.yml)
solana config set --url "$solana_url" > /dev/null

relayer=$(yq -r '.observer_relayer_accounts.relayer_account_0.solana_address' config.yml)
echo "funding solana relayer address ${relayer} with 100 SOL"
solana airdrop 100 "$relayer" > /dev/null

relayer=$(yq -r '.observer_relayer_accounts.relayer_account_1.solana_address' config.yml)
echo "funding solana relayer address ${relayer} with 100 SOL"
solana airdrop 100 "$relayer" > /dev/null

### Run zetae2e command depending on the option passed

if [ "$LOCALNET_MODE" == "upgrade" ]; then
Expand Down
23 changes: 23 additions & 0 deletions contrib/localnet/scripts/start-zetaclientd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ set_sepolia_endpoint() {
jq '.EVMChainConfigs."11155111".Endpoint = "http://eth2:8545"' /root/.zetacored/config/zetaclient_config.json > tmp.json && mv tmp.json /root/.zetacored/config/zetaclient_config.json
}

# creates a file that contains a relayer private key (e.g. Solana relayer key)
create_relayer_key_file() {
local num="$1"
local file="$2"

# read observer relayer private key from config
privkey_relayer=$(yq -r ".observer_relayer_accounts.relayer_account_${num}.solana_private_key" /root/config.yml)

# create the key file that contains the private key
jq -n --arg privkey_relayer "$privkey_relayer" '{"private_key": $privkey_relayer}' > "${file}"
}
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved

PREPARAMS_PATH="/root/preparams/${HOSTNAME}.json"
if [[ -n "${ZETACLIENTD_GEN_PREPARAMS}" ]]; then
# generate pre-params as early as possible
Expand Down Expand Up @@ -54,13 +66,21 @@ done
operator=$(cat $HOME/.zetacored/os.json | jq '.ObserverAddress' )
operatorAddress=$(echo "$operator" | tr -d '"')
echo "operatorAddress: $operatorAddress"

# create the path that holds observer relayer private keys (e.g. Solana relayer key)
RELAYER_KEY_PATH="$HOME/.zetacored/relayer-keys"
mkdir -p "${RELAYER_KEY_PATH}"
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved

echo "Start zetaclientd"
# skip initialization if the config file already exists (zetaclientd init has already been run)
if [[ $HOSTNAME == "zetaclient0" && ! -f ~/.zetacored/config/zetaclient_config.json ]]
then
MYIP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)
zetaclientd init --zetacore-url zetacore0 --chain-id athens_101-1 --operator "$operatorAddress" --log-format=text --public-ip "$MYIP" --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH"

# create relayer key file for solana
create_relayer_key_file 0 "${RELAYER_KEY_PATH}/solana.json"

# if eth2 is enabled, set the endpoint in the zetaclient_config.json
# in this case, the additional evm is represented with the sepolia chain, we set manually the eth2 endpoint to the sepolia chain (11155111 -> http://eth2:8545)
# in /root/.zetacored/config/zetaclient_config.json
Expand All @@ -81,6 +101,9 @@ then
done
zetaclientd init --peer "/ip4/172.20.0.21/tcp/6668/p2p/${SEED}" --zetacore-url "$node" --chain-id athens_101-1 --operator "$operatorAddress" --log-format=text --public-ip "$MYIP" --log-level 1 --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH"

# create relayer key file for solana
create_relayer_key_file "${num}" "${RELAYER_KEY_PATH}/solana.json"

# check if the option is additional-evm
# in this case, the additional evm is represented with the sepolia chain, we set manually the eth2 endpoint to the sepolia chain (11155111 -> http://eth2:8545)
# in /root/.zetacored/config/zetaclient_config.json
Expand Down
20 changes: 14 additions & 6 deletions e2e/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,21 @@ func (s DoubleQuotedString) AsEVMAddress() (ethcommon.Address, error) {
// Config contains the configuration for the e2e test
type Config struct {
// Default account to use when running tests and running setup
DefaultAccount Account `yaml:"default_account"`
AdditionalAccounts AdditionalAccounts `yaml:"additional_accounts"`
PolicyAccounts PolicyAccounts `yaml:"policy_accounts"`
RPCs RPCs `yaml:"rpcs"`
Contracts Contracts `yaml:"contracts"`
ZetaChainID string `yaml:"zeta_chain_id"`
DefaultAccount Account `yaml:"default_account"`
AdditionalAccounts AdditionalAccounts `yaml:"additional_accounts"`
PolicyAccounts PolicyAccounts `yaml:"policy_accounts"`
ObserverRelayerAccounts ObserverRelayerAccounts `yaml:"observer_relayer_accounts"`
RPCs RPCs `yaml:"rpcs"`
Contracts Contracts `yaml:"contracts"`
ZetaChainID string `yaml:"zeta_chain_id"`
}

// Account contains configuration for an account
type Account struct {
RawBech32Address DoubleQuotedString `yaml:"bech32_address"`
RawEVMAddress DoubleQuotedString `yaml:"evm_address"`
RawPrivateKey DoubleQuotedString `yaml:"private_key"`
SolanaAddress DoubleQuotedString `yaml:"solana_address"`
SolanaPrivateKey DoubleQuotedString `yaml:"solana_private_key"`
}

Expand All @@ -76,6 +78,12 @@ type PolicyAccounts struct {
AdminPolicyAccount Account `yaml:"admin_policy_account"`
}

// ObserverRelayerAccounts are the accounts used by the observers to interact with gateway contracts in non-EVM chains (e.g. Solana)
type ObserverRelayerAccounts struct {
RelayerAccount0 Account `yaml:"relayer_account_0"`
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
RelayerAccount1 Account `yaml:"relayer_account_1"`
}

// RPCs contains the configuration for the RPC endpoints
type RPCs struct {
Zevm string `yaml:"zevm"`
Expand Down
29 changes: 18 additions & 11 deletions zetaclient/chains/solana/signer/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
observertypes "github.com/zeta-chain/zetacore/x/observer/types"
"github.com/zeta-chain/zetacore/zetaclient/chains/base"
"github.com/zeta-chain/zetacore/zetaclient/chains/interfaces"
"github.com/zeta-chain/zetacore/zetaclient/keys"
"github.com/zeta-chain/zetacore/zetaclient/metrics"
"github.com/zeta-chain/zetacore/zetaclient/outboundprocessor"
)
Expand All @@ -28,8 +29,8 @@ type Signer struct {
// client is the Solana RPC client that interacts with the Solana chain
client interfaces.SolanaRPCClient

// solanaFeePayerKey is the private key of the fee payer account on Solana chain
solanaFeePayerKey solana.PrivateKey
// relayerKey is the private key of the relayer account for Solana chain
relayerKey solana.PrivateKey

// gatewayID is the program ID of gateway program on Solana chain
gatewayID solana.PublicKey
Expand All @@ -44,7 +45,7 @@ func NewSigner(
chainParams observertypes.ChainParams,
solClient interfaces.SolanaRPCClient,
tss interfaces.TSSSigner,
solanaKey solana.PrivateKey,
relayerKey keys.RelayerKey,
ts *metrics.TelemetryServer,
logger base.Logger,
) (*Signer, error) {
Expand All @@ -56,15 +57,21 @@ func NewSigner(
if err != nil {
return nil, errors.Wrapf(err, "cannot parse gateway address %s", chainParams.GatewayAddress)
}
logger.Std.Info().Msgf("Solana fee payer address: %s", solanaKey.PublicKey())

// create solana observer
// construct Solana private key
privKey, err := solana.PrivateKeyFromBase58(relayerKey.PrivateKey)
if err != nil {
return nil, errors.Wrap(err, "unable to construct solana private key")
}
logger.Std.Info().Msgf("Solana relayer address: %s", privKey.PublicKey())

// create Solana signer
return &Signer{
Signer: baseSigner,
client: solClient,
solanaFeePayerKey: solanaKey,
gatewayID: gatewayID,
pda: pda,
Signer: baseSigner,
client: solClient,
relayerKey: privKey,
gatewayID: gatewayID,
pda: pda,
}, nil
}

Expand Down Expand Up @@ -114,7 +121,7 @@ func (signer *Signer) TryProcessOutbound(
return
}

// sign the withdraw transaction by fee payer
// sign the withdraw transaction by relayer key
tx, err := signer.SignWithdrawTx(ctx, *msg)
if err != nil {
logger.Error().Err(err).Msgf("TryProcessOutbound: SignWithdrawTx error for chain %d nonce %d", chainID, nonce)
Expand Down
6 changes: 3 additions & 3 deletions zetaclient/chains/solana/signer/withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (signer *Signer) SignMsgWithdraw(
return msg.SetSignature(signature), nil
}

// SignWithdrawTx wraps the withdraw 'msg' into a Solana transaction and signs it with the fee payer key.
// SignWithdrawTx wraps the withdraw 'msg' into a Solana transaction and signs it with the relayer key.
func (signer *Signer) SignWithdrawTx(ctx context.Context, msg contracts.MsgWithdraw) (*solana.Transaction, error) {
// create withdraw instruction with program call data
var err error
Expand All @@ -65,7 +65,7 @@ func (signer *Signer) SignWithdrawTx(ctx context.Context, msg contracts.MsgWithd
}

// attach required accounts to the instruction
privkey := signer.solanaFeePayerKey
privkey := signer.relayerKey
attachWithdrawAccounts(&inst, privkey.PublicKey(), signer.pda, msg.To(), signer.gatewayID)

// get a recent blockhash
Expand All @@ -89,7 +89,7 @@ func (signer *Signer) SignWithdrawTx(ctx context.Context, msg contracts.MsgWithd
return nil, errors.Wrap(err, "NewTransaction error")
}

// fee payer signs the transaction
// relayer signs the transaction
_, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey {
if key.Equals(privkey.PublicKey()) {
return &privkey
Expand Down
41 changes: 6 additions & 35 deletions zetaclient/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,9 @@ package config

import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"sync"

"cosmossdk.io/errors"
"github.com/gagliardetto/solana-go"

"github.com/zeta-chain/zetacore/pkg/chains"
)

Expand Down Expand Up @@ -85,9 +79,9 @@ type Config struct {
TssPath string `json:"TssPath"`
TestTssKeysign bool `json:"TestTssKeysign"`
KeyringBackend KeyringBackend `json:"KeyringBackend"`
RelayerKeyPath string `json:"RelayerKeyPath"`
HsmMode bool `json:"HsmMode"`
HsmHotKey string `json:"HsmHotKey"`
SolanaKeyFile string `json:"SolanaKeyFile"`

// chain configs
EVMChainConfigs map[int64]EVMConfig `json:"EVMChainConfigs"`
Expand Down Expand Up @@ -165,34 +159,11 @@ func (c Config) GetKeyringBackend() KeyringBackend {
return c.KeyringBackend
}

// LoadSolanaPrivateKey loads the Solana private key from the key file
func (c Config) LoadSolanaPrivateKey() (solana.PrivateKey, error) {
// key file path
fileName := path.Join(c.ZetaCoreHome, c.SolanaKeyFile)

// load the gateway keypair from a JSON file
// #nosec G304 -- user is allowed to specify the key file
fileContent, err := os.ReadFile(fileName)
if err != nil {
return solana.PrivateKey{}, errors.Wrapf(err, "unable to read Solana key file: %s", fileName)
}

// unmarshal the JSON content into a slice of bytes
var keyBytes []byte
err = json.Unmarshal(fileContent, &keyBytes)
if err != nil {
return solana.PrivateKey{}, errors.Wrap(err, "unable to unmarshal Solana key bytes")
}

// ensure the key length is 64 bytes
if len(keyBytes) != 64 {
return solana.PrivateKey{}, fmt.Errorf("invalid Solana key length: %d", len(keyBytes))
}

// create private key from the key bytes
privKey := solana.PrivateKey(keyBytes)

return privKey, nil
// GetRelayerKeyPath returns the relayer key path
func (c Config) GetRelayerKeyPath() string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.RelayerKeyPath
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
}

func (c EVMConfig) Empty() bool {
Expand Down
Loading