diff --git a/universalClient/chains/chains.go b/universalClient/chains/chains.go index 5a0c4f01..8947de8a 100644 --- a/universalClient/chains/chains.go +++ b/universalClient/chains/chains.go @@ -175,7 +175,6 @@ func (c *Chains) fetchAndUpdate(parent context.Context) error { switch action { case chainActionSkip: - // Disabled or no change - skip continue case chainActionAdd: @@ -191,6 +190,11 @@ func (c *Chains) fetchAndUpdate(parent context.Context) error { if err := c.addChain(parent, cfg); err != nil { c.logger.Error().Err(err).Str("chain", chainID).Msg("failed to add updated chain") } + + case chainActionRemove: + if err := c.removeChain(chainID); err != nil { + c.logger.Error().Err(err).Str("chain", chainID).Msg("failed to remove disabled chain") + } } } @@ -228,17 +232,29 @@ const ( func (c *Chains) determineChainAction(cfg *uregistrytypes.ChainConfig) chainAction { chainID := cfg.Chain - // Check if chain exists + // Check if chain is fully disabled (both flags off) + bothDisabled := cfg.Enabled == nil || + (!cfg.Enabled.IsInboundEnabled && !cfg.Enabled.IsOutboundEnabled) + c.chainsMu.RLock() _, exists := c.chains[chainID] existingConfig := c.chainConfigs[chainID] c.chainsMu.RUnlock() + if bothDisabled { + if exists { + c.logger.Info().Str("chain", chainID).Msg("chain fully disabled (inbound+outbound off), removing") + return chainActionRemove + } + c.logger.Debug().Str("chain", chainID).Msg("chain fully disabled, skipping") + return chainActionSkip + } + if !exists { return chainActionAdd } - // Check if config changed + // Check if config changed (includes enabled flag changes) if existingConfig != nil && !configsEqual(existingConfig, cfg) { return chainActionUpdate } @@ -364,6 +380,22 @@ func (c *Chains) IsEVMChain(chainID string) bool { return cfg != nil && cfg.VmType == uregistrytypes.VmType_EVM } +// IsChainInboundEnabled returns whether inbound is enabled for the given chain +func (c *Chains) IsChainInboundEnabled(chainID string) bool { + c.chainsMu.RLock() + cfg := c.chainConfigs[chainID] + c.chainsMu.RUnlock() + return cfg != nil && cfg.Enabled != nil && cfg.Enabled.IsInboundEnabled +} + +// IsChainOutboundEnabled returns whether outbound is enabled for the given chain +func (c *Chains) IsChainOutboundEnabled(chainID string) bool { + c.chainsMu.RLock() + cfg := c.chainConfigs[chainID] + c.chainsMu.RUnlock() + return cfg != nil && cfg.Enabled != nil && cfg.Enabled.IsOutboundEnabled +} + // GetStandardConfirmations returns the chain's standard block confirmations from registry config (BlockConfirmation.StandardInbound). Used for outbound tx completion. Returns 12 if not set. func (c *Chains) GetStandardConfirmations(chainID string) uint64 { c.chainsMu.RLock() @@ -506,6 +538,11 @@ func configsEqual(a, b *uregistrytypes.ChainConfig) bool { return false } + // Compare enabled flags + if !chainEnabledEqual(a.Enabled, b.Enabled) { + return false + } + return true } @@ -539,6 +576,17 @@ func vaultMethodsEqual(a, b []*uregistrytypes.VaultMethods) bool { return true } +func chainEnabledEqual(a, b *uregistrytypes.ChainEnabled) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return a.IsInboundEnabled == b.IsInboundEnabled && + a.IsOutboundEnabled == b.IsOutboundEnabled +} + func blockConfirmationEqual(a, b *uregistrytypes.BlockConfirmation) bool { if a == nil && b == nil { return true diff --git a/universalClient/chains/chains_test.go b/universalClient/chains/chains_test.go index d46c3336..675ca52c 100644 --- a/universalClient/chains/chains_test.go +++ b/universalClient/chains/chains_test.go @@ -259,21 +259,64 @@ func TestDetermineChainAction(t *testing.T) { } chains := NewChains(nil, nil, cfg, logger) + enabled := &uregistrytypes.ChainEnabled{IsInboundEnabled: true, IsOutboundEnabled: true} + t.Run("new chain returns add", func(t *testing.T) { chainCfg := &uregistrytypes.ChainConfig{ - Chain: "eip155:1", - VmType: uregistrytypes.VmType_EVM, + Chain: "eip155:1", + VmType: uregistrytypes.VmType_EVM, + Enabled: enabled, } action := chains.determineChainAction(chainCfg) assert.Equal(t, chainActionAdd, action) }) + t.Run("both flags off returns skip for new chain", func(t *testing.T) { + chainCfg := &uregistrytypes.ChainConfig{ + Chain: "eip155:99", + VmType: uregistrytypes.VmType_EVM, + Enabled: &uregistrytypes.ChainEnabled{IsInboundEnabled: false, IsOutboundEnabled: false}, + } + action := chains.determineChainAction(chainCfg) + assert.Equal(t, chainActionSkip, action) + }) + + t.Run("both flags off returns remove for existing chain", func(t *testing.T) { + chains.chainsMu.Lock() + chains.chains["eip155:99"] = nil + chains.chainConfigs["eip155:99"] = &uregistrytypes.ChainConfig{Chain: "eip155:99", Enabled: enabled} + chains.chainsMu.Unlock() + + chainCfg := &uregistrytypes.ChainConfig{ + Chain: "eip155:99", + VmType: uregistrytypes.VmType_EVM, + Enabled: &uregistrytypes.ChainEnabled{IsInboundEnabled: false, IsOutboundEnabled: false}, + } + action := chains.determineChainAction(chainCfg) + assert.Equal(t, chainActionRemove, action) + + chains.chainsMu.Lock() + delete(chains.chains, "eip155:99") + delete(chains.chainConfigs, "eip155:99") + chains.chainsMu.Unlock() + }) + + t.Run("nil enabled returns skip for new chain", func(t *testing.T) { + chainCfg := &uregistrytypes.ChainConfig{ + Chain: "eip155:98", + VmType: uregistrytypes.VmType_EVM, + } + action := chains.determineChainAction(chainCfg) + assert.Equal(t, chainActionSkip, action) + }) + t.Run("existing chain with same config returns skip", func(t *testing.T) { chainCfg := &uregistrytypes.ChainConfig{ Chain: "eip155:1", VmType: uregistrytypes.VmType_EVM, GatewayAddress: "0x123", + Enabled: enabled, } // Add the chain first @@ -297,12 +340,14 @@ func TestDetermineChainAction(t *testing.T) { Chain: "eip155:1", VmType: uregistrytypes.VmType_EVM, GatewayAddress: "0x123", + Enabled: enabled, } newCfg := &uregistrytypes.ChainConfig{ Chain: "eip155:1", VmType: uregistrytypes.VmType_EVM, GatewayAddress: "0x456", // Different address + Enabled: enabled, } // Add the chain first @@ -320,6 +365,33 @@ func TestDetermineChainAction(t *testing.T) { delete(chains.chainConfigs, "eip155:1") chains.chainsMu.Unlock() }) + + t.Run("enabled flag change triggers update", func(t *testing.T) { + oldCfg := &uregistrytypes.ChainConfig{ + Chain: "eip155:1", + VmType: uregistrytypes.VmType_EVM, + Enabled: enabled, + } + + newCfg := &uregistrytypes.ChainConfig{ + Chain: "eip155:1", + VmType: uregistrytypes.VmType_EVM, + Enabled: &uregistrytypes.ChainEnabled{IsInboundEnabled: true, IsOutboundEnabled: false}, + } + + chains.chainsMu.Lock() + chains.chains["eip155:1"] = nil + chains.chainConfigs["eip155:1"] = oldCfg + chains.chainsMu.Unlock() + + action := chains.determineChainAction(newCfg) + assert.Equal(t, chainActionUpdate, action) + + chains.chainsMu.Lock() + delete(chains.chains, "eip155:1") + delete(chains.chainConfigs, "eip155:1") + chains.chainsMu.Unlock() + }) } func TestPerSyncTimeout(t *testing.T) { diff --git a/universalClient/chains/common/event_processor.go b/universalClient/chains/common/event_processor.go index 2b891d01..705306ac 100644 --- a/universalClient/chains/common/event_processor.go +++ b/universalClient/chains/common/event_processor.go @@ -20,13 +20,15 @@ import ( // EventProcessor processes events from the chain's database and votes on them type EventProcessor struct { - signer *pushsigner.Signer - chainStore *ChainStore - logger zerolog.Logger - chainID string - running bool - stopCh chan struct{} - wg sync.WaitGroup + signer *pushsigner.Signer + chainStore *ChainStore + logger zerolog.Logger + chainID string + inboundEnabled bool + outboundEnabled bool + running bool + stopCh chan struct{} + wg sync.WaitGroup } // NewEventProcessor creates a new event processor @@ -34,14 +36,18 @@ func NewEventProcessor( signer *pushsigner.Signer, database *db.DB, chainID string, + inboundEnabled bool, + outboundEnabled bool, logger zerolog.Logger, ) *EventProcessor { return &EventProcessor{ - signer: signer, - chainStore: NewChainStore(database), - chainID: chainID, - logger: logger.With().Str("component", "event_processor").Str("chain", chainID).Logger(), - stopCh: make(chan struct{}), + signer: signer, + chainStore: NewChainStore(database), + chainID: chainID, + inboundEnabled: inboundEnabled, + outboundEnabled: outboundEnabled, + logger: logger.With().Str("component", "event_processor").Str("chain", chainID).Logger(), + stopCh: make(chan struct{}), } } @@ -114,6 +120,10 @@ func (ep *EventProcessor) processConfirmedEvents(ctx context.Context) error { for _, event := range events { if event.Type == EventTypeInbound { + if !ep.inboundEnabled { + ep.logger.Warn().Str("event_id", event.EventID).Msg("inbound disabled, skipping inbound event processing") + continue + } if err := ep.processInboundEvent(ctx, &event); err != nil { ep.logger.Error(). Err(err). @@ -122,6 +132,10 @@ func (ep *EventProcessor) processConfirmedEvents(ctx context.Context) error { continue } } else if event.Type == EventTypeOutbound { + if !ep.outboundEnabled { + ep.logger.Warn().Str("event_id", event.EventID).Msg("outbound disabled, skipping outbound event processing") + continue + } if err := ep.processOutboundEvent(ctx, &event); err != nil { ep.logger.Error(). Err(err). diff --git a/universalClient/chains/common/event_processor_test.go b/universalClient/chains/common/event_processor_test.go index 11a281dc..b723db27 100644 --- a/universalClient/chains/common/event_processor_test.go +++ b/universalClient/chains/common/event_processor_test.go @@ -1,6 +1,7 @@ package common import ( + "context" "encoding/json" "testing" @@ -8,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + ucdb "github.com/pushchain/push-chain-node/universalClient/db" "github.com/pushchain/push-chain-node/universalClient/store" uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" ) @@ -17,7 +19,7 @@ func TestNewEventProcessor(t *testing.T) { logger := zerolog.Nop() chainID := "eip155:1" - processor := NewEventProcessor(nil, nil, chainID, logger) + processor := NewEventProcessor(nil, nil, chainID, true, true, logger) require.NotNil(t, processor) assert.Equal(t, chainID, processor.chainID) @@ -49,7 +51,7 @@ func TestEventProcessorStop(t *testing.T) { func TestEventProcessorBase58ToHex(t *testing.T) { logger := zerolog.Nop() - processor := NewEventProcessor(nil, nil, "test-chain", logger) + processor := NewEventProcessor(nil, nil, "test-chain", true, true, logger) t.Run("empty string returns 0x", func(t *testing.T) { result, err := processor.base58ToHex("") @@ -83,7 +85,7 @@ func TestEventProcessorBase58ToHex(t *testing.T) { func TestEventProcessorConstructInbound(t *testing.T) { logger := zerolog.Nop() - processor := NewEventProcessor(nil, nil, "eip155:1", logger) + processor := NewEventProcessor(nil, nil, "eip155:1", true, true, logger) t.Run("nil event returns error", func(t *testing.T) { inbound, err := processor.constructInbound(nil) @@ -172,7 +174,7 @@ func TestEventProcessorConstructInbound(t *testing.T) { func TestEventProcessorExtractOutboundIDs(t *testing.T) { logger := zerolog.Nop() - processor := NewEventProcessor(nil, nil, "eip155:1", logger) + processor := NewEventProcessor(nil, nil, "eip155:1", true, true, logger) t.Run("nil event returns error", func(t *testing.T) { txID, utxID, err := processor.extractOutboundIDs(nil) @@ -253,7 +255,7 @@ func TestEventProcessorExtractOutboundIDs(t *testing.T) { func TestEventProcessorExtractOutboundObservation(t *testing.T) { logger := zerolog.Nop() - processor := NewEventProcessor(nil, nil, "eip155:1", logger) + processor := NewEventProcessor(nil, nil, "eip155:1", true, true, logger) t.Run("nil event returns error", func(t *testing.T) { obs, err := processor.extractOutboundObservation(nil) @@ -302,3 +304,146 @@ func TestEventProcessorStruct(t *testing.T) { assert.Nil(t, ep.stopCh) }) } + +func TestNewEventProcessorEnabledFlags(t *testing.T) { + logger := zerolog.Nop() + + t.Run("both enabled", func(t *testing.T) { + ep := NewEventProcessor(nil, nil, "eip155:1", true, true, logger) + assert.True(t, ep.inboundEnabled) + assert.True(t, ep.outboundEnabled) + }) + + t.Run("inbound only", func(t *testing.T) { + ep := NewEventProcessor(nil, nil, "eip155:1", true, false, logger) + assert.True(t, ep.inboundEnabled) + assert.False(t, ep.outboundEnabled) + }) + + t.Run("outbound only", func(t *testing.T) { + ep := NewEventProcessor(nil, nil, "eip155:1", false, true, logger) + assert.False(t, ep.inboundEnabled) + assert.True(t, ep.outboundEnabled) + }) + + t.Run("both disabled", func(t *testing.T) { + ep := NewEventProcessor(nil, nil, "eip155:1", false, false, logger) + assert.False(t, ep.inboundEnabled) + assert.False(t, ep.outboundEnabled) + }) +} + +func TestProcessConfirmedEventsEnabledFlags(t *testing.T) { + logger := zerolog.Nop() + ctx := context.Background() + + // Helper to create an in-memory DB and seed confirmed events + setupDB := func(t *testing.T, events []store.Event) *ucdb.DB { + t.Helper() + database, err := ucdb.OpenInMemoryDB(true) + require.NoError(t, err) + for _, e := range events { + result := database.Client().Create(&e) + require.NoError(t, result.Error) + } + return database + } + + inboundEventData, _ := json.Marshal(UniversalTx{ + SourceChain: "eip155:1", + Sender: "0xsender", + Amount: "1000", + TxType: 2, + }) + + outboundEventData, _ := json.Marshal(OutboundEvent{ + TxID: "0xtxid", + UniversalTxID: "0xutxid", + }) + + makeEvents := func() []store.Event { + return []store.Event{ + { + EventID: "0xaaa:0", + Status: "CONFIRMED", + Type: EventTypeInbound, + EventData: inboundEventData, + }, + { + EventID: "0xbbb:0", + Status: "CONFIRMED", + Type: EventTypeOutbound, + EventData: outboundEventData, + }, + } + } + + t.Run("inbound disabled skips inbound events, leaves them CONFIRMED", func(t *testing.T) { + database := setupDB(t, makeEvents()) + // inbound=false, outbound=false (no signer so outbound will also fail to vote, but that's ok) + ep := NewEventProcessor(nil, database, "eip155:1", false, false, logger) + + err := ep.processConfirmedEvents(ctx) + require.NoError(t, err) + + // Inbound event should still be CONFIRMED (skipped, not processed) + var inboundEvt store.Event + database.Client().Where("event_id = ?", "0xaaa:0").First(&inboundEvt) + assert.Equal(t, "CONFIRMED", inboundEvt.Status) + }) + + t.Run("outbound disabled skips outbound events, leaves them CONFIRMED", func(t *testing.T) { + database := setupDB(t, makeEvents()) + ep := NewEventProcessor(nil, database, "eip155:1", false, false, logger) + + err := ep.processConfirmedEvents(ctx) + require.NoError(t, err) + + // Outbound event should still be CONFIRMED (skipped, not processed) + var outboundEvt store.Event + database.Client().Where("event_id = ?", "0xbbb:0").First(&outboundEvt) + assert.Equal(t, "CONFIRMED", outboundEvt.Status) + }) + + t.Run("inbound enabled but outbound disabled skips only outbound", func(t *testing.T) { + // Seed only outbound events so we don't hit nil signer panic on inbound + database := setupDB(t, []store.Event{ + { + EventID: "0xbbb:0", + Status: "CONFIRMED", + Type: EventTypeOutbound, + EventData: outboundEventData, + }, + }) + ep := NewEventProcessor(nil, database, "eip155:1", true, false, logger) + + err := ep.processConfirmedEvents(ctx) + require.NoError(t, err) + + // Outbound event should still be CONFIRMED (skipped due to outbound disabled) + var outboundEvt store.Event + database.Client().Where("event_id = ?", "0xbbb:0").First(&outboundEvt) + assert.Equal(t, "CONFIRMED", outboundEvt.Status) + }) + + t.Run("outbound enabled but inbound disabled skips only inbound", func(t *testing.T) { + // Seed only inbound events so we don't hit nil signer panic on outbound + database := setupDB(t, []store.Event{ + { + EventID: "0xaaa:0", + Status: "CONFIRMED", + Type: EventTypeInbound, + EventData: inboundEventData, + }, + }) + ep := NewEventProcessor(nil, database, "eip155:1", false, true, logger) + + err := ep.processConfirmedEvents(ctx) + require.NoError(t, err) + + // Inbound event should still be CONFIRMED (skipped due to inbound disabled) + var inboundEvt store.Event + database.Client().Where("event_id = ?", "0xaaa:0").First(&inboundEvt) + assert.Equal(t, "CONFIRMED", inboundEvt.Status) + }) +} diff --git a/universalClient/chains/evm/client.go b/universalClient/chains/evm/client.go index 15797e16..1bf5dd71 100644 --- a/universalClient/chains/evm/client.go +++ b/universalClient/chains/evm/client.go @@ -77,10 +77,14 @@ func NewClient( // Initialize components that don't require RPC client if pushSigner != nil { + inboundEnabled := config.Enabled != nil && config.Enabled.IsInboundEnabled + outboundEnabled := config.Enabled != nil && config.Enabled.IsOutboundEnabled client.eventProcessor = common.NewEventProcessor( pushSigner, database, chainIDStr, + inboundEnabled, + outboundEnabled, log, ) } diff --git a/universalClient/chains/svm/client.go b/universalClient/chains/svm/client.go index f8b8005b..30071b7e 100644 --- a/universalClient/chains/svm/client.go +++ b/universalClient/chains/svm/client.go @@ -86,10 +86,14 @@ func NewClient( // Initialize components that don't require RPC client if pushSigner != nil { + inboundEnabled := config.Enabled != nil && config.Enabled.IsInboundEnabled + outboundEnabled := config.Enabled != nil && config.Enabled.IsOutboundEnabled client.eventProcessor = common.NewEventProcessor( pushSigner, database, chainIDStr, + inboundEnabled, + outboundEnabled, log, ) } diff --git a/universalClient/tss/coordinator/coordinator.go b/universalClient/tss/coordinator/coordinator.go index 218365b6..05a01536 100644 --- a/universalClient/tss/coordinator/coordinator.go +++ b/universalClient/tss/coordinator/coordinator.go @@ -402,6 +402,15 @@ func (c *Coordinator) processConfirmedEvents(ctx context.Context) error { continue } + // Skip if outbound is disabled for destination chain + if !c.chains.IsChainOutboundEnabled(chain) { + c.logger.Warn(). + Str("chain", chain). + Str("event_id", event.EventID). + Msg("outbound disabled for destination chain, skipping TSS signing") + continue + } + nonce, ok := c.assignSignNonce(ctx, event, chain, inFlightPerChain, nonceByChain, skippedChains) if !ok { continue diff --git a/universalClient/tss/sessionmanager/sessionmanager.go b/universalClient/tss/sessionmanager/sessionmanager.go index cfbc871f..e74a4968 100644 --- a/universalClient/tss/sessionmanager/sessionmanager.go +++ b/universalClient/tss/sessionmanager/sessionmanager.go @@ -747,6 +747,11 @@ func (sm *SessionManager) verifySigningRequest(ctx context.Context, event *store return errors.New("destination chain is missing") } + // Reject signing if outbound is disabled for the destination chain + if sm.chains != nil && !sm.chains.IsChainOutboundEnabled(chainID) { + return errors.Errorf("outbound disabled for chain %s, refusing to sign", chainID) + } + if err := sm.validateGasPrice(ctx, chainID, req.GasPrice); err != nil { return errors.Wrap(err, "gas price validation failed") } diff --git a/universalClient/tss/sessionmanager/sessionmanager_test.go b/universalClient/tss/sessionmanager/sessionmanager_test.go index b89f7bac..28c45835 100644 --- a/universalClient/tss/sessionmanager/sessionmanager_test.go +++ b/universalClient/tss/sessionmanager/sessionmanager_test.go @@ -15,12 +15,18 @@ import ( "gorm.io/driver/sqlite" "gorm.io/gorm" + "math/big" + + "github.com/pushchain/push-chain-node/universalClient/chains" + "github.com/pushchain/push-chain-node/universalClient/chains/common" + "github.com/pushchain/push-chain-node/universalClient/config" "github.com/pushchain/push-chain-node/universalClient/pushcore" "github.com/pushchain/push-chain-node/universalClient/store" "github.com/pushchain/push-chain-node/universalClient/tss/coordinator" "github.com/pushchain/push-chain-node/universalClient/tss/dkls" "github.com/pushchain/push-chain-node/universalClient/tss/eventstore" "github.com/pushchain/push-chain-node/universalClient/tss/keyshare" + uexecutortypes "github.com/pushchain/push-chain-node/x/uexecutor/types" "github.com/pushchain/push-chain-node/x/uvalidator/types" ) @@ -385,3 +391,46 @@ func TestSessionManager_Integration(t *testing.T) { containsAny(err.Error(), []string{"failed to create session", "DKLS", "dkls", "session", "no endpoints"}), "error should be about session creation or endpoints, got: %s", err.Error()) } + +func TestVerifySigningRequest_OutboundDisabled(t *testing.T) { + sm, _, _, _, _, _ := setupTestSessionManager(t) + ctx := context.Background() + + // Create a Chains manager with empty maps — IsChainOutboundEnabled returns false for all chains + chainsManager := chains.NewChains(nil, nil, &config.Config{PushChainID: "test-chain"}, zerolog.Nop()) + sm.chains = chainsManager + + outboundData := uexecutortypes.OutboundCreatedEvent{ + DestinationChain: "eip155:1", + } + eventDataBytes, _ := json.Marshal(outboundData) + + event := &store.Event{ + EventID: "sign-event-1", + Type: "SIGN", + Status: eventstore.StatusConfirmed, + EventData: eventDataBytes, + } + + req := &common.UnSignedOutboundTxReq{ + SigningHash: []byte{0x01, 0x02, 0x03}, + GasPrice: big.NewInt(1000000000), + } + + t.Run("rejects signing when outbound disabled for destination chain", func(t *testing.T) { + err := sm.verifySigningRequest(ctx, event, req) + require.Error(t, err) + assert.Contains(t, err.Error(), "outbound disabled") + assert.Contains(t, err.Error(), "eip155:1") + }) + + t.Run("nil chains skips outbound check", func(t *testing.T) { + sm.chains = nil + err := sm.verifySigningRequest(ctx, event, req) + // Should pass the outbound check (skipped) and fail later on gas price validation + // or hash verification — but NOT on "outbound disabled" + if err != nil { + assert.NotContains(t, err.Error(), "outbound disabled") + } + }) +} diff --git a/universalClient/tss/txbroadcaster/broadcaster.go b/universalClient/tss/txbroadcaster/broadcaster.go index b7ffc089..d782fff7 100644 --- a/universalClient/tss/txbroadcaster/broadcaster.go +++ b/universalClient/tss/txbroadcaster/broadcaster.go @@ -117,6 +117,14 @@ func (b *Broadcaster) broadcastEvent(ctx context.Context, event *store.Event) { } chainID := data.DestinationChain + + // Skip if outbound is disabled for destination chain + if !b.chains.IsChainOutboundEnabled(chainID) { + b.logger.Warn().Str("chain", chainID).Str("event_id", event.EventID). + Msg("outbound disabled for destination chain, skipping broadcast") + return + } + if b.chains.IsEVMChain(chainID) { b.broadcastEVM(ctx, event, data, chainID) } else { diff --git a/universalClient/tss/txbroadcaster/broadcaster_test.go b/universalClient/tss/txbroadcaster/broadcaster_test.go index 707665f8..f22eadce 100644 --- a/universalClient/tss/txbroadcaster/broadcaster_test.go +++ b/universalClient/tss/txbroadcaster/broadcaster_test.go @@ -92,7 +92,14 @@ func newTestChains(t *testing.T, chainID string, vmType uregistrytypes.VmType, c configsField := v.FieldByName("chainConfigs") configsMap := *(*map[string]*uregistrytypes.ChainConfig)(unsafe.Pointer(configsField.UnsafeAddr())) - configsMap[chainID] = &uregistrytypes.ChainConfig{Chain: chainID, VmType: vmType} + configsMap[chainID] = &uregistrytypes.ChainConfig{ + Chain: chainID, + VmType: vmType, + Enabled: &uregistrytypes.ChainEnabled{ + IsInboundEnabled: true, + IsOutboundEnabled: true, + }, + } return c } diff --git a/universalClient/tss/txresolver/resolver.go b/universalClient/tss/txresolver/resolver.go index 0fd264f9..9ce9ebb8 100644 --- a/universalClient/tss/txresolver/resolver.go +++ b/universalClient/tss/txresolver/resolver.go @@ -107,7 +107,13 @@ func (r *Resolver) processBroadcasted(ctx context.Context) { } func (r *Resolver) resolveEvent(ctx context.Context, event *store.Event) { + // Extract chain ID early to check outbound flag chainID, rawTxHash, err := parseCAIPTxHash(event.BroadcastedTxHash) + if err == nil && !r.chains.IsChainOutboundEnabled(chainID) { + r.logger.Warn().Str("chain", chainID).Str("event_id", event.EventID). + Msg("outbound disabled for destination chain, skipping resolution") + return + } if err != nil { txID, utxID, extractErr := extractOutboundIDs(event) if extractErr != nil { diff --git a/universalClient/tss/txresolver/resolver_test.go b/universalClient/tss/txresolver/resolver_test.go index 52f1581e..a635e3ee 100644 --- a/universalClient/tss/txresolver/resolver_test.go +++ b/universalClient/tss/txresolver/resolver_test.go @@ -100,7 +100,14 @@ func newTestChains(t *testing.T, chainID string, vmType uregistrytypes.VmType, c configsField := v.FieldByName("chainConfigs") configsMap := *(*map[string]*uregistrytypes.ChainConfig)(unsafe.Pointer(configsField.UnsafeAddr())) - configsMap[chainID] = &uregistrytypes.ChainConfig{Chain: chainID, VmType: vmType} + configsMap[chainID] = &uregistrytypes.ChainConfig{ + Chain: chainID, + VmType: vmType, + Enabled: &uregistrytypes.ChainEnabled{ + IsInboundEnabled: true, + IsOutboundEnabled: true, + }, + } return c }