Skip to content

Commit

Permalink
feat: observation of inbound SOL token deposit (zeta-chain#2465)
Browse files Browse the repository at this point in the history
* add a cmd as an experimental testbed for solana

* cmd(solana): sign ECDSA and build withdraw tx

* e2e: localnet solana WIP: initialize gateway program

* e2e(solana): start deposit test

* update solana program with chain id commit in TSS signature

* fix the initialize of solana program after update the program

* zetaclient(solana): observe and filter and parse deposit instruction

* zetaclient(solana): remember last observed tx to save RPC calls

* zetaclient(solana): report to zetacored about deposit

* localnet: deploy sol zrc20

* use solana chain from merged commits

* fix solana rpc localnet config

* fix chain params for solana

* initialted inbound observation on SOL deposit

* Use docker image and add make targets

* polish Solana initialize and deposit E2E tests

* make Solana inbound e2e test passing

* clean up unused files; reduce log prints

* added entry to changelog

* revert Dockerfile-localnet

* remove solana-test in Makefile because Solana e2e tests is ran as part of start-e2e-test

* polished e2e tests, solana config and chain parameters

* add issue link for TODO

* remove panics

* move Solana gateway program initialization to the contract setup phase

* refactor e2e clients creation; add context to Solana signature query

* fix unit tests

* added inbound last_scanned_block_number metrics

* integrate solana tests into CI

* filter at most two events [SOL + SPL] per solana tx to be consistent with EVM chain inbound observation

* fix unit test compile

* use observer context for Solana RPC calls

* better format stack information on panic

* move stack print into logError() to avoid duplicate log print

* Fix `bg`

* fix unit test

* rename pdaID as pda to be a more correct Solana terminology

---------

Co-authored-by: brewmaster012 <[email protected]>
Co-authored-by: Alex Gartner <[email protected]>
Co-authored-by: Dmitry <[email protected]>
  • Loading branch information
4 people authored Jul 19, 2024
1 parent 331ba6b commit 3ad6f99
Show file tree
Hide file tree
Showing 74 changed files with 3,071 additions and 157 deletions.
12 changes: 11 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ on:
type: boolean
required: false
default: false
solana-test:
type: boolean
required: false
default: false

concurrency:
group: e2e-${{ github.head_ref || github.sha }}
Expand All @@ -66,7 +70,7 @@ jobs:
PERFORMANCE_TESTS: ${{ steps.matrix-conditionals.outputs.PERFORMANCE_TESTS }}
STATEFUL_DATA_TESTS: ${{ steps.matrix-conditionals.outputs.STATEFUL_DATA_TESTS }}
TSS_MIGRATION_TESTS: ${{ steps.matrix-conditionals.outputs.TSS_MIGRATION_TESTS }}

SOLANA_TESTS: ${{ steps.matrix-conditionals.outputs.SOLANA_TESTS }}
steps:
# use api rather than event context to avoid race conditions (label added after push)
- id: matrix-conditionals
Expand All @@ -89,6 +93,7 @@ jobs:
core.setOutput('PERFORMANCE_TESTS', labels.includes('PERFORMANCE_TESTS'));
core.setOutput('STATEFUL_DATA_TESTS', labels.includes('STATEFUL_DATA_TESTS'));
core.setOutput('TSS_MIGRATION_TESTS', labels.includes('TSS_MIGRATION_TESTS'));
core.setOutput('SOLANA_TESTS', labels.includes('SOLANA_TESTS'));
} else if (context.eventName === 'merge_group') {
core.setOutput('DEFAULT_TESTS', true);
} else if (context.eventName === 'push' && context.ref === 'refs/heads/develop') {
Expand All @@ -109,6 +114,7 @@ jobs:
core.setOutput('ADMIN_TESTS', true);
core.setOutput('PERFORMANCE_TESTS', true);
core.setOutput('STATEFUL_DATA_TESTS', true);
core.setOutput('SOLANA_TESTS', true);
} else if (context.eventName === 'workflow_dispatch') {
core.setOutput('DEFAULT_TESTS', context.payload.inputs['default-test']);
core.setOutput('UPGRADE_TESTS', context.payload.inputs['upgrade-test']);
Expand All @@ -118,6 +124,7 @@ jobs:
core.setOutput('PERFORMANCE_TESTS', context.payload.inputs['performance-test']);
core.setOutput('STATEFUL_DATA_TESTS', context.payload.inputs['stateful-data-test']);
core.setOutput('TSS_MIGRATION_TESTS', context.payload.inputs['tss-migration-test']);
core.setOutput('SOLANA_TESTS', context.payload.inputs['solana-test']);
}
e2e:
Expand Down Expand Up @@ -150,6 +157,9 @@ jobs:
- make-target: "start-tss-migration-test"
runs-on: ubuntu-20.04
run: ${{ needs.matrix-conditionals.outputs.TSS_MIGRATION_TESTS == 'true' }}
- make-target: "start-solana-test"
runs-on: ubuntu-20.04
run: ${{ needs.matrix-conditionals.outputs.SOLANA_TESTS == 'true' }}
name: ${{ matrix.make-target }}
uses: ./.github/workflows/reusable-e2e.yml
with:
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ install-zetae2e: go.sum
@go install -mod=readonly $(BUILD_FLAGS) ./cmd/zetae2e
.PHONY: install-zetae2e

solana:
@echo "Building solana docker image"
$(DOCKER) build -t solana-local -f contrib/localnet/solana/Dockerfile contrib/localnet/solana/

start-e2e-test: zetanode
@echo "--> Starting e2e test"
cd contrib/localnet/ && $(DOCKER) compose up -d
Expand Down Expand Up @@ -259,6 +263,11 @@ start-tss-migration-test: zetanode
export E2E_ARGS="--test-tss-migration" && \
cd contrib/localnet/ && $(DOCKER) compose up -d

start-solana-test: zetanode solana
@echo "--> Starting solana test"
export E2E_ARGS="--skip-regular --test-solana" && \
cd contrib/localnet/ && $(DOCKER) compose --profile solana -f docker-compose.yml up -d

###############################################################################
### Upgrade Tests ###
###############################################################################
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
* [2366](https://github.com/zeta-chain/node/pull/2366) - add migration script for adding authorizations table
* [2372](https://github.com/zeta-chain/node/pull/2372) - add queries for tss fund migration info
* [2416](https://github.com/zeta-chain/node/pull/2416) - add Solana chain information
* [2465](https://github.com/zeta-chain/node/pull/2465) - add Solana inbound SOL token observation

### Refactor

Expand Down
2 changes: 2 additions & 0 deletions cmd/zetaclientd/start_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ func maskCfg(cfg config.Config) string {
chain.Endpoint = endpointURL.Hostname()
}

// mask endpoints
maskedCfg.BitcoinConfig.RPCUsername = ""
maskedCfg.BitcoinConfig.RPCPassword = ""
maskedCfg.SolanaConfig.Endpoint = ""

return maskedCfg.String()
}
40 changes: 38 additions & 2 deletions cmd/zetaclientd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
solrpc "github.com/gagliardetto/solana-go/rpc"
"github.com/rs/zerolog"

"github.com/zeta-chain/zetacore/zetaclient/authz"
Expand All @@ -17,6 +18,7 @@ import (
evmobserver "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer"
evmsigner "github.com/zeta-chain/zetacore/zetaclient/chains/evm/signer"
"github.com/zeta-chain/zetacore/zetaclient/chains/interfaces"
solanaobserver "github.com/zeta-chain/zetacore/zetaclient/chains/solana/observer"
"github.com/zeta-chain/zetacore/zetaclient/config"
"github.com/zeta-chain/zetacore/zetaclient/context"
"github.com/zeta-chain/zetacore/zetaclient/keys"
Expand Down Expand Up @@ -168,7 +170,7 @@ func CreateChainObserverMap(
}

// BTC observer
_, chainParams, found := appContext.GetBTCChainParams()
_, btcChainParams, found := appContext.GetBTCChainParams()
if !found {
return nil, fmt.Errorf("bitcoin chains params not found")
}
Expand All @@ -184,7 +186,7 @@ func CreateChainObserverMap(
observer, err := btcobserver.NewObserver(
btcChain,
btcClient,
*chainParams,
*btcChainParams,
zetacoreClient,
tss,
dbpath,
Expand All @@ -199,5 +201,39 @@ func CreateChainObserverMap(
}
}

// Solana chain params
_, solChainParams, found := appContext.GetSolanaChainParams()
if !found {
logger.Std.Error().Msg("solana chain params not found")
return observerMap, nil
}

// create Solana chain observer
solChain, solConfig, enabled := appContext.GetSolanaChainAndConfig()
if enabled {
rpcClient := solrpc.New(solConfig.Endpoint)
if rpcClient == nil {
// should never happen
logger.Std.Error().Msg("solana create Solana client error")
return observerMap, nil
}

observer, err := solanaobserver.NewObserver(
solChain,
rpcClient,
*solChainParams,
zetacoreClient,
tss,
dbpath,
logger,
ts,
)
if err != nil {
logger.Std.Error().Err(err).Msg("NewObserver error for solana chain")
} else {
observerMap[solChainParams.ChainId] = observer
}
}

return observerMap, nil
}
76 changes: 49 additions & 27 deletions cmd/zetae2e/config/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/gagliardetto/solana-go/rpc"
"google.golang.org/grpc"

"github.com/zeta-chain/zetacore/e2e/config"
Expand All @@ -18,51 +19,72 @@ import (
observertypes "github.com/zeta-chain/zetacore/x/observer/types"
)

// E2EClients contains all the RPC clients and gRPC clients for E2E tests
type E2EClients struct {
// the RPC clients for external chains in the localnet
BtcRPCClient *rpcclient.Client
SolanaClient *rpc.Client
EvmClient *ethclient.Client
EvmAuth *bind.TransactOpts

// the gRPC clients for ZetaChain
CctxClient crosschaintypes.QueryClient
FungibleClient fungibletypes.QueryClient
AuthClient authtypes.QueryClient
BankClient banktypes.QueryClient
ObserverClient observertypes.QueryClient
LightClient lightclienttypes.QueryClient

// the RPC clients for ZetaChain
ZevmClient *ethclient.Client
ZevmAuth *bind.TransactOpts
}

// getClientsFromConfig get clients from config
func getClientsFromConfig(ctx context.Context, conf config.Config, account config.Account) (
*rpcclient.Client,
*ethclient.Client,
*bind.TransactOpts,
crosschaintypes.QueryClient,
fungibletypes.QueryClient,
authtypes.QueryClient,
banktypes.QueryClient,
observertypes.QueryClient,
lightclienttypes.QueryClient,
*ethclient.Client,
*bind.TransactOpts,
E2EClients,
error,
) {
if conf.RPCs.Solana == "" {
return E2EClients{}, fmt.Errorf("solana rpc is empty")
}
solanaClient := rpc.New(conf.RPCs.Solana)
if solanaClient == nil {
return E2EClients{}, fmt.Errorf("failed to get solana client")
}
btcRPCClient, err := getBtcClient(conf.RPCs.Bitcoin)
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get btc client: %w", err)
return E2EClients{}, fmt.Errorf("failed to get btc client: %w", err)
}
evmClient, evmAuth, err := getEVMClient(ctx, conf.RPCs.EVM, account)
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get evm client: %w", err)
return E2EClients{}, fmt.Errorf("failed to get evm client: %w", err)
}
cctxClient, fungibleClient, authClient, bankClient, observerClient, lightclientClient, err := getZetaClients(
conf.RPCs.ZetaCoreGRPC,
)
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get zeta clients: %w", err)
return E2EClients{}, fmt.Errorf("failed to get zeta clients: %w", err)
}
zevmClient, zevmAuth, err := getEVMClient(ctx, conf.RPCs.Zevm, account)
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("failed to get zevm client: %w", err)
return E2EClients{}, fmt.Errorf("failed to get zevm client: %w", err)
}
return btcRPCClient,
evmClient,
evmAuth,
cctxClient,
fungibleClient,
authClient,
bankClient,
observerClient,
lightclientClient,
zevmClient,
zevmAuth,
nil

return E2EClients{
BtcRPCClient: btcRPCClient,
SolanaClient: solanaClient,
EvmClient: evmClient,
EvmAuth: evmAuth,
CctxClient: cctxClient,
FungibleClient: fungibleClient,
AuthClient: authClient,
BankClient: bankClient,
ObserverClient: observerClient,
LightClient: lightclientClient,
ZevmClient: zevmClient,
ZevmAuth: zevmAuth,
}, nil
}

// getBtcClient get btc client
Expand Down
42 changes: 18 additions & 24 deletions cmd/zetae2e/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,8 @@ func RunnerFromConfig(
logger *runner.Logger,
opts ...runner.E2ERunnerOption,
) (*runner.E2ERunner, error) {
// initialize clients
btcRPCClient,
evmClient,
evmAuth,
cctxClient,
fungibleClient,
authClient,
bankClient,
observerClient,
lightClient,
zevmClient,
zevmAuth,
err := getClientsFromConfig(ctx, conf, account)
// initialize all clients for E2E tests
e2eClients, err := getClientsFromConfig(ctx, conf, account)
if err != nil {
return nil, fmt.Errorf("failed to get clients from config: %w", err)
}
Expand All @@ -41,17 +30,19 @@ func RunnerFromConfig(
name,
ctxCancel,
account,
evmClient,
zevmClient,
cctxClient,
fungibleClient,
authClient,
bankClient,
observerClient,
lightClient,
evmAuth,
zevmAuth,
btcRPCClient,
e2eClients.EvmClient,
e2eClients.ZevmClient,
e2eClients.CctxClient,
e2eClients.FungibleClient,
e2eClients.AuthClient,
e2eClients.BankClient,
e2eClients.ObserverClient,
e2eClients.LightClient,
e2eClients.EvmAuth,
e2eClients.ZevmAuth,
e2eClients.BtcRPCClient,
e2eClients.SolanaClient,

logger,
opts...,
)
Expand All @@ -74,6 +65,8 @@ func RunnerFromConfig(

// ExportContractsFromRunner export contracts from the runner to config using a source config
func ExportContractsFromRunner(r *runner.E2ERunner, conf config.Config) config.Config {
conf.Contracts.Solana.GatewayProgramID = r.GatewayProgram.String()

// copy contracts from deployer runner
conf.Contracts.EVM.ZetaEthAddr = config.DoubleQuotedString(r.ZetaEthAddr.Hex())
conf.Contracts.EVM.ConnectorEthAddr = config.DoubleQuotedString(r.ConnectorEthAddr.Hex())
Expand All @@ -85,6 +78,7 @@ func ExportContractsFromRunner(r *runner.E2ERunner, conf config.Config) config.C
conf.Contracts.ZEVM.ETHZRC20Addr = config.DoubleQuotedString(r.ETHZRC20Addr.Hex())
conf.Contracts.ZEVM.ERC20ZRC20Addr = config.DoubleQuotedString(r.ERC20ZRC20Addr.Hex())
conf.Contracts.ZEVM.BTCZRC20Addr = config.DoubleQuotedString(r.BTCZRC20Addr.Hex())
conf.Contracts.ZEVM.SOLZRC20Addr = config.DoubleQuotedString(r.SOLZRC20Addr.Hex())
conf.Contracts.ZEVM.UniswapFactoryAddr = config.DoubleQuotedString(r.UniswapV2FactoryAddr.Hex())
conf.Contracts.ZEVM.UniswapRouterAddr = config.DoubleQuotedString(r.UniswapV2RouterAddr.Hex())
conf.Contracts.ZEVM.ConnectorZEVMAddr = config.DoubleQuotedString(r.ConnectorZEVMAddr.Hex())
Expand Down
17 changes: 17 additions & 0 deletions cmd/zetae2e/config/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"fmt"

"github.com/gagliardetto/solana-go"
"github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol"
zetaeth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zeta.eth.sol"
zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol"
Expand All @@ -24,6 +25,11 @@ import (
func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error {
var err error

// set Solana contracts
if c := conf.Contracts.Solana.GatewayProgramID; c != "" {
r.GatewayProgram = solana.MustPublicKeyFromBase58(c)
}

// set EVM contracts
if c := conf.Contracts.EVM.ZetaEthAddr; c != "" {
r.ZetaEthAddr, err = c.AsEVMAddress()
Expand Down Expand Up @@ -114,6 +120,17 @@ func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error {
}
}

if c := conf.Contracts.ZEVM.SOLZRC20Addr; c != "" {
r.SOLZRC20Addr, err = c.AsEVMAddress()
if err != nil {
return fmt.Errorf("invalid SOLZRC20Addr: %w", err)
}
r.SOLZRC20, err = zrc20.NewZRC20(r.SOLZRC20Addr, r.ZEVMClient)
if err != nil {
return err
}
}

if c := conf.Contracts.ZEVM.UniswapFactoryAddr; c != "" {
r.UniswapV2FactoryAddr, err = c.AsEVMAddress()
if err != nil {
Expand Down
Loading

0 comments on commit 3ad6f99

Please sign in to comment.