Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 52 additions & 14 deletions universalClient/core/client.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Package core provides the top-level orchestrator for the Push Universal Validator.
// It wires together all subsystems (pushcore, pushsigner, chains, tss, api)
// It wires together all subsystems (pushcore, pushsigner, externalchains, pushwatcher, tss, api)
// and manages their lifecycle.
package core

Expand All @@ -11,12 +11,13 @@ import (

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/pushchain/push-chain-node/universalClient/api"
"github.com/pushchain/push-chain-node/universalClient/chains"
"github.com/pushchain/push-chain-node/universalClient/config"
"github.com/pushchain/push-chain-node/universalClient/db"
"github.com/pushchain/push-chain-node/universalClient/externalchains"
"github.com/pushchain/push-chain-node/universalClient/logger"
"github.com/pushchain/push-chain-node/universalClient/pushcore"
"github.com/pushchain/push-chain-node/universalClient/pushsigner"
"github.com/pushchain/push-chain-node/universalClient/pushwatcher"
"github.com/pushchain/push-chain-node/universalClient/tss"
"github.com/rs/zerolog"
)
Expand All @@ -29,7 +30,8 @@ type UniversalClient struct {
queryServer *api.Server
pushCore *pushcore.Client
pushSigner *pushsigner.Signer
chains *chains.Chains
chains *externalchains.Chains
pushChain *pushwatcher.Client
tssNode *tss.Node
}

Expand Down Expand Up @@ -66,9 +68,26 @@ func NewUniversalClient(ctx context.Context, cfg *config.Config) (*UniversalClie
return nil, fmt.Errorf("failed to create pushsigner: %w", err)
}

chainsManager := chains.NewChains(pushCore, pushSigner, cfg, log)
chainsManager := externalchains.NewChains(pushCore, pushSigner, cfg, log)

tssNode, err := initTSS(ctx, cfg, pushCore, chainsManager, pushSigner, log)
// Push chain DB is shared by the push chain client and the TSS node.
pushDB, err := openPushDB(cfg)
if err != nil {
return nil, err
}

pushChain, err := pushwatcher.NewClient(
pushDB,
cfg.GetChainConfig(cfg.PushChainID),
pushCore,
cfg.PushChainID,
log,
)
if err != nil {
return nil, fmt.Errorf("failed to create push chain client: %w", err)
}

tssNode, err := initTSS(ctx, cfg, pushCore, chainsManager, pushSigner, pushDB, log)
if err != nil {
return nil, err
}
Expand All @@ -83,10 +102,26 @@ func NewUniversalClient(ctx context.Context, cfg *config.Config) (*UniversalClie
pushCore: pushCore,
pushSigner: pushSigner,
chains: chainsManager,
pushChain: pushChain,
tssNode: tssNode,
}, nil
}

// openPushDB opens the Push chain database under NodeHome.
func openPushDB(cfg *config.Config) (*db.DB, error) {
if cfg.PushChainID == "" {
return nil, fmt.Errorf("push_chain_id is required")
}
// Sanitize chain ID for use as a database filename (e.g. "push_42101-1" → "push_42101-1.db")
dbFilename := sanitizeForFilename(cfg.PushChainID) + ".db"
baseDir := filepath.Join(cfg.NodeHome, config.DatabasesSubdir)
pushDB, err := db.OpenFileDB(baseDir, dbFilename, true)
if err != nil {
return nil, fmt.Errorf("failed to create push database: %w", err)
}
return pushDB, nil
}

// Start launches all subsystems, blocks until ctx is cancelled, then shuts down.
func (uc *UniversalClient) Start() error {
uc.log.Info().Msg("starting universal client")
Expand All @@ -95,6 +130,10 @@ func (uc *UniversalClient) Start() error {
return fmt.Errorf("failed to start chains manager: %w", err)
}

if err := uc.pushChain.Start(uc.ctx); err != nil {
return fmt.Errorf("failed to start push chain client: %w", err)
}

if uc.tssNode != nil {
if err := uc.tssNode.Start(uc.ctx); err != nil {
return fmt.Errorf("failed to start TSS node: %w", err)
Expand Down Expand Up @@ -127,6 +166,12 @@ func (uc *UniversalClient) shutdown() {
}
}

if uc.pushChain != nil {
if err := uc.pushChain.Stop(); err != nil {
uc.log.Error().Err(err).Str("subsystem", "push_chain").Msg("subsystem failed to stop")
}
}

if uc.chains != nil {
uc.chains.Stop()
}
Expand Down Expand Up @@ -158,22 +203,15 @@ func initTSS(
ctx context.Context,
cfg *config.Config,
pushCore *pushcore.Client,
chainsManager *chains.Chains,
chainsManager *externalchains.Chains,
pushSigner *pushsigner.Signer,
pushDB *db.DB,
log zerolog.Logger,
) (*tss.Node, error) {
if cfg.PushValoperAddress == "" || cfg.TSSP2PPrivateKeyHex == "" {
return nil, nil
}

// Sanitize chain ID for use as a database filename (e.g. "push_42101-1" → "push_42101-1.db")
dbFilename := sanitizeForFilename(cfg.PushChainID) + ".db"
baseDir := filepath.Join(cfg.NodeHome, config.DatabasesSubdir)
pushDB, err := db.OpenFileDB(baseDir, dbFilename, true)
if err != nil {
return nil, fmt.Errorf("failed to create push database: %w", err)
}

node, err := tss.NewNode(ctx, tss.Config{
ValidatorAddress: cfg.PushValoperAddress,
P2PPrivateKeyHex: cfg.TSSP2PPrivateKeyHex,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package chains
package externalchains

import (
"context"
Expand All @@ -7,12 +7,11 @@ import (
"sync"
"time"

"github.com/pushchain/push-chain-node/universalClient/chains/common"
"github.com/pushchain/push-chain-node/universalClient/chains/evm"
"github.com/pushchain/push-chain-node/universalClient/chains/push"
"github.com/pushchain/push-chain-node/universalClient/chains/svm"
"github.com/pushchain/push-chain-node/universalClient/config"
"github.com/pushchain/push-chain-node/universalClient/db"
"github.com/pushchain/push-chain-node/universalClient/externalchains/common"
"github.com/pushchain/push-chain-node/universalClient/externalchains/evm"
"github.com/pushchain/push-chain-node/universalClient/externalchains/svm"
"github.com/pushchain/push-chain-node/universalClient/pushcore"
"github.com/pushchain/push-chain-node/universalClient/pushsigner"
uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types"
Expand Down Expand Up @@ -75,14 +74,6 @@ func (c *Chains) Start(ctx context.Context) error {
return fmt.Errorf("pushCore must be non-nil")
}

// Push chain client is a hard requirement: the universal client cannot
// do meaningful work (TSS coordination, signing, validator-set discovery)
// without it, so a startup failure here surfaces immediately rather than
// running degraded and relying on the periodic loop to recover.
if err := c.ensurePushChain(ctx); err != nil {
return fmt.Errorf("failed to attach push chain client: %w", err)
}

c.running = true
c.stopCh = make(chan struct{})
c.wg.Add(1)
Expand Down Expand Up @@ -158,13 +149,10 @@ func (c *Chains) fetchAndUpdate(parent context.Context) error {
return err
}

// Track seen chains (Push chain always marked as seen)
// Track seen chains
seenChains := make(map[string]bool)
if c.pushChainID != "" {
seenChains[c.pushChainID] = true
}

// Process each chain config
// Process each chain config (Push chain is managed by core, not here)
for _, cfg := range cfgs {
chainID := cfg.Chain
if chainID == "" || chainID == c.pushChainID {
Expand Down Expand Up @@ -199,10 +187,10 @@ func (c *Chains) fetchAndUpdate(parent context.Context) error {
}
}

// Remove stale chains (never remove Push chain)
// Remove stale chains
c.chainsMu.RLock()
for chainID := range c.chains {
if chainID != c.pushChainID && !seenChains[chainID] {
if !seenChains[chainID] {
c.logger.Info().Str("chain", chainID).Msg("removing chain no longer in config")
if err := c.removeChain(chainID); err != nil {
c.logger.Error().Err(err).Str("chain", chainID).Msg("failed to remove chain")
Expand Down Expand Up @@ -426,71 +414,6 @@ func (c *Chains) getChainDB(chainID string) (*db.DB, error) {
return database, nil
}

// ensurePushChain ensures the push chain client is always present
func (c *Chains) ensurePushChain(ctx context.Context) error {
if c.pushChainID == "" {
return fmt.Errorf("push chain ID not configured")
}

c.chainsMu.RLock()
_, exists := c.chains[c.pushChainID]
c.chainsMu.RUnlock()

if exists {
return nil // Already exists
}

// Get or create database for push chain
pushDB, err := c.getChainDB(c.pushChainID)
if err != nil {
return fmt.Errorf("failed to get database for push chain: %w", err)
}

// Create a minimal chain config for push chain
// Push chain doesn't need gateway or other configs
pushConfig := &uregistrytypes.ChainConfig{
Chain: c.pushChainID,
// VmType is not set for push chain as it's not a gateway chain
// GatewayAddress is empty for push chain
Enabled: &uregistrytypes.ChainEnabled{
IsInboundEnabled: true,
IsOutboundEnabled: true,
},
}

// Get chain-specific config for push chain
chainConfig := c.config.GetChainConfig(c.pushChainID)

// Create push chain client
client, err := push.NewClient(
pushDB,
chainConfig,
c.pushCore,
c.pushChainID,
c.logger,
)
if err != nil {
return fmt.Errorf("failed to create push chain client: %w", err)
}

// Start the push chain client
if err := client.Start(ctx); err != nil {
return fmt.Errorf("failed to start push chain client: %w", err)
}

// Store the client and config
c.chainsMu.Lock()
c.chains[c.pushChainID] = client
c.chainConfigs[c.pushChainID] = pushConfig
c.chainsMu.Unlock()

c.logger.Info().
Str("chain", c.pushChainID).
Msg("chain client added")

return nil
}

// Helper functions

// sanitizeChainID converts chain ID to filesystem-safe format
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package chains
package externalchains

import (
"context"
Expand All @@ -10,8 +10,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/pushchain/push-chain-node/universalClient/chains/common"
"github.com/pushchain/push-chain-node/universalClient/config"
"github.com/pushchain/push-chain-node/universalClient/externalchains/common"
uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types"
)

Expand Down Expand Up @@ -1472,26 +1472,6 @@ func TestAddChain_ErrorCases(t *testing.T) {
})
}

func TestEnsurePushChain_EmptyID(t *testing.T) {
t.Run("returns error when pushChainID is empty", func(t *testing.T) {
c := newTestChains()
c.pushChainID = ""

err := c.ensurePushChain(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "push chain ID not configured")
})

t.Run("returns nil when push chain already exists", func(t *testing.T) {
c := newTestChains()
mock := &mockChainClient{}
c.chains[c.pushChainID] = mock

err := c.ensurePushChain(context.Background())
require.NoError(t, err)
})
}

func TestGetChainDB(t *testing.T) {
t.Run("creates database for EVM chain", func(t *testing.T) {
c := newTestChains()
Expand Down Expand Up @@ -1588,20 +1568,6 @@ func TestStart_PushCoreNil(t *testing.T) {
})
}

func TestEnsurePushChain_DBError(t *testing.T) {
t.Run("returns error when getChainDB fails", func(t *testing.T) {
c := newTestChains()
c.config = &config.Config{
PushChainID: "localchain_9000-1",
NodeHome: "/dev/null/impossible/path",
}

err := c.ensurePushChain(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to get database for push chain")
})
}

func TestConfigsEqual_GatewayMethodsOrdering(t *testing.T) {
t.Run("gateway methods different order is not equal", func(t *testing.T) {
cfg1 := &uregistrytypes.ChainConfig{
Expand Down Expand Up @@ -1665,8 +1631,8 @@ func TestNewChains_ConfigPreserved(t *testing.T) {
t.Run("preserves all config fields", func(t *testing.T) {
logger := zerolog.Nop()
cfg := &config.Config{
PushChainID: "push:1",
NodeHome: "/tmp/test",
PushChainID: "push:1",
NodeHome: "/tmp/test",
ConfigRefreshIntervalSeconds: 30,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/rs/zerolog"

"github.com/pushchain/push-chain-node/universalClient/chains/common"
"github.com/pushchain/push-chain-node/universalClient/config"
"github.com/pushchain/push-chain-node/universalClient/db"
"github.com/pushchain/push-chain-node/universalClient/externalchains/common"
"github.com/pushchain/push-chain-node/universalClient/pushsigner"
uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/rs/zerolog"

chaincommon "github.com/pushchain/push-chain-node/universalClient/chains/common"
"github.com/pushchain/push-chain-node/universalClient/db"
chaincommon "github.com/pushchain/push-chain-node/universalClient/externalchains/common"
"github.com/pushchain/push-chain-node/universalClient/store"
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"testing"
"time"

"github.com/pushchain/push-chain-node/universalClient/chains/common"
"github.com/pushchain/push-chain-node/universalClient/db"
"github.com/pushchain/push-chain-node/universalClient/externalchains/common"
"github.com/pushchain/push-chain-node/universalClient/store"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/rs/zerolog"

"github.com/pushchain/push-chain-node/universalClient/chains/common"
"github.com/pushchain/push-chain-node/universalClient/db"
"github.com/pushchain/push-chain-node/universalClient/externalchains/common"
uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types"
)

Expand Down
Loading
Loading